@@ -21,10 +21,12 @@ final class DriverSuspension implements Suspension
2121 /** @var \WeakReference<\Fiber>|null */
2222 private readonly ?\WeakReference $ fiberRef ;
2323
24- private ?\FiberError $ fiberError = null ;
24+ private ?\Error $ error = null ;
2525
2626 private bool $ pending = false ;
2727
28+ private bool $ deadMain = false ;
29+
2830 public function __construct (
2931 private readonly \Closure $ run ,
3032 private readonly \Closure $ queue ,
@@ -38,8 +40,13 @@ public function __construct(
3840
3941 public function resume (mixed $ value = null ): void
4042 {
43+ // Ignore spurious resumes to old dead {main} suspension
44+ if ($ this ->deadMain ) {
45+ return ;
46+ }
47+
4148 if (!$ this ->pending ) {
42- throw $ this ->fiberError ?? new \Error ('Must call suspend() before calling resume() ' );
49+ throw $ this ->error ?? new \Error ('Must call suspend() before calling resume() ' );
4350 }
4451
4552 $ this ->pending = false ;
@@ -62,6 +69,13 @@ public function resume(mixed $value = null): void
6269
6370 public function suspend (): mixed
6471 {
72+ // Throw exception when trying to use old dead {main} suspension
73+ if ($ this ->deadMain ) {
74+ throw new \Error (
75+ 'Suspension cannot be suspended after an uncaught exception is thrown from the event loop ' ,
76+ );
77+ }
78+
6579 if ($ this ->pending ) {
6680 throw new \Error ('Must call resume() or throw() before calling suspend() again ' );
6781 }
@@ -73,6 +87,7 @@ public function suspend(): mixed
7387 }
7488
7589 $ this ->pending = true ;
90+ $ this ->error = null ;
7691
7792 // Awaiting from within a fiber.
7893 if ($ fiber ) {
@@ -81,12 +96,12 @@ public function suspend(): mixed
8196 try {
8297 $ value = \Fiber::suspend ();
8398 $ this ->suspendedFiber = null ;
84- } catch (\FiberError $ exception ) {
99+ } catch (\FiberError $ error ) {
85100 $ this ->pending = false ;
86101 $ this ->suspendedFiber = null ;
87- $ this ->fiberError = $ exception ;
102+ $ this ->error = $ error ;
88103
89- throw $ exception ;
104+ throw $ error ;
90105 }
91106
92107 // Setting $this->suspendedFiber = null in finally will set the fiber to null if a fiber is destroyed
@@ -100,7 +115,9 @@ public function suspend(): mixed
100115
101116 /** @psalm-suppress RedundantCondition $this->pending should be changed when resumed. */
102117 if ($ this ->pending ) {
103- $ this ->pending = false ;
118+ // This is now a dead {main} suspension.
119+ $ this ->deadMain = true ;
120+
104121 $ result && $ result (); // Unwrap any uncaught exceptions from the event loop
105122
106123 \gc_collect_cycles (); // Collect any circular references before dumping pending suspensions.
@@ -127,8 +144,13 @@ public function suspend(): mixed
127144
128145 public function throw (\Throwable $ throwable ): void
129146 {
147+ // Ignore spurious resumes to old dead {main} suspension
148+ if ($ this ->deadMain ) {
149+ return ;
150+ }
151+
130152 if (!$ this ->pending ) {
131- throw $ this ->fiberError ?? new \Error ('Must call suspend() before calling throw() ' );
153+ throw $ this ->error ?? new \Error ('Must call suspend() before calling throw() ' );
132154 }
133155
134156 $ this ->pending = false ;
0 commit comments