Skip to content

Commit b018d0f

Browse files
authored
Fix Fiber leaks if Suspension never resumed, v2 (#82)
1 parent 8405671 commit b018d0f

4 files changed

Lines changed: 19 additions & 22 deletions

File tree

.php-cs-fixer.dist.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public function getRules(): array
2828
"allow_single_line_closure" => true,
2929
],
3030
"array_syntax" => ["syntax" => "short"],
31+
"blank_lines_before_namespace" => true,
3132
"cast_spaces" => true,
3233
"combine_consecutive_unsets" => true,
3334
"declare_strict_types" => true,
@@ -73,7 +74,6 @@ public function getRules(): array
7374
"psr_autoloading" => ['dir' => $this->src],
7475
"return_type_declaration" => ["space_before" => "none"],
7576
"short_scalar_cast" => true,
76-
"single_blank_line_before_namespace" => true,
7777
"line_ending" => true,
7878
];
7979
}

src/EventLoop/Internal/AbstractDriver.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,12 +285,21 @@ public function getSuspension(): Suspension
285285
\assert($fiber !== $this->fiber);
286286

287287
// Use current object in case of {main}
288-
return $this->suspensions[$fiber ?? $this] ??= new DriverSuspension(
288+
$suspension = ($this->suspensions[$fiber ?? $this] ?? null)?->get();
289+
if ($suspension) {
290+
return $suspension;
291+
}
292+
293+
$suspension = new DriverSuspension(
289294
$this->runCallback,
290295
$this->queueCallback,
291296
$this->interruptCallback,
292-
$this->suspensions
297+
$this->suspensions,
293298
);
299+
300+
$this->suspensions[$fiber ?? $this] = \WeakReference::create($suspension);
301+
302+
return $suspension;
294303
}
295304

296305
public function setErrorHandler(?\Closure $errorHandler): void

src/EventLoop/Internal/DriverSuspension.php

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,15 @@ final class DriverSuspension implements Suspension
2323

2424
private bool $pending = false;
2525

26-
private readonly \WeakReference $suspensions;
27-
28-
/**
29-
* @param \Closure $run
30-
* @param \Closure $queue
31-
* @param \Closure $interrupt
32-
*
33-
* @internal
34-
*/
3526
public function __construct(
3627
private readonly \Closure $run,
3728
private readonly \Closure $queue,
3829
private readonly \Closure $interrupt,
39-
\WeakMap $suspensions
30+
private readonly \WeakMap $suspensions,
4031
) {
4132
$fiber = \Fiber::getCurrent();
4233

4334
$this->fiberRef = $fiber ? \WeakReference::create($fiber) : null;
44-
$this->suspensions = \WeakReference::create($suspensions);
4535
}
4636

4737
public function resume(mixed $value = null): void
@@ -101,13 +91,12 @@ public function suspend(): mixed
10191
$this->pending = false;
10292
$result && $result(); // Unwrap any uncaught exceptions from the event loop
10393

104-
$info = '';
105-
$suspensions = $this->suspensions->get();
106-
if ($suspensions) {
107-
\gc_collect_cycles();
94+
\gc_collect_cycles(); // Collect any circular references before dumping pending suspensions.
10895

109-
/** @var self $suspension */
110-
foreach ($suspensions as $suspension) {
96+
$info = '';
97+
foreach ($this->suspensions as $suspensionRef) {
98+
if ($suspension = $suspensionRef->get()) {
99+
\assert($suspension instanceof self);
111100
$fiber = $suspension->fiberRef?->get();
112101
if ($fiber === null) {
113102
continue;

test/EventLoopTest.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,7 @@ public function testSuspensionWithinCallbackGarbageCollectionSuspended(): void
269269

270270
\gc_collect_cycles();
271271

272-
// This documents an expected failure, should actually be true, but suspensions have to be resumed currently.
273-
self::assertNull($finally);
272+
self::assertTrue($finally);
274273
}
275274

276275
public function testSuspensionWithinQueue(): void

0 commit comments

Comments
 (0)