From 625f50ada1a17194c3aa942910f796b68dfe6460 Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Fri, 27 Jan 2023 16:17:09 -0600 Subject: [PATCH 01/10] Rethrow uncaught exception from {main} suspension --- src/EventLoop/Internal/DriverSuspension.php | 17 ++++++++++++----- test/EventLoopTest.php | 7 +++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/EventLoop/Internal/DriverSuspension.php b/src/EventLoop/Internal/DriverSuspension.php index a1d9778..4032262 100644 --- a/src/EventLoop/Internal/DriverSuspension.php +++ b/src/EventLoop/Internal/DriverSuspension.php @@ -19,7 +19,7 @@ final class DriverSuspension implements Suspension /** @var \WeakReference<\Fiber>|null */ private readonly ?\WeakReference $fiberRef; - private ?\FiberError $fiberError = null; + private ?\Throwable $exception = null; private bool $pending = false; @@ -47,7 +47,7 @@ public function __construct( public function resume(mixed $value = null): void { if (!$this->pending) { - throw $this->fiberError ?? new \Error('Must call suspend() before calling resume()'); + throw $this->exception ?? new \Error('Must call suspend() before calling resume()'); } $this->pending = false; @@ -85,7 +85,7 @@ public function suspend(): mixed return \Fiber::suspend(); } catch (\FiberError $exception) { $this->pending = false; - $this->fiberError = $exception; + $this->exception = $exception; throw $exception; } finally { @@ -99,7 +99,14 @@ public function suspend(): mixed /** @psalm-suppress RedundantCondition $this->pending should be changed when resumed. */ if ($this->pending) { $this->pending = false; - $result && $result(); // Unwrap any uncaught exceptions from the event loop + + try { + $result && $result(); // Unwrap any uncaught exceptions from the event loop + } catch (\Throwable $exception) { + $this->exception = $exception; + + throw $exception; + } $info = ''; $suspensions = $this->suspensions->get(); @@ -127,7 +134,7 @@ public function suspend(): mixed public function throw(\Throwable $throwable): void { if (!$this->pending) { - throw $this->fiberError ?? new \Error('Must call suspend() before calling throw()'); + throw $this->exception ?? new \Error('Must call suspend() before calling throw()'); } $this->pending = false; diff --git a/test/EventLoopTest.php b/test/EventLoopTest.php index 9420b9f..c9d0f0a 100644 --- a/test/EventLoopTest.php +++ b/test/EventLoopTest.php @@ -299,5 +299,12 @@ public function testSuspensionThrowingErrorViaInterrupt(): void } catch (UncaughtThrowable $t) { self::assertSame($error, $t->getPrevious()); } + + try { + $suspension->resume(); // Should throw same exception as above. + self::fail("Error was not thrown"); + } catch (UncaughtThrowable $u) { + self::assertSame($t, $u); + } } } From f81313e401a2d8db370889d135c0cfd9363c6a19 Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Fri, 27 Jan 2023 16:22:40 -0600 Subject: [PATCH 02/10] Null exception if suspension is {main} is suspended again --- src/EventLoop/Internal/DriverSuspension.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EventLoop/Internal/DriverSuspension.php b/src/EventLoop/Internal/DriverSuspension.php index 4032262..5d3ce28 100644 --- a/src/EventLoop/Internal/DriverSuspension.php +++ b/src/EventLoop/Internal/DriverSuspension.php @@ -76,6 +76,7 @@ public function suspend(): mixed } $this->pending = true; + $this->exception = null; // Awaiting from within a fiber. if ($fiber) { From 020e23b71ec2c11605291d37eb0a1742bc201ed1 Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Sat, 28 Jan 2023 09:50:19 -0600 Subject: [PATCH 03/10] s/exception/throwable --- src/EventLoop/Internal/DriverSuspension.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/EventLoop/Internal/DriverSuspension.php b/src/EventLoop/Internal/DriverSuspension.php index 5d3ce28..9b4daca 100644 --- a/src/EventLoop/Internal/DriverSuspension.php +++ b/src/EventLoop/Internal/DriverSuspension.php @@ -19,7 +19,7 @@ final class DriverSuspension implements Suspension /** @var \WeakReference<\Fiber>|null */ private readonly ?\WeakReference $fiberRef; - private ?\Throwable $exception = null; + private ?\Throwable $throwable = null; private bool $pending = false; @@ -47,7 +47,7 @@ public function __construct( public function resume(mixed $value = null): void { if (!$this->pending) { - throw $this->exception ?? new \Error('Must call suspend() before calling resume()'); + throw $this->throwable ?? new \Error('Must call suspend() before calling resume()'); } $this->pending = false; @@ -76,7 +76,7 @@ public function suspend(): mixed } $this->pending = true; - $this->exception = null; + $this->throwable = null; // Awaiting from within a fiber. if ($fiber) { @@ -84,11 +84,11 @@ public function suspend(): mixed try { return \Fiber::suspend(); - } catch (\FiberError $exception) { + } catch (\FiberError $error) { $this->pending = false; - $this->exception = $exception; + $this->throwable = $error; - throw $exception; + throw $error; } finally { $this->suspendedFiber = null; } @@ -103,10 +103,10 @@ public function suspend(): mixed try { $result && $result(); // Unwrap any uncaught exceptions from the event loop - } catch (\Throwable $exception) { - $this->exception = $exception; + } catch (\Throwable $throwable) { + $this->throwable = $throwable; - throw $exception; + throw $throwable; } $info = ''; @@ -135,7 +135,7 @@ public function suspend(): mixed public function throw(\Throwable $throwable): void { if (!$this->pending) { - throw $this->exception ?? new \Error('Must call suspend() before calling throw()'); + throw $this->throwable ?? new \Error('Must call suspend() before calling throw()'); } $this->pending = false; From 5c72a6e451221d4b97054839fc0389398a993f03 Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Sat, 11 Feb 2023 15:21:13 -0600 Subject: [PATCH 04/10] Discard cached suspension when throwing to {main} --- src/EventLoop/Internal/AbstractDriver.php | 10 +++++----- test/EventLoopTest.php | 12 ++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/EventLoop/Internal/AbstractDriver.php b/src/EventLoop/Internal/AbstractDriver.php index 78578bd..7a3f36a 100644 --- a/src/EventLoop/Internal/AbstractDriver.php +++ b/src/EventLoop/Internal/AbstractDriver.php @@ -379,6 +379,7 @@ final protected function error(\Closure $closure, \Throwable $exception): void $this->interrupt = static fn () => $exception instanceof UncaughtThrowable ? throw $exception : throw UncaughtThrowable::throwingCallback($closure, $exception); + unset($this->suspensions[$this]); // Remove suspension for {main} return; } @@ -608,11 +609,10 @@ private function createErrorCallback(): void try { $errorHandler($exception); } catch (\Throwable $exception) { - $this->setInterrupt( - static fn () => $exception instanceof UncaughtThrowable - ? throw $exception - : throw UncaughtThrowable::throwingErrorHandler($errorHandler, $exception) - ); + $this->interrupt = static fn () => $exception instanceof UncaughtThrowable + ? throw $exception + : throw UncaughtThrowable::throwingErrorHandler($errorHandler, $exception); + unset($this->suspensions[$this]); // Remove suspension for {main} } }; } diff --git a/test/EventLoopTest.php b/test/EventLoopTest.php index c9d0f0a..b1e9afb 100644 --- a/test/EventLoopTest.php +++ b/test/EventLoopTest.php @@ -306,5 +306,17 @@ public function testSuspensionThrowingErrorViaInterrupt(): void } catch (UncaughtThrowable $u) { self::assertSame($t, $u); } + + try { + $suspension->resume(); // Calling resume on the same suspension should throw UncaughtThrowable. + self::fail("Error was not thrown"); + } catch (UncaughtThrowable $t) { + self::assertSame($t, $u); + } + + // Creating a new Suspension and re-entering the event loop (e.g. in a shutdown function) should work. + $suspension = EventLoop::getSuspension(); + EventLoop::queue($suspension->resume(...)); + $suspension->suspend(); } } From 6b0a36335dcf56d54abee98edce92742c3f36599 Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Sat, 11 Feb 2023 18:22:03 -0600 Subject: [PATCH 05/10] Do not store loop exception in suspension --- src/EventLoop/Internal/DriverSuspension.php | 15 +++++++++------ test/EventLoopTest.php | 13 +++---------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/EventLoop/Internal/DriverSuspension.php b/src/EventLoop/Internal/DriverSuspension.php index 9b4daca..85a3a41 100644 --- a/src/EventLoop/Internal/DriverSuspension.php +++ b/src/EventLoop/Internal/DriverSuspension.php @@ -5,6 +5,7 @@ namespace Revolt\EventLoop\Internal; use Revolt\EventLoop\Suspension; +use Revolt\EventLoop\UncaughtThrowable; /** * @internal @@ -19,7 +20,7 @@ final class DriverSuspension implements Suspension /** @var \WeakReference<\Fiber>|null */ private readonly ?\WeakReference $fiberRef; - private ?\Throwable $throwable = null; + private ?\Error $error = null; private bool $pending = false; @@ -47,7 +48,7 @@ public function __construct( public function resume(mixed $value = null): void { if (!$this->pending) { - throw $this->throwable ?? new \Error('Must call suspend() before calling resume()'); + throw $this->error ?? new \Error('Must call suspend() before calling resume()'); } $this->pending = false; @@ -76,7 +77,7 @@ public function suspend(): mixed } $this->pending = true; - $this->throwable = null; + $this->error = null; // Awaiting from within a fiber. if ($fiber) { @@ -86,7 +87,7 @@ public function suspend(): mixed return \Fiber::suspend(); } catch (\FiberError $error) { $this->pending = false; - $this->throwable = $error; + $this->error = $error; throw $error; } finally { @@ -104,7 +105,9 @@ public function suspend(): mixed try { $result && $result(); // Unwrap any uncaught exceptions from the event loop } catch (\Throwable $throwable) { - $this->throwable = $throwable; + $this->error = new \Error( + 'Suspension cannot be resumed after an uncaught exception is thrown from the event loop', + ); throw $throwable; } @@ -135,7 +138,7 @@ public function suspend(): mixed public function throw(\Throwable $throwable): void { if (!$this->pending) { - throw $this->throwable ?? new \Error('Must call suspend() before calling throw()'); + throw $this->error ?? new \Error('Must call suspend() before calling throw()'); } $this->pending = false; diff --git a/test/EventLoopTest.php b/test/EventLoopTest.php index b1e9afb..fa1dac7 100644 --- a/test/EventLoopTest.php +++ b/test/EventLoopTest.php @@ -301,17 +301,10 @@ public function testSuspensionThrowingErrorViaInterrupt(): void } try { - $suspension->resume(); // Should throw same exception as above. + $suspension->resume(); // Calling resume on the same suspension should throw an Error. self::fail("Error was not thrown"); - } catch (UncaughtThrowable $u) { - self::assertSame($t, $u); - } - - try { - $suspension->resume(); // Calling resume on the same suspension should throw UncaughtThrowable. - self::fail("Error was not thrown"); - } catch (UncaughtThrowable $t) { - self::assertSame($t, $u); + } catch (\Error $e) { + self::assertStringContainsString('resumed after an uncaught exception', $e->getMessage()); } // Creating a new Suspension and re-entering the event loop (e.g. in a shutdown function) should work. From 17e40d4199a35775e2b09efb91c939e2e638a4eb Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Sat, 11 Feb 2023 18:27:03 -0600 Subject: [PATCH 06/10] Remove unused import --- src/EventLoop/Internal/DriverSuspension.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/EventLoop/Internal/DriverSuspension.php b/src/EventLoop/Internal/DriverSuspension.php index 85a3a41..0d51649 100644 --- a/src/EventLoop/Internal/DriverSuspension.php +++ b/src/EventLoop/Internal/DriverSuspension.php @@ -5,7 +5,6 @@ namespace Revolt\EventLoop\Internal; use Revolt\EventLoop\Suspension; -use Revolt\EventLoop\UncaughtThrowable; /** * @internal From 67e96b3eb95df54641323b64a724a2c019de03fa Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Sat, 11 Feb 2023 18:34:27 -0600 Subject: [PATCH 07/10] Store other thrown Error as well --- src/EventLoop/Internal/DriverSuspension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EventLoop/Internal/DriverSuspension.php b/src/EventLoop/Internal/DriverSuspension.php index 0d51649..e7ecd97 100644 --- a/src/EventLoop/Internal/DriverSuspension.php +++ b/src/EventLoop/Internal/DriverSuspension.php @@ -128,7 +128,7 @@ public function suspend(): mixed } } - throw new \Error('Event loop terminated without resuming the current suspension:' . $info); + throw $this->error = new \Error('Event loop terminated without resuming the current suspension:' . $info); } return $result(); From cfed1a97d79b0926ffcc60f9f73eb95632111d9b Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 18 Nov 2023 16:59:07 +0100 Subject: [PATCH 08/10] Ignore spurious resumes on {main} suspension (#88) --- composer.json | 2 +- psalm-baseline.xml | 43 +++++++++++++++++++++ psalm.xml | 1 + src/EventLoop.php | 4 +- src/EventLoop/Driver.php | 4 +- src/EventLoop/Internal/DriverSuspension.php | 33 ++++++++++------ test/EventLoopTest.php | 36 ++++++++++++++++- 7 files changed, 105 insertions(+), 18 deletions(-) create mode 100644 psalm-baseline.xml diff --git a/composer.json b/composer.json index 18e66e7..c69c780 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "ext-json": "*", "phpunit/phpunit": "^9", "jetbrains/phpstorm-stubs": "^2019.3", - "psalm/phar": "^4.7" + "psalm/phar": "^5" }, "autoload": { "psr-4": { diff --git a/psalm-baseline.xml b/psalm-baseline.xml new file mode 100644 index 0000000..9aed453 --- /dev/null +++ b/psalm-baseline.xml @@ -0,0 +1,43 @@ + + + + + signals]]> + signals]]> + + + + + signals]]> + signals]]> + + + + + (int) ($timeout * 1_000_000) + + + + + \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) + \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) + \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) + \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) + \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) + \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) + \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) + + + + + handle, $callback->stream)]]> + handle)]]> + signalCallback, $callback->signal)]]> + + + + + stopped]]> + + + diff --git a/psalm.xml b/psalm.xml index 7c63f8b..42fe89f 100644 --- a/psalm.xml +++ b/psalm.xml @@ -5,6 +5,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" + errorBaseline="psalm-baseline.xml" > diff --git a/src/EventLoop.php b/src/EventLoop.php index 347f588..1b81136 100644 --- a/src/EventLoop.php +++ b/src/EventLoop.php @@ -346,7 +346,7 @@ public static function getType(string $callbackId): CallbackType * * @param string $callbackId The callback identifier. * - * @return bool {@code true} if the callback is currently enabled, otherwise {@code false}. + * @return bool `true` if the callback is currently enabled, otherwise `false`. */ public static function isEnabled(string $callbackId): bool { @@ -358,7 +358,7 @@ public static function isEnabled(string $callbackId): bool * * @param string $callbackId The callback identifier. * - * @return bool {@code true} if the callback is currently referenced, otherwise {@code false}. + * @return bool `true` if the callback is currently referenced, otherwise `false`. */ public static function isReferenced(string $callbackId): bool { diff --git a/src/EventLoop/Driver.php b/src/EventLoop/Driver.php index 84e5e8a..80464aa 100644 --- a/src/EventLoop/Driver.php +++ b/src/EventLoop/Driver.php @@ -294,7 +294,7 @@ public function getType(string $callbackId): CallbackType; * * @param string $callbackId The callback identifier. * - * @return bool {@code true} if the callback is currently enabled, otherwise {@code false}. + * @return bool `true` if the callback is currently enabled, otherwise `false`. */ public function isEnabled(string $callbackId): bool; @@ -303,7 +303,7 @@ public function isEnabled(string $callbackId): bool; * * @param string $callbackId The callback identifier. * - * @return bool {@code true} if the callback is currently referenced, otherwise {@code false}. + * @return bool `true` if the callback is currently referenced, otherwise `false`. */ public function isReferenced(string $callbackId): bool; diff --git a/src/EventLoop/Internal/DriverSuspension.php b/src/EventLoop/Internal/DriverSuspension.php index e11a21f..590421f 100644 --- a/src/EventLoop/Internal/DriverSuspension.php +++ b/src/EventLoop/Internal/DriverSuspension.php @@ -25,6 +25,8 @@ final class DriverSuspension implements Suspension private bool $pending = false; + private bool $deadMain = false; + public function __construct( private readonly \Closure $run, private readonly \Closure $queue, @@ -38,6 +40,11 @@ public function __construct( public function resume(mixed $value = null): void { + // Ignore spurious resumes to old dead {main} suspension + if ($this->deadMain) { + return; + } + if (!$this->pending) { throw $this->error ?? new \Error('Must call suspend() before calling resume()'); } @@ -62,6 +69,12 @@ public function resume(mixed $value = null): void public function suspend(): mixed { + // Throw exception when trying to use old dead {main} suspension + if ($this->deadMain) { + throw new \Error( + 'Suspension cannot be suspended after an uncaught exception is thrown from the event loop', + ); + } if ($this->pending) { throw new \Error('Must call resume() or throw() before calling suspend() again'); } @@ -101,17 +114,10 @@ public function suspend(): mixed /** @psalm-suppress RedundantCondition $this->pending should be changed when resumed. */ if ($this->pending) { - $this->pending = false; + // This is now a dead {main} suspension. + $this->deadMain = true; - try { - $result && $result(); // Unwrap any uncaught exceptions from the event loop - } catch (\Throwable $throwable) { - $this->error = new \Error( - 'Suspension cannot be resumed after an uncaught exception is thrown from the event loop', - ); - - throw $throwable; - } + $result && $result(); // Unwrap any uncaught exceptions from the event loop \gc_collect_cycles(); // Collect any circular references before dumping pending suspensions. @@ -129,7 +135,7 @@ public function suspend(): mixed } } - throw $this->error = new \Error('Event loop terminated without resuming the current suspension (the cause is either a fiber deadlock, or an incorrectly unreferenced/canceled watcher):' . $info); + throw new \Error('Event loop terminated without resuming the current suspension (the cause is either a fiber deadlock, or an incorrectly unreferenced/canceled watcher):' . $info); } return $result(); @@ -137,6 +143,11 @@ public function suspend(): mixed public function throw(\Throwable $throwable): void { + // Ignore spurious resumes to old dead {main} suspension + if ($this->deadMain) { + return; + } + if (!$this->pending) { throw $this->error ?? new \Error('Must call suspend() before calling throw()'); } diff --git a/test/EventLoopTest.php b/test/EventLoopTest.php index 4f01d13..8f334f2 100644 --- a/test/EventLoopTest.php +++ b/test/EventLoopTest.php @@ -299,11 +299,14 @@ public function testSuspensionThrowingErrorViaInterrupt(): void self::assertSame($error, $t->getPrevious()); } + $suspension->resume(); // Calling resume on the same suspension should not throw an Error. + $suspension->throw(new \RuntimeException()); // Calling throw on the same suspension should not throw an Error. + try { - $suspension->resume(); // Calling resume on the same suspension should throw an Error. + $suspension->suspend(); // Calling suspend on the same suspension should throw an Error. self::fail("Error was not thrown"); } catch (\Error $e) { - self::assertStringContainsString('resumed after an uncaught exception', $e->getMessage()); + self::assertStringContainsString('suspended after an uncaught exception', $e->getMessage()); } // Creating a new Suspension and re-entering the event loop (e.g. in a shutdown function) should work. @@ -312,6 +315,35 @@ public function testSuspensionThrowingErrorViaInterrupt(): void $suspension->suspend(); } + public function testSuspensionThrowingErrorViaInterrupt2(): void + { + $suspension = EventLoop::getSuspension(); + $error = new \Error("Test error"); + EventLoop::queue(static fn () => throw $error); + EventLoop::queue($suspension->resume(...), 123); + try { + $suspension->suspend(); + self::fail("Error was not thrown"); + } catch (UncaughtThrowable $t) { + self::assertSame($error, $t->getPrevious()); + } + + $suspension->resume(); // Calling resume on the same suspension should not throw an Error. + $suspension->throw(new \RuntimeException()); // Calling throw on the same suspension should not throw an Error. + + try { + $suspension->suspend(); // Calling suspend on the same suspension should throw an Error. + self::fail("Error was not thrown"); + } catch (\Error $e) { + self::assertStringContainsString('suspended after an uncaught exception', $e->getMessage()); + } + + // Creating a new Suspension and re-entering the event loop (e.g. in a shutdown function) should work. + $suspension = EventLoop::getSuspension(); + EventLoop::queue($suspension->resume(...), 321); + $this->assertEquals(321, $suspension->suspend()); + } + public function testFiberDestroyedWhileSuspended(): void { $outer = new class (new class ($this) { From 0668dbe5841f3c84c1f5666b7076ceab5aea8c51 Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Sat, 18 Nov 2023 10:22:27 -0600 Subject: [PATCH 09/10] Remove Psalm baseline --- psalm-baseline.xml | 43 ------------------------------------------- psalm.xml | 1 - 2 files changed, 44 deletions(-) delete mode 100644 psalm-baseline.xml diff --git a/psalm-baseline.xml b/psalm-baseline.xml deleted file mode 100644 index 9aed453..0000000 --- a/psalm-baseline.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - signals]]> - signals]]> - - - - - signals]]> - signals]]> - - - - - (int) ($timeout * 1_000_000) - - - - - \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) - \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) - \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) - \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) - \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) - \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) - \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) - - - - - handle, $callback->stream)]]> - handle)]]> - signalCallback, $callback->signal)]]> - - - - - stopped]]> - - - diff --git a/psalm.xml b/psalm.xml index 97e89ec..f99206e 100644 --- a/psalm.xml +++ b/psalm.xml @@ -5,7 +5,6 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" - errorBaseline="psalm-baseline.xml" > From fc9aca706ca7d06e237764442b78a3a14c3e399c Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Sat, 18 Nov 2023 10:29:51 -0600 Subject: [PATCH 10/10] Spacing nit --- src/EventLoop/Internal/DriverSuspension.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EventLoop/Internal/DriverSuspension.php b/src/EventLoop/Internal/DriverSuspension.php index 590421f..14aeb87 100644 --- a/src/EventLoop/Internal/DriverSuspension.php +++ b/src/EventLoop/Internal/DriverSuspension.php @@ -75,6 +75,7 @@ public function suspend(): mixed 'Suspension cannot be suspended after an uncaught exception is thrown from the event loop', ); } + if ($this->pending) { throw new \Error('Must call resume() or throw() before calling suspend() again'); }