Skip to content

Commit e521e57

Browse files
authored
Merge pull request #118 from clue-labs/stack
Improve memory consumption by hiding resolver and canceller references from call stack on PHP 7+
2 parents 27e3b95 + c00b39f commit e521e57

2 files changed

Lines changed: 57 additions & 2 deletions

File tree

src/Promise.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ class Promise implements ExtendedPromiseInterface, CancellablePromiseInterface
1515
public function __construct(callable $resolver, callable $canceller = null)
1616
{
1717
$this->canceller = $canceller;
18-
$this->call($resolver);
18+
19+
// Explicitly overwrite arguments with null values before invoking
20+
// resolver function. This ensure that these arguments do not show up
21+
// in the stack trace in PHP 7+ only.
22+
$cb = $resolver;
23+
$resolver = $canceller = null;
24+
$this->call($cb);
1925
}
2026

2127
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
@@ -228,8 +234,13 @@ private function extract($promise)
228234
return $promise;
229235
}
230236

231-
private function call(callable $callback)
237+
private function call(callable $cb)
232238
{
239+
// Explicitly overwrite argument with null value. This ensure that this
240+
// argument does not show up in the stack trace in PHP 7+ only.
241+
$callback = $cb;
242+
$cb = null;
243+
233244
// Use reflection to inspect number of arguments expected by this callback.
234245
// We did some careful benchmarking here: Using reflection to avoid unneeded
235246
// function arguments is actually faster than blindly passing them.

tests/PromiseTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,50 @@ public function shouldRejectWithoutCreatingGarbageCyclesIfResolverThrowsExceptio
122122
$this->assertSame(0, gc_collect_cycles());
123123
}
124124

125+
/**
126+
* Test that checks number of garbage cycles after throwing from a canceller
127+
* that explicitly uses a reference to the promise. This is rather synthetic,
128+
* actual use cases often have implicit (hidden) references which ought not
129+
* to be stored in the stack trace.
130+
*
131+
* Reassigned arguments only show up in the stack trace in PHP 7, so we can't
132+
* avoid this on legacy PHP. As an alternative, consider explicitly unsetting
133+
* any references before throwing.
134+
*
135+
* @test
136+
* @requires PHP 7
137+
*/
138+
public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException()
139+
{
140+
gc_collect_cycles();
141+
142+
$promise = new Promise(function () {}, function () use (&$promise) {
143+
throw new \Exception('foo');
144+
});
145+
$promise->cancel();
146+
unset($promise);
147+
148+
$this->assertSame(0, gc_collect_cycles());
149+
}
150+
151+
/**
152+
* @test
153+
* @requires PHP 7
154+
* @see self::shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException
155+
*/
156+
public function shouldRejectWithoutCreatingGarbageCyclesIfResolverWithReferenceThrowsException()
157+
{
158+
gc_collect_cycles();
159+
160+
$promise = new Promise(function () use (&$promise) {
161+
throw new \Exception('foo');
162+
});
163+
164+
unset($promise);
165+
166+
$this->assertSame(0, gc_collect_cycles());
167+
}
168+
125169
/** @test */
126170
public function shouldIgnoreNotifyAfterReject()
127171
{

0 commit comments

Comments
 (0)