From e755b858932144fdab6e3821620a7793384fd020 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 28 Jan 2023 20:49:14 +0100 Subject: [PATCH 01/11] Implement custom FiberFactory interface --- src/EventLoop/DefaultFiberFactory.php | 20 +++++++ src/EventLoop/Driver/EvDriver.php | 5 +- src/EventLoop/Driver/EventDriver.php | 5 +- src/EventLoop/Driver/StreamSelectDriver.php | 5 +- src/EventLoop/Driver/UvDriver.php | 5 +- src/EventLoop/FiberFactory.php | 17 ++++++ src/EventLoop/FiberLocal.php | 10 ++-- src/EventLoop/Internal/AbstractDriver.php | 14 +++-- src/EventLoop/TracingFiberFactory.php | 62 +++++++++++++++++++++ test/Driver/TracingFiberFactoryTest.php | 36 ++++++++++++ 10 files changed, 161 insertions(+), 18 deletions(-) create mode 100644 src/EventLoop/DefaultFiberFactory.php create mode 100644 src/EventLoop/FiberFactory.php create mode 100644 src/EventLoop/TracingFiberFactory.php create mode 100644 test/Driver/TracingFiberFactoryTest.php diff --git a/src/EventLoop/DefaultFiberFactory.php b/src/EventLoop/DefaultFiberFactory.php new file mode 100644 index 0000000..9dafbb5 --- /dev/null +++ b/src/EventLoop/DefaultFiberFactory.php @@ -0,0 +1,20 @@ + */ private array $signals = []; - public function __construct() + public function __construct(?FiberFactory $fiberFactory = null) { - parent::__construct(); + parent::__construct($fiberFactory); $this->handle = new \EvLoop(); diff --git a/src/EventLoop/Driver/EventDriver.php b/src/EventLoop/Driver/EventDriver.php index 869c3a9..7e8475a 100644 --- a/src/EventLoop/Driver/EventDriver.php +++ b/src/EventLoop/Driver/EventDriver.php @@ -6,6 +6,7 @@ namespace Revolt\EventLoop\Driver; +use Revolt\EventLoop\FiberFactory; use Revolt\EventLoop\Internal\AbstractDriver; use Revolt\EventLoop\Internal\DriverCallback; use Revolt\EventLoop\Internal\SignalCallback; @@ -34,9 +35,9 @@ public static function isSupported(): bool /** @var array */ private array $signals = []; - public function __construct() + public function __construct(?FiberFactory $fiberFactory = null) { - parent::__construct(); + parent::__construct($fiberFactory); /** @psalm-suppress TooFewArguments https://github.com/JetBrains/phpstorm-stubs/pull/763 */ $this->handle = new \EventBase(); diff --git a/src/EventLoop/Driver/StreamSelectDriver.php b/src/EventLoop/Driver/StreamSelectDriver.php index a9ceb85..a30fec3 100644 --- a/src/EventLoop/Driver/StreamSelectDriver.php +++ b/src/EventLoop/Driver/StreamSelectDriver.php @@ -6,6 +6,7 @@ namespace Revolt\EventLoop\Driver; +use Revolt\EventLoop\FiberFactory; use Revolt\EventLoop\Internal\AbstractDriver; use Revolt\EventLoop\Internal\DriverCallback; use Revolt\EventLoop\Internal\SignalCallback; @@ -43,9 +44,9 @@ final class StreamSelectDriver extends AbstractDriver private bool $streamSelectIgnoreResult = false; - public function __construct() + public function __construct(?FiberFactory $fiberFactory = null) { - parent::__construct(); + parent::__construct($fiberFactory); $this->signalQueue = new \SplQueue(); $this->timerQueue = new TimerQueue(); diff --git a/src/EventLoop/Driver/UvDriver.php b/src/EventLoop/Driver/UvDriver.php index 1ae72a4..43fc8bb 100644 --- a/src/EventLoop/Driver/UvDriver.php +++ b/src/EventLoop/Driver/UvDriver.php @@ -4,6 +4,7 @@ namespace Revolt\EventLoop\Driver; +use Revolt\EventLoop\FiberFactory; use Revolt\EventLoop\Internal\AbstractDriver; use Revolt\EventLoop\Internal\DriverCallback; use Revolt\EventLoop\Internal\SignalCallback; @@ -31,9 +32,9 @@ public static function isSupported(): bool private readonly \Closure $timerCallback; private readonly \Closure $signalCallback; - public function __construct() + public function __construct(?FiberFactory $fiberFactory = null) { - parent::__construct(); + parent::__construct($fiberFactory); $this->handle = \uv_loop_new(); diff --git a/src/EventLoop/FiberFactory.php b/src/EventLoop/FiberFactory.php new file mode 100644 index 0000000..c4aae40 --- /dev/null +++ b/src/EventLoop/FiberFactory.php @@ -0,0 +1,17 @@ + */ private readonly \SplQueue $callbackQueue; + private readonly FiberFactory $fiberFactory; + private bool $idle = false; private bool $stopped = false; private \WeakMap $suspensions; - public function __construct() + public function __construct(?FiberFactory $fiberFactory = null) { + $this->fiberFactory = $fiberFactory ?? new DefaultFiberFactory(); + $this->suspensions = new \WeakMap(); $this->internalSuspensionMarker = new \stdClass(); @@ -382,7 +388,7 @@ final protected function error(\Closure $closure, \Throwable $exception): void return; } - $fiber = new \Fiber($this->errorCallback); + $fiber = $this->fiberFactory->create($this->errorCallback); /** @noinspection PhpUnhandledExceptionInspection */ $fiber->start($this->errorHandler, $exception); @@ -511,7 +517,7 @@ private function invokeInterrupt(): void private function createLoopFiber(): void { - $this->fiber = new \Fiber(function (): void { + $this->fiber = $this->fiberFactory->create(function (): void { $this->stopped = false; // Invoke microtasks if we have some @@ -537,7 +543,7 @@ private function createLoopFiber(): void private function createCallbackFiber(): void { - $this->callbackFiber = new \Fiber(function (): void { + $this->callbackFiber = $this->fiberFactory->create(function (): void { do { $this->invokeMicrotasks(); diff --git a/src/EventLoop/TracingFiberFactory.php b/src/EventLoop/TracingFiberFactory.php new file mode 100644 index 0000000..c244fdd --- /dev/null +++ b/src/EventLoop/TracingFiberFactory.php @@ -0,0 +1,62 @@ + + */ +final class TracingFiberFactory implements FiberFactory, Countable, IteratorAggregate +{ + /** + * @var \WeakMap<\Fiber, null> + */ + private \WeakMap $map; + + public function __construct() + { + /** @var \WeakMap<\Fiber, null> */ + $this->map = new \WeakMap(); + } + + /** + * Creates a new fiber instance. + * + * @param callable $callable The callable to invoke when starting the fiber. + * + * @return \Fiber + */ + public function create(callable $callback): \Fiber + { + $f = new \Fiber($callback); + $this->map[$f] = null; + return $f; + } + + /** + * Returns the number of running fibers. + * + * @return int + */ + public function count(): int + { + return $this->map->count(); + } + + /** + * Iterate over all currently running fibers. + * + * @return Traversable<\Fiber, null> + */ + public function getIterator(): Traversable + { + return $this->map->getIterator(); + } +} diff --git a/test/Driver/TracingFiberFactoryTest.php b/test/Driver/TracingFiberFactoryTest.php new file mode 100644 index 0000000..4123e6b --- /dev/null +++ b/test/Driver/TracingFiberFactoryTest.php @@ -0,0 +1,36 @@ +count()); + $this->start(static function (Driver $loop): void { + $loop->queue(static function () use ($loop) { + $suspension = $loop->getSuspension(); + $loop->delay(1, $suspension->resume(...)); + $suspension->suspend(); + }); + $loop->delay(0.5, function () { + self::assertEquals(3, self::$factory->count()); + }); + }); + self::assertEquals(2, self::$factory->count()); + } +} From 8551b3e9760edb76d8131a000b3ecbc3d1331f1b Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 28 Jan 2023 21:10:59 +0100 Subject: [PATCH 02/11] Fix description --- src/EventLoop/TracingFiberFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EventLoop/TracingFiberFactory.php b/src/EventLoop/TracingFiberFactory.php index c244fdd..0c803e3 100644 --- a/src/EventLoop/TracingFiberFactory.php +++ b/src/EventLoop/TracingFiberFactory.php @@ -51,7 +51,7 @@ public function count(): int } /** - * Iterate over all currently running fibers. + * Iterate over all fibers currently in scope. * * @return Traversable<\Fiber, null> */ From f2f634f945624d9a4463b4e80a6dcc0b8846f5cc Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 28 Jan 2023 21:38:48 +0100 Subject: [PATCH 03/11] cs-fix --- src/EventLoop/FiberLocal.php | 3 ++- test/Driver/TracingFiberFactoryTest.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/EventLoop/FiberLocal.php b/src/EventLoop/FiberLocal.php index 4ceebb2..1a3e1c0 100644 --- a/src/EventLoop/FiberLocal.php +++ b/src/EventLoop/FiberLocal.php @@ -37,7 +37,8 @@ private static function getFiberStorage(): \WeakMap $fiber = \Fiber::getCurrent(); if ($fiber === null) { - $fiber = self::$dummyMain ??= new class {}; + $fiber = self::$dummyMain ??= new class () { + }; } $localStorage = self::$localStorage ??= new \WeakMap(); diff --git a/test/Driver/TracingFiberFactoryTest.php b/test/Driver/TracingFiberFactoryTest.php index 4123e6b..5ef1dd5 100644 --- a/test/Driver/TracingFiberFactoryTest.php +++ b/test/Driver/TracingFiberFactoryTest.php @@ -12,7 +12,7 @@ class TracingFiberFactoryTest extends StreamSelectDriverTest private static TracingFiberFactory $factory; public function getFactory(): callable { - self::$factory ??= new TracingFiberFactory; + self::$factory ??= new TracingFiberFactory(); return static function (): StreamSelectDriver { return new StreamSelectDriver(self::$factory); }; From f897d2f5f32da1d51f62ef745ce98a850452f992 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Wed, 1 May 2024 12:54:12 +0200 Subject: [PATCH 04/11] Allow passing fiber factory to tracing fiber factory --- src/EventLoop/TracingFiberFactory.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/EventLoop/TracingFiberFactory.php b/src/EventLoop/TracingFiberFactory.php index 0c803e3..7e92f86 100644 --- a/src/EventLoop/TracingFiberFactory.php +++ b/src/EventLoop/TracingFiberFactory.php @@ -18,10 +18,11 @@ final class TracingFiberFactory implements FiberFactory, Countable, IteratorAggr /** * @var \WeakMap<\Fiber, null> */ - private \WeakMap $map; + private readonly \WeakMap $map; - public function __construct() - { + public function __construct( + private readonly FiberFactory $fiberFactory = new DefaultFiberFactory() + ) { /** @var \WeakMap<\Fiber, null> */ $this->map = new \WeakMap(); } @@ -35,7 +36,7 @@ public function __construct() */ public function create(callable $callback): \Fiber { - $f = new \Fiber($callback); + $f = $this->fiberFactory->create($callback); $this->map[$f] = null; return $f; } From 41098ec2e3b7553974d44d94d7e13ab46497e2e5 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 7 Dec 2024 19:19:46 +0100 Subject: [PATCH 05/11] Use fiber for errors as well --- src/EventLoop/Internal/AbstractDriver.php | 37 ++++++++++++++--------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/EventLoop/Internal/AbstractDriver.php b/src/EventLoop/Internal/AbstractDriver.php index dd801a7..84ec96c 100644 --- a/src/EventLoop/Internal/AbstractDriver.php +++ b/src/EventLoop/Internal/AbstractDriver.php @@ -4,6 +4,7 @@ namespace Revolt\EventLoop\Internal; +use Fiber; use Revolt\EventLoop\CallbackType; use Revolt\EventLoop\DefaultFiberFactory; use Revolt\EventLoop\Driver; @@ -31,7 +32,7 @@ abstract class AbstractDriver implements Driver private \Fiber $fiber; private \Fiber $callbackFiber; - private \Closure $errorCallback; + private \Fiber $errorFiber; /** @var array */ private array $callbacks = []; @@ -87,7 +88,7 @@ public function __construct(?FiberFactory $fiberFactory = null) $this->createLoopFiber(); $this->createCallbackFiber(); - $this->createErrorCallback(); + $this->createErrorFiber(); /** @psalm-suppress InvalidArgument */ $this->interruptCallback = $this->setInterrupt(...); @@ -407,10 +408,14 @@ final protected function error(\Closure $closure, \Throwable $exception): void return; } - $fiber = $this->fiberFactory->create($this->errorCallback); + if ($this->errorFiber->isTerminated()) { + $this->createErrorFiber(); + } /** @noinspection PhpUnhandledExceptionInspection */ - $fiber->start($this->errorHandler, $exception); + if ($this->errorFiber->resume($exception) !== $this->internalSuspensionMarker) { + $this->createErrorFiber(); + } } /** @@ -628,16 +633,20 @@ private function createCallbackFiber(): void }); } - private function createErrorCallback(): void + private function createErrorFiber(): void { - $this->errorCallback = function (\Closure $errorHandler, \Throwable $exception): void { - try { - $errorHandler($exception); - } catch (\Throwable $exception) { - $this->interrupt = static fn () => $exception instanceof UncaughtThrowable - ? throw $exception - : throw UncaughtThrowable::throwingErrorHandler($errorHandler, $exception); - } - }; + $this->errorFiber = new \Fiber(function (): void { + do { + try { + $exception = Fiber::suspend($this->internalSuspensionMarker); + ($this->errorHandler)($exception); + } catch (\Throwable $exception) { + $this->interrupt = static fn () => $exception instanceof UncaughtThrowable + ? throw $exception + : throw UncaughtThrowable::throwingErrorHandler($this->errorHandler, $exception); + } + } while (true); + }); + $this->errorFiber->start(); } } From 8e4dbf1adb419e6251a13f3bce627669949acc33 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 7 Dec 2024 19:20:51 +0100 Subject: [PATCH 06/11] Use fiber for errors as well --- src/EventLoop/Internal/AbstractDriver.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/EventLoop/Internal/AbstractDriver.php b/src/EventLoop/Internal/AbstractDriver.php index 84ec96c..eab718e 100644 --- a/src/EventLoop/Internal/AbstractDriver.php +++ b/src/EventLoop/Internal/AbstractDriver.php @@ -641,9 +641,10 @@ private function createErrorFiber(): void $exception = Fiber::suspend($this->internalSuspensionMarker); ($this->errorHandler)($exception); } catch (\Throwable $exception) { + $errorHandler = $this->errorHandler; $this->interrupt = static fn () => $exception instanceof UncaughtThrowable ? throw $exception - : throw UncaughtThrowable::throwingErrorHandler($this->errorHandler, $exception); + : throw UncaughtThrowable::throwingErrorHandler($errorHandler, $exception); } } while (true); }); From 0eb7d92b19e122365025d0b38163eaed9370af7b Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 7 Dec 2024 19:24:32 +0100 Subject: [PATCH 07/11] Cleanup --- src/EventLoop/Internal/AbstractDriver.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/EventLoop/Internal/AbstractDriver.php b/src/EventLoop/Internal/AbstractDriver.php index eab718e..ae21aad 100644 --- a/src/EventLoop/Internal/AbstractDriver.php +++ b/src/EventLoop/Internal/AbstractDriver.php @@ -411,9 +411,10 @@ final protected function error(\Closure $closure, \Throwable $exception): void if ($this->errorFiber->isTerminated()) { $this->createErrorFiber(); } - /** @noinspection PhpUnhandledExceptionInspection */ - if ($this->errorFiber->resume($exception) !== $this->internalSuspensionMarker) { + $yielded = $this->errorFiber->isStarted() ? $this->errorFiber->resume($exception) : $this->errorFiber->start($exception); + + if ($$yielded !== $this->internalSuspensionMarker) { $this->createErrorFiber(); } } @@ -635,10 +636,10 @@ private function createCallbackFiber(): void private function createErrorFiber(): void { - $this->errorFiber = new \Fiber(function (): void { + $this->errorFiber = new \Fiber(function (\Throwable $exception): void { do { try { - $exception = Fiber::suspend($this->internalSuspensionMarker); + $exception ??= Fiber::suspend($this->internalSuspensionMarker); ($this->errorHandler)($exception); } catch (\Throwable $exception) { $errorHandler = $this->errorHandler; @@ -646,8 +647,8 @@ private function createErrorFiber(): void ? throw $exception : throw UncaughtThrowable::throwingErrorHandler($errorHandler, $exception); } + $exception = null; } while (true); }); - $this->errorFiber->start(); } } From b0c11f7262ec9715c1338382c6b2f2627ec2c8df Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 7 Dec 2024 19:26:21 +0100 Subject: [PATCH 08/11] Cleanup --- src/EventLoop/Internal/AbstractDriver.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/EventLoop/Internal/AbstractDriver.php b/src/EventLoop/Internal/AbstractDriver.php index ae21aad..2ee9c76 100644 --- a/src/EventLoop/Internal/AbstractDriver.php +++ b/src/EventLoop/Internal/AbstractDriver.php @@ -639,7 +639,6 @@ private function createErrorFiber(): void $this->errorFiber = new \Fiber(function (\Throwable $exception): void { do { try { - $exception ??= Fiber::suspend($this->internalSuspensionMarker); ($this->errorHandler)($exception); } catch (\Throwable $exception) { $errorHandler = $this->errorHandler; @@ -647,7 +646,7 @@ private function createErrorFiber(): void ? throw $exception : throw UncaughtThrowable::throwingErrorHandler($errorHandler, $exception); } - $exception = null; + $exception = Fiber::suspend($this->internalSuspensionMarker); } while (true); }); } From d62780a36df750683ed7f936fa2afa90d10f2385 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 7 Dec 2024 19:26:45 +0100 Subject: [PATCH 09/11] Cleanup --- src/EventLoop/Internal/AbstractDriver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EventLoop/Internal/AbstractDriver.php b/src/EventLoop/Internal/AbstractDriver.php index 2ee9c76..b794420 100644 --- a/src/EventLoop/Internal/AbstractDriver.php +++ b/src/EventLoop/Internal/AbstractDriver.php @@ -414,7 +414,7 @@ final protected function error(\Closure $closure, \Throwable $exception): void /** @noinspection PhpUnhandledExceptionInspection */ $yielded = $this->errorFiber->isStarted() ? $this->errorFiber->resume($exception) : $this->errorFiber->start($exception); - if ($$yielded !== $this->internalSuspensionMarker) { + if ($yielded !== $this->internalSuspensionMarker) { $this->createErrorFiber(); } } From da27e69e0174622e748a15d7c73f9765fdfcb48f Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 7 Dec 2024 19:30:44 +0100 Subject: [PATCH 10/11] Cleanup --- src/EventLoop/Internal/AbstractDriver.php | 2 ++ src/EventLoop/TracingFiberFactory.php | 1 + 2 files changed, 3 insertions(+) diff --git a/src/EventLoop/Internal/AbstractDriver.php b/src/EventLoop/Internal/AbstractDriver.php index b794420..c3a0764 100644 --- a/src/EventLoop/Internal/AbstractDriver.php +++ b/src/EventLoop/Internal/AbstractDriver.php @@ -639,9 +639,11 @@ private function createErrorFiber(): void $this->errorFiber = new \Fiber(function (\Throwable $exception): void { do { try { + assert($this->errorHandler !== null); ($this->errorHandler)($exception); } catch (\Throwable $exception) { $errorHandler = $this->errorHandler; + assert($errorHandler !== null); $this->interrupt = static fn () => $exception instanceof UncaughtThrowable ? throw $exception : throw UncaughtThrowable::throwingErrorHandler($errorHandler, $exception); diff --git a/src/EventLoop/TracingFiberFactory.php b/src/EventLoop/TracingFiberFactory.php index 7e92f86..6da816b 100644 --- a/src/EventLoop/TracingFiberFactory.php +++ b/src/EventLoop/TracingFiberFactory.php @@ -37,6 +37,7 @@ public function __construct( public function create(callable $callback): \Fiber { $f = $this->fiberFactory->create($callback); + /** @psalm-suppress InaccessibleProperty */ $this->map[$f] = null; return $f; } From a5ad0b62d0cc82f4180bccff5d46d798b44393e1 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 7 Dec 2024 19:35:07 +0100 Subject: [PATCH 11/11] cs-fix --- src/EventLoop/Internal/AbstractDriver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EventLoop/Internal/AbstractDriver.php b/src/EventLoop/Internal/AbstractDriver.php index c3a0764..4601d1a 100644 --- a/src/EventLoop/Internal/AbstractDriver.php +++ b/src/EventLoop/Internal/AbstractDriver.php @@ -639,11 +639,11 @@ private function createErrorFiber(): void $this->errorFiber = new \Fiber(function (\Throwable $exception): void { do { try { - assert($this->errorHandler !== null); + \assert($this->errorHandler !== null); ($this->errorHandler)($exception); } catch (\Throwable $exception) { $errorHandler = $this->errorHandler; - assert($errorHandler !== null); + \assert($errorHandler !== null); $this->interrupt = static fn () => $exception instanceof UncaughtThrowable ? throw $exception : throw UncaughtThrowable::throwingErrorHandler($errorHandler, $exception);