Skip to content

Commit 9bd9d48

Browse files
authored
Merge pull request #124 from clue-labs/static
Improve memory consumption for pending promises by using static internal callbacks without binding to self
2 parents 5e60e55 + ef228a9 commit 9bd9d48

5 files changed

Lines changed: 157 additions & 6 deletions

File tree

src/Promise.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public function done(callable $onFulfilled = null, callable $onRejected = null,
6161
return $this->result->done($onFulfilled, $onRejected, $onProgress);
6262
}
6363

64-
$this->handlers[] = function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) {
64+
$this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) {
6565
$promise
6666
->done($onFulfilled, $onRejected);
6767
};
@@ -73,7 +73,7 @@ public function done(callable $onFulfilled = null, callable $onRejected = null,
7373

7474
public function otherwise(callable $onRejected)
7575
{
76-
return $this->then(null, function ($reason) use ($onRejected) {
76+
return $this->then(null, static function ($reason) use ($onRejected) {
7777
if (!_checkTypehint($onRejected, $reason)) {
7878
return new RejectedPromise($reason);
7979
}
@@ -84,11 +84,11 @@ public function otherwise(callable $onRejected)
8484

8585
public function always(callable $onFulfilledOrRejected)
8686
{
87-
return $this->then(function ($value) use ($onFulfilledOrRejected) {
87+
return $this->then(static function ($value) use ($onFulfilledOrRejected) {
8888
return resolve($onFulfilledOrRejected())->then(function () use ($value) {
8989
return $value;
9090
});
91-
}, function ($reason) use ($onFulfilledOrRejected) {
91+
}, static function ($reason) use ($onFulfilledOrRejected) {
9292
return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
9393
return new RejectedPromise($reason);
9494
});
@@ -116,7 +116,7 @@ private function resolver(callable $onFulfilled = null, callable $onRejected = n
116116
{
117117
return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) {
118118
if ($onProgress) {
119-
$progressHandler = function ($update) use ($notify, $onProgress) {
119+
$progressHandler = static function ($update) use ($notify, $onProgress) {
120120
try {
121121
$notify($onProgress($update));
122122
} catch (\Throwable $e) {
@@ -129,7 +129,7 @@ private function resolver(callable $onFulfilled = null, callable $onRejected = n
129129
$progressHandler = $notify;
130130
}
131131

132-
$this->handlers[] = function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
132+
$this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
133133
$promise
134134
->then($onFulfilled, $onRejected)
135135
->done($resolve, $reject, $progressHandler);

tests/DeferredTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,37 @@ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerHoldsReferenc
7676

7777
$this->assertSame(0, gc_collect_cycles());
7878
}
79+
80+
/** @test */
81+
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingDeferred()
82+
{
83+
gc_collect_cycles();
84+
$deferred = new Deferred();
85+
$deferred->promise();
86+
unset($deferred);
87+
88+
$this->assertSame(0, gc_collect_cycles());
89+
}
90+
91+
/** @test */
92+
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingDeferredWithUnusedCanceller()
93+
{
94+
gc_collect_cycles();
95+
$deferred = new Deferred(function () { });
96+
$deferred->promise();
97+
unset($deferred);
98+
99+
$this->assertSame(0, gc_collect_cycles());
100+
}
101+
102+
/** @test */
103+
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingDeferredWithNoopCanceller()
104+
{
105+
gc_collect_cycles();
106+
$deferred = new Deferred(function () { });
107+
$deferred->promise()->cancel();
108+
unset($deferred);
109+
110+
$this->assertSame(0, gc_collect_cycles());
111+
}
79112
}

tests/FulfilledPromiseTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,30 @@ public function shouldThrowExceptionIfConstructedWithAPromise()
4747

4848
return new FulfilledPromise(new FulfilledPromise());
4949
}
50+
51+
/** @test */
52+
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToFulfilledPromiseWithAlwaysFollowers()
53+
{
54+
gc_collect_cycles();
55+
$promise = new FulfilledPromise(1);
56+
$promise->always(function () {
57+
throw new \RuntimeException();
58+
});
59+
unset($promise);
60+
61+
$this->assertSame(0, gc_collect_cycles());
62+
}
63+
64+
/** @test */
65+
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToFulfilledPromiseWithThenFollowers()
66+
{
67+
gc_collect_cycles();
68+
$promise = new FulfilledPromise(1);
69+
$promise = $promise->then(function () {
70+
throw new \RuntimeException();
71+
});
72+
unset($promise);
73+
74+
$this->assertSame(0, gc_collect_cycles());
75+
}
5076
}

tests/PromiseTest.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,72 @@ public function shouldIgnoreNotifyAfterReject()
191191
$promise->cancel();
192192
}
193193

194+
195+
/** @test */
196+
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromise()
197+
{
198+
gc_collect_cycles();
199+
$promise = new Promise(function () { });
200+
unset($promise);
201+
202+
$this->assertSame(0, gc_collect_cycles());
203+
}
204+
205+
/** @test */
206+
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithThenFollowers()
207+
{
208+
gc_collect_cycles();
209+
$promise = new Promise(function () { });
210+
$promise->then()->then()->then();
211+
unset($promise);
212+
213+
$this->assertSame(0, gc_collect_cycles());
214+
}
215+
216+
/** @test */
217+
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithDoneFollowers()
218+
{
219+
gc_collect_cycles();
220+
$promise = new Promise(function () { });
221+
$promise->done();
222+
unset($promise);
223+
224+
$this->assertSame(0, gc_collect_cycles());
225+
}
226+
227+
/** @test */
228+
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithOtherwiseFollowers()
229+
{
230+
gc_collect_cycles();
231+
$promise = new Promise(function () { });
232+
$promise->otherwise(function () { });
233+
unset($promise);
234+
235+
$this->assertSame(0, gc_collect_cycles());
236+
}
237+
238+
/** @test */
239+
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithAlwaysFollowers()
240+
{
241+
gc_collect_cycles();
242+
$promise = new Promise(function () { });
243+
$promise->always(function () { });
244+
unset($promise);
245+
246+
$this->assertSame(0, gc_collect_cycles());
247+
}
248+
249+
/** @test */
250+
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithProgressFollowers()
251+
{
252+
gc_collect_cycles();
253+
$promise = new Promise(function () { });
254+
$promise->then(null, null, function () { });
255+
unset($promise);
256+
257+
$this->assertSame(0, gc_collect_cycles());
258+
}
259+
194260
/** @test */
195261
public function shouldFulfillIfFullfilledWithSimplePromise()
196262
{

tests/RejectedPromiseTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,30 @@ public function shouldThrowExceptionIfConstructedWithAPromise()
4747

4848
return new RejectedPromise(new RejectedPromise());
4949
}
50+
51+
/** @test */
52+
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToRejectedPromiseWithAlwaysFollowers()
53+
{
54+
gc_collect_cycles();
55+
$promise = new RejectedPromise(1);
56+
$promise->always(function () {
57+
throw new \RuntimeException();
58+
});
59+
unset($promise);
60+
61+
$this->assertSame(0, gc_collect_cycles());
62+
}
63+
64+
/** @test */
65+
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToRejectedPromiseWithThenFollowers()
66+
{
67+
gc_collect_cycles();
68+
$promise = new RejectedPromise(1);
69+
$promise = $promise->then(null, function () {
70+
throw new \RuntimeException();
71+
});
72+
unset($promise);
73+
74+
$this->assertSame(0, gc_collect_cycles());
75+
}
5076
}

0 commit comments

Comments
 (0)