8000 Turn Suspension into an interface · revoltphp/event-loop@531a57f · GitHub
[go: up one dir, main page]

Skip to content

Commit 531a57f

Browse files
committed
Turn Suspension into an interface
This is equivalent to the interface change in reactphp/async#15 to allow different implementations. This also allows decorating `Suspension` to implement listeners like proposed in #2.
1 parent dc9fbe7 commit 531a57f

File tree

3 files changed

+115
-93
lines changed

3 files changed

+115
-93
lines changed

src/EventLoop/Internal/AbstractDriver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ public function unreference(string $callbackId): string
458458

459459
public function createSuspension(\Fiber $scheduler): Suspension
460460
{
461-
return new Suspension($this, $scheduler, $this->interruptCallback);
461+
return new DriverSuspension($this, $scheduler, $this->interruptCallback);
462462
}
463463

464464
/**
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
namespace Revolt\EventLoop\Internal;
4+
5+
use Revolt\EventLoop\Driver;
6+
use Revolt\EventLoop\Suspension;
7+
8+
/**
9+
* @internal
10+
*/
11+
final class DriverSuspension implements Suspension
12+
{
13+
private ?\Fiber $fiber;
14+
private \Fiber $scheduler;
15+
private Driver $driver;
16+
private bool $pending = false;
17+
private ?\FiberError $error = null;
18+
/** @var callable */
19+
private $interrupt;
20+
21+
/**
22+
* @param Driver $driver
23+
* @param \Fiber $scheduler
24+
* @param callable $interrupt
25+
*
26+
* @internal
27+
*/
28+
public function __construct(Driver $driver, \Fiber $scheduler, callable $interrupt)
29+
{
30+
$this->driver = $driver;
31+
$this->scheduler = $scheduler;
32+
$this->interrupt = $interrupt;
33+
$this->fiber = \Fiber::getCurrent();
34+
35+
// User callbacks are always executed outside the event loop fiber, so this should always be false.
36+
\assert($this->fiber !== $this->scheduler);
37+
}
38+
39+
public function throw(\Throwable $throwable): void
40+
{
41+
if (!$this->pending) {
42+
throw $this->error ?? new \Error('Must call suspend() before calling throw()');
43+
}
44+
45+
$this->pending = false;
46+
47+
if ($this->fiber) {
48+
$this->driver->queue([$this->fiber, 'throw'], $throwable);
49+
} else {
50+
// Suspend event loop fiber to {main}.
51+
($this->interrupt)(static fn () => throw $throwable);
52+
}
53+
}
54+
55+
public function resume(mixed $value = null): void
56+
{
57+
if (!$this->pending) {
58+
throw $this->error ?? new \Error('Must call suspend() before calling resume()');
59+
}
60+
61+
$this->pending = false;
62+
63+
if ($this->fiber) {
64+
$this->driver->queue([$this->fiber, 'resume'], $value);
65+
} else {
66+
// Suspend event loop fiber to {main}.
67+
($this->interrupt)(static fn () => $value);
68+
}
69+
}
70+
71+
public function suspend(): mixed
72+
{
73+
if ($this->pending) {
74+
throw new \Error('Must call resume() or throw() before calling suspend() again');
75+
}
76+
77+
if ($this->fiber !== \Fiber::getCurrent()) {
78+
throw new \Error('Must not call suspend() from another fiber');
79+
}
80+
81+
$this->pending = true;
82+
83+
// Awaiting from within a fiber.
84+
if ($this->fiber) {
85+
try {
86+
return \Fiber::suspend();
87+
} catch (\FiberError $exception) {
88+
$this->pending = false;
89+
$this->error = $exception;
90+
91+
throw $exception;
92+
}
93+
}
94+
95+
// Awaiting from {main}.
96+
$lambda = $this->scheduler->isStarted() ? $this->scheduler->resume() : $this->scheduler->start();
97+
98+
/** @psalm-suppress RedundantCondition $this->pending should be changed when resumed. */
99+
if ($this->pending) {
100+
// Should only be true if the event loop exited without resolving the promise.
101+
throw new \Error('Scheduler suspended or exited unexpectedly');
102+
}
103+
104+
return $lambda();
105+
}
106+
}

src/EventLoop/Suspension.php

Lines changed: 8 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -10,103 +10,19 @@
1010
* ```php
1111
* $suspension = EventLoop::createSuspension();
1212
*
13-
* $promise->then(fn ($value) => $suspension->resume($value), fn ($throwable) => $suspension->throw($throwable));
13+
* $promise->then(
14+
* fn (mixed $value) => $suspension->resume($value),
15+
* fn (Throwable $error) => $suspension->throw($error)
16+
* );
1417
*
1518
* $suspension->suspend();
1619
* ```
1720
*/
18-
final class Suspension
21+
interface Suspension
1922
{
20-
private ?\Fiber $fiber;
21-
private \Fiber $scheduler;
22-
private Driver $driver;
23-
private bool $pending = false;
24-
private ?\FiberError $error = null;
25-
/** @var callable */
26-
private $interrupt;
23+
public function resume(mixed $value = null): void;
2724

28-
/**
29-
* @param Driver $driver
30-
* @param \Fiber $scheduler
31-
* @param callable $interrupt
32-
*
33-
* @internal
34-
*/
35-
public function __construct(Driver $driver, \Fiber $scheduler, callable $interrupt)
36-
{
37-
$this->driver = $driver;
38-
$this->scheduler = $scheduler;
39-
$this->interrupt = $interrupt;
40-
$this->fiber = \Fiber::getCurrent();
25+
public function suspend(): mixed;
4126

42-
// User callbacks are always executed outside the event loop fiber, so this should always be false.
43-
\assert($this->fiber !== $this->scheduler);
44-
}
45-
46-
public function throw(\Throwable $throwable): void
47-
{
48-
if (!$this->pending) {
49-
throw $this->error ?? new \Error('Must call suspend() before calling throw()');
50-
}
51-
52-
$this->pending = false;
53-
54-
if ($this->fiber) {
55-
$this->driver->queue([$this->fiber, 'throw'], $throwable);
56-
} else {
57-
// Suspend event loop fiber to {main}.
58-
($this->interrupt)(static fn () => throw $throwable);
59-
}
60-
}
61-
62-
public function resume(mixed $value = null): void
63-
{
64-
if (!$this->pending) {
65-
throw $this->error ?? new \Error('Must call suspend() before calling resume()');
66-
}
67-
68-
$this->pending = false;
69-
70-
if ($this->fiber) {
71-
$this->driver->queue([$this->fiber, 'resume'], $value);
72-
} else {
73-
// Suspend event loop fiber to {main}.
74-
($this->interrupt)(static fn () => $value);
75-
}
76-
}
77-
78-
public function suspend(): mixed
79-
{
80-
if ($this->pending) {
81-
throw new \Error('Must call resume() or throw() before calling suspend() again');
82-
}
83-
84-
if ($this->fiber !== \Fiber::getCurrent()) {
85-
throw new \Error('Must not call suspend() from another fiber');
86-
}
87-
88-
$this->pending = true;
89-
90-
// Awaiting from within a fiber.
91-
if ($this->fiber) {
92-
try {
93-
return \Fiber::suspend();
94-
} catch (\FiberError $exception) {
95-
$this->pending = false;
96-
$this->error = $exception;
97-
throw $exception;
98-
}
99-
}
100-
101-
// Awaiting from {main}.
102-
$lambda = $this->scheduler->isStarted() ? $this->scheduler->resume() : $this->scheduler->start();
103-
104-
/** @psalm-suppress RedundantCondition $this->pending should be changed when resumed. */
105-
if ($this->pending) {
106-
// Should only be true if the event loop exited without resolving the promise.
107-
throw new \Error('Scheduler suspended or exited unexpectedly');
108-
}
109-
110-
return $lambda();
111-
}
27+
public function throw(\Throwable $throwable): void;
11228
}

0 commit comments

Comments
 (0)
0