diff --git a/src/EventLoop/Internal/AbstractDriver.php b/src/EventLoop/Internal/AbstractDriver.php index e1d49f0..ded274f 100644 --- a/src/EventLoop/Internal/AbstractDriver.php +++ b/src/EventLoop/Internal/AbstractDriver.php @@ -48,7 +48,7 @@ abstract class AbstractDriver implements Driver private readonly \Closure $interruptCallback; private readonly \Closure $queueCallback; - /** @var \Closure(): ((\Closure(): mixed)|bool|null) */ + /** @var \Closure(): ((\Closure(): mixed)|bool) */ private readonly \Closure $runCallback; private readonly \stdClass $internalSuspensionMarker; @@ -89,13 +89,13 @@ public function __construct() $this->interruptCallback = $this->setInterrupt(...); $this->queueCallback = $this->queue(...); $this->runCallback = - /** @return (\Closure(): mixed)|bool|null */ - function (): \Closure|bool|null { + /** @return (\Closure(): mixed)|bool */ + function (): \Closure|bool { if ($this->fiber->isTerminated()) { $this->createLoopFiber(); } - return $this->fiber->isStarted() ? $this->fiber->resume() : $this->fiber->start(); + return ($this->fiber->isStarted() ? $this->fiber->resume() : $this->fiber->start()) ?? $this->fiber->getReturn(); }; } @@ -493,9 +493,11 @@ private function tick(bool $previousIdle): void $this->dispatch($blocking); } - private function invokeCallbacks(): void + private function invokeCallbacks(): bool { + $didWork = false; while (!$this->microtaskQueue->isEmpty() || !$this->callbackQueue->isEmpty()) { + $didWork = true; /** @noinspection PhpUnhandledExceptionInspection */ $yielded = $this->callbackFiber->isStarted() ? $this->callbackFiber->resume() @@ -509,6 +511,7 @@ private function invokeCallbacks(): void $this->invokeInterrupt(); } } + return $didWork; } /** @@ -534,28 +537,31 @@ private function invokeInterrupt(): void private function createLoopFiber(): void { - $this->fiber = new \Fiber(function (): void { + $this->fiber = new \Fiber(function (): bool { $this->stopped = false; // Invoke microtasks if we have some - $this->invokeCallbacks(); + $didWork = $this->invokeCallbacks(); /** @psalm-suppress RedundantCondition $this->stopped may be changed by $this->invokeCallbacks(). */ while (!$this->stopped) { if ($this->interrupt) { + $didWork = true; $this->invokeInterrupt(); } if ($this->isEmpty()) { - return; + return $didWork; } $previousIdle = $this->idle; $this->idle = true; $this->tick($previousIdle); - $this->invokeCallbacks(); + $didWork = $this->invokeCallbacks(); } + + return $didWork; }); } diff --git a/src/EventLoop/Internal/DriverSuspension.php b/src/EventLoop/Internal/DriverSuspension.php index d19ccbf..9a4dae6 100644 --- a/src/EventLoop/Internal/DriverSuspension.php +++ b/src/EventLoop/Internal/DriverSuspension.php @@ -28,7 +28,7 @@ final class DriverSuspension implements Suspension private bool $deadMain = false; /** - * @param \Closure(): ((\Closure(): mixed)|bool|null) $run + * @param \Closure(): ((\Closure(): mixed)|bool) $run * @param \WeakMap> $suspensions */ public function __construct( @@ -117,8 +117,11 @@ public function suspend(): mixed // Awaiting from {main}. $result = ($this->run)(); - while ($this->pending && $result === false) { - + if ($this->pending && \is_bool($result)) { + do { + while (\gc_collect_cycles()); + $result = ($this->run)(); + } while ($this->pending && $result === true); } /** @psalm-suppress RedundantCondition $this->pending should be changed when resumed. */