Skip to content

Commit eb95f5b

Browse files
authored
Add fiber adapter based on suspensions (#2)
1 parent 705deb8 commit eb95f5b

13 files changed

+116
-5
lines changed

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
},
1414
"require": {
1515
"php": ">=8.1",
16+
"react/async": "^4",
1617
"react/event-loop": "^0.5 || ^1",
1718
"revolt/event-loop": "^0.2.4"
1819
},
@@ -33,11 +34,13 @@
3334
"autoload-dev": {
3435
"psr-4": {
3536
"Revolt\\EventLoop\\React\\": "test",
37+
"React\\Tests\\Async\\": "vendor/react/async/tests",
3638
"React\\Tests\\EventLoop\\": "vendor/react/event-loop/tests"
3739
}
3840
},
3941
"config": {
4042
"preferred-install": {
43+
"react/async": "source",
4144
"react/event-loop": "source"
4245
}
4346
},

etc/Factory.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
namespace React\EventLoop;
44

5-
use Revolt\EventLoop\Adapter\React\Internal\EventLoopAdapter;
6-
75
(static function () {
86
$constName = 'REVOLT_ADAPTER_REACT_DISABLE_FACTORY_OVERRIDE';
97

@@ -14,16 +12,14 @@
1412
if (!$const && !$env) {
1513
/**
1614
* Class used to overwrite React's loop factory with an implementation throwing an error.
17-
*
18-
* @noinspection PhpUndefinedClassInspection
1915
*/
2016
final class Factory
2117
{
2218
public static function create(): LoopInterface
2319
{
2420
throw new \Error(
2521
__METHOD__ . '() has been overridden by revolt/event-loop-adapter-react to prevent you from accidentally creating two event loop instances. ' .
26-
'Use ' . EventLoopAdapter::class . '::get() instead of React\EventLoop\Factory::create() to ensure everything is running on the same event loop. ' .
22+
'Use ' . Loop::class . '::get() instead of React\EventLoop\Factory::create() to ensure everything is running on the same event loop. ' .
2723
'You may set a constant or environment variable named REVOLT_ADAPTER_REACT_DISABLE_FACTORY_OVERRIDE to disable this protection or open an issue at https://github.com/revoltphp/event-loop-adapter-react if you\'re unsure on the right way forward.'
2824
);
2925
}

src/Internal/FiberAdapter.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace Revolt\EventLoop\React\Internal;
4+
5+
use React\Async\FiberInterface;
6+
use Revolt\EventLoop;
7+
8+
/** @internal */
9+
final class FiberAdapter implements FiberInterface
10+
{
11+
private ?EventLoop\Suspension $suspension = null;
12+
13+
public function resume(mixed $value): void
14+
{
15+
if ($this->suspension === null) {
16+
throw new \Error('Must call suspend() before calling resume()');
17+
}
18+
19+
$this->suspension->resume($value);
20+
21+
// Note: resume() above is async in revolt, but sync in react,
22+
// so let's suspend here until the queued resumption above is executed.
23+
$suspension = EventLoop::getSuspension();
24+
EventLoop::queue($suspension->resume(...));
25+
$suspension->suspend();
26+
}
27+
28+
public function throw(\Throwable $throwable): void
29+
{
30+
if ($this->suspension === null) {
31+
throw new \Error('Must call suspend() before calling throw()');
32+
}
33+
34+
$this->suspension->throw($throwable);
35+
36+
// Note: throw() above is async in revolt, but sync in react,
37+
// so let's suspend here until the queued throwing above is executed.
38+
$suspension = EventLoop::getSuspension();
39+
EventLoop::queue($suspension->resume(...));
40+
$suspension->suspend();
41+
}
42+
43+
public function suspend(): mixed
44+
{
45+
if ($this->suspension !== null) {
46+
throw new \Error('Must call resume() or throw() before calling suspend() again');
47+
}
48+
49+
$this->suspension = EventLoop::getSuspension();
50+
51+
return $this->suspension->suspend();
52+
}
53+
}

src/bootstrap.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
<?php
22

3+
use React\Async\FiberFactory;
4+
use React\Async\FiberInterface;
35
use React\EventLoop\Loop;
46
use Revolt\EventLoop\React\Internal\EventLoopAdapter;
7+
use Revolt\EventLoop\React\Internal\FiberAdapter;
58

69
/**
710
* @psalm-suppress InternalMethod
811
*/
912
Loop::set(EventLoopAdapter::get());
13+
14+
/**
15+
* @psalm-suppress InternalMethod
16+
*/
17+
FiberFactory::factory(static fn (): FiberInterface => new FiberAdapter());

test/AwaitTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Revolt\EventLoop\React;
4+
5+
use React;
6+
7+
class AwaitTest extends React\Tests\Async\AwaitTest
8+
{
9+
public function testExecutionOrder()
10+
{
11+
self::expectOutputString('acbd');
12+
13+
$deferred = new React\Promise\Deferred();
14+
15+
$promises[] = React\Async\async(function () use (&$deferred) {
16+
print 'a';
17+
React\Async\await($deferred->promise());
18+
print 'b';
19+
})();
20+
21+
$promises[] = React\Async\async(function () use ($deferred) {
22+
print 'c';
23+
$deferred->resolve();
24+
print 'd';
25+
})();
26+
27+
React\Async\await(React\Promise\all($promises));
28+
}
29+
}

test/EvTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public function createLoop(): LoopInterface
1414
$this->markTestSkipped("ext-ev required");
1515
}
1616

17+
self::clearGlobalLoop();
1718
EventLoop::setDriver(new EventLoop\Driver\EvDriver());
1819
return EventLoopAdapter::get();
1920
}

test/EvTimerTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public function createLoop(): LoopInterface
1414
$this->markTestSkipped("ext-ev required");
1515
}
1616

17+
self::clearGlobalLoop();
1718
EventLoop::setDriver(new EventLoop\Driver\EvDriver());
1819
return EventLoopAdapter::get();
1920
}

test/EventTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public function createLoop(): LoopInterface
1313
$this->markTestSkipped("ext-event required");
1414
}
1515

16+
self::clearGlobalLoop();
1617
EventLoop::setDriver(new EventLoop\Driver\EventDriver());
1718
return Internal\EventLoopAdapter::get();
1819
}

test/EventTimerTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public function createLoop(): LoopInterface
1414
$this->markTestSkipped("ext-event required");
1515
}
1616

17+
self::clearGlobalLoop();
1718
EventLoop::setDriver(new EventLoop\Driver\EventDriver());
1819
return EventLoopAdapter::get();
1920
}

test/Test.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,16 @@
1313

1414
class Test extends AbstractLoopTest
1515
{
16+
/** @noinspection PhpUndefinedFieldInspection */
17+
protected static function clearGlobalLoop(): void
18+
{
19+
(static fn () => self::$driver = new EventLoop\Driver\StreamSelectDriver())
20+
->bindTo(null, EventLoop::class)();
21+
}
22+
1623
public function createLoop(): LoopInterface
1724
{
25+
self::clearGlobalLoop();
1826
EventLoop::setDriver(new StreamSelectDriver());
1927
return EventLoopAdapter::get();
2028
}

0 commit comments

Comments
 (0)