Skip to content

Commit c53fabf

Browse files
authored
Pass interrupt as a callback to Suspension (#8)
This swaps `interrupt()` as a method on `Driver` for `createSuspension()`, which accepts the currently active event loop fiber and passes the interrupt function as a cached closure to the `Suspension` constructor.
1 parent a6ef6c0 commit c53fabf

5 files changed

Lines changed: 34 additions & 32 deletions

File tree

src/EventLoop.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ public static function createSuspension(): Suspension
367367
self::$fiber = self::createFiber();
368368
}
369369

370-
return new Suspension(self::getDriver(), self::$fiber);
370+
return self::getDriver()->createSuspension(self::$fiber);
371371
}
372372

373373
/**

src/EventLoop/Driver.php

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,13 @@ public function run(): void;
3030
public function stop(): void;
3131

3232
/**
33-
* Interrupts the event loop and continues with {main}.
33+
* Create an object used to suspend and resume execution, either within a fiber or from {main}.
3434
*
35-
* The driver MUST check for a set interrupt after invoking an event callback or microtask. If an interrupt exists,
36-
* it must be reset, and the driver must suspend with the given callback, i.e. call \Fiber::suspend($callback);
35+
* @param \Fiber $scheduler Fiber containing the running event loop.
3736
*
38-
* @param callable $callback Callback to run on {main} before continuing.
39-
*
40-
* @internal This API is only supposed to be called by the Suspension API.
37+
* @return Suspension
4138
*/
42-
public function interrupt(callable $callback): void;
39+
public function createSuspension(\Fiber $scheduler): Suspension;
4340

4441
/**
4542
* @return bool True if the event loop is running, false if it is stopped.

src/EventLoop/Driver/TracingDriver.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Revolt\EventLoop\Driver;
66
use Revolt\EventLoop\InvalidCallbackError;
7+
use Revolt\EventLoop\Suspension;
78

89
final class TracingDriver implements Driver
910
{
@@ -36,9 +37,9 @@ public function stop(): void
3637
$this->driver->stop();
3738
}
3839

39-
public function interrupt(callable $callback): void
40+
public function createSuspension(\Fiber $scheduler): Suspension
4041
{
41-
$this->driver->interrupt($callback);
42+
return $this->driver->createSuspension($scheduler);
4243
}
4344

4445
public function isRunning(): bool

src/EventLoop/Internal/AbstractDriver.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Revolt\EventLoop;
66
use Revolt\EventLoop\Driver;
77
use Revolt\EventLoop\InvalidCallbackError;
8+
use Revolt\EventLoop\Suspension;
89
use Revolt\EventLoop\UnsupportedFeatureException;
910

1011
/**
@@ -46,6 +47,8 @@ abstract class AbstractDriver implements Driver
4647
/** @var callable|null */
4748
private $interrupt;
4849

50+
private \Closure $interruptCallback;
51+
4952
private bool $running = false;
5053

5154
private \stdClass $internalSuspensionMarker;
@@ -55,6 +58,8 @@ public function __construct()
5558
$this->internalSuspensionMarker = new \stdClass();
5659
$this->createCallbackFiber();
5760
$this->createQueueFiber();
61+
/** @psalm-suppress InvalidArgument */
62+
$this->interruptCallback = \Closure::fromCallable([$this, 'interrupt']);
5863
}
5964

6065
/**
@@ -106,11 +111,6 @@ public function stop(): void
106111
$this->running = false;
107112
}
108113

109-
public function interrupt(callable $callback): void
110-
{
111-
$this->interrupt = $callback;
112-
}
113-
114114
/**
115115
* @return bool True if the event loop is running, false if it is stopped.
116116
*/
@@ -446,6 +446,11 @@ public function unreference(string $callbackId): string
446446
return $callbackId;
447447
}
448448

449+
public function createSuspension(\Fiber $scheduler): Suspension
450+
{
451+
return new Suspension($this, $scheduler, $this->interruptCallback);
452+
}
453+
449454
/**
450455
* Set a callback to be executed when an error occurs.
451456
*
@@ -702,6 +707,11 @@ private function invokeMicrotasks(): void
702707
}
703708
}
704709

710+
private function interrupt(callable $callback): void
711+
{
712+
$this->interrupt = $callback;
713+
}
714+
705715
private function createCallbackFiber(): void
706716
{
707717
$suspensionMarker = $this->internalSuspensionMarker;

src/EventLoop/Suspension.php

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22

33
namespace Revolt\EventLoop;
44

5-
use Revolt\EventLoop;
6-
75
/**
86
* Should be used to run and suspend the event loop instead of directly interacting with fibers.
97
*
108
* **Example**
119
*
1210
* ```php
13-
* $suspension = Scheduler::createSuspension();
11+
* $suspension = EventLoop::createSuspension();
1412
*
1513
* $promise->then(fn ($value) => $suspension->resume($value), fn ($throwable) => $suspension->throw($throwable));
1614
*
@@ -23,29 +21,25 @@ final class Suspension
2321
private \Fiber $scheduler;
2422
private Driver $driver;
2523
private bool $pending = false;
24+
/** @var callable */
25+
private $interrupt;
2626

2727
/**
28-
* Suspension constructor.
29-
*
3028
* @param Driver $driver
3129
* @param \Fiber $scheduler
30+
* @param callable $interrupt
3231
*
3332
* @internal
3433
*/
35-
public function __construct(Driver $driver, \Fiber $scheduler)
34+
public function __construct(Driver $driver, \Fiber $scheduler, callable $interrupt)
3635
{
3736
$this->driver = $driver;
37+
$this->scheduler = $scheduler;
38+
$this->interrupt = $interrupt;
3839
$this->fiber = \Fiber::getCurrent();
3940

40-
if ($this->fiber === $scheduler) {
41-
throw new \Error(\sprintf(
42-
'Cannot call %s() within a scheduler microtask (%s::queue() callback)',
43-
__METHOD__,
44-
EventLoop::class,
45-
));
46-
}
47-
48-
$this->scheduler = $scheduler;
41+
// User callbacks are always executed outside the event loop fiber, so this should always be false.
42+
\assert($this->fiber !== $this->scheduler);
4943
}
5044

5145
public function throw(\Throwable $throwable): void
@@ -60,7 +54,7 @@ public function throw(\Throwable $throwable): void
6054
$this->driver->queue([$this->fiber, 'throw'], $throwable);
6155
} else {
6256
// Suspend event loop fiber to {main}.
63-
$this->driver->interrupt(static fn () => throw $throwable);
57+
($this->interrupt)(static fn () => throw $throwable);
6458
}
6559
}
6660

@@ -76,7 +70,7 @@ public function resume(mixed $value): void
7670
$this->driver->queue([$this->fiber, 'resume'], $value);
7771
} else {
7872
// Suspend event loop fiber to {main}.
79-
$this->driver->interrupt(static fn () => $value);
73+
($this->interrupt)(static fn () => $value);
8074
}
8175
}
8276

0 commit comments

Comments
 (0)