Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement custom FiberFactory interface #72

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/EventLoop/DefaultFiberFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Revolt\EventLoop;

final class DefaultFiberFactory implements FiberFactory
{
/**
* Creates a new fiber instance.
*
* @param callable $callable The callable to invoke when starting the fiber.
*
* @return \Fiber
*/
public function create(callable $callback): \Fiber
{
return new \Fiber($callback);
}
}
5 changes: 3 additions & 2 deletions src/EventLoop/Driver/EvDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace Revolt\EventLoop\Driver;

use Revolt\EventLoop\FiberFactory;
use Revolt\EventLoop\Internal\AbstractDriver;
use Revolt\EventLoop\Internal\DriverCallback;
use Revolt\EventLoop\Internal\SignalCallback;
Expand Down Expand Up @@ -38,9 +39,9 @@
/** @var array<string, \EvSignal> */
private array $signals = [];

public function __construct()
public function __construct(?FiberFactory $fiberFactory = null)
{
parent::__construct();
parent::__construct($fiberFactory);

$this->handle = new \EvLoop();

Expand Down Expand Up @@ -165,16 +166,16 @@
$this->handle->nowUpdate();
$now = $this->now();

foreach ($callbacks as $callback) {

Check warning on line 169 in src/EventLoop/Driver/EvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.1

MixedAssignment

src/EventLoop/Driver/EvDriver.php:169:32: MixedAssignment: Unable to determine the type that $callback is being assigned to (see https://psalm.dev/032)
if (!isset($this->events[$id = $callback->id])) {

Check warning on line 170 in src/EventLoop/Driver/EvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.1

MixedArrayOffset

src/EventLoop/Driver/EvDriver.php:170:24: MixedArrayOffset: Cannot access value on variable $this->events using mixed offset (see https://psalm.dev/031)

Check warning on line 170 in src/EventLoop/Driver/EvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.1

MixedAssignment

src/EventLoop/Driver/EvDriver.php:170:38: MixedAssignment: Unable to determine the type that $id is being assigned to (see https://psalm.dev/032)

Check warning on line 170 in src/EventLoop/Driver/EvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.1

MixedPropertyFetch

src/EventLoop/Driver/EvDriver.php:170:44: MixedPropertyFetch: Cannot fetch property on mixed var $callback (see https://psalm.dev/034)
if ($callback instanceof StreamReadableCallback) {
\assert(\is_resource($callback->stream));

$this->events[$id] = $this->handle->io($callback->stream, \Ev::READ, $this->ioCallback, $callback);

Check warning on line 174 in src/EventLoop/Driver/EvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.1

MixedArrayOffset

src/EventLoop/Driver/EvDriver.php:174:21: MixedArrayOffset: Cannot access value on variable $this->events[$id] using mixed offset (see https://psalm.dev/031)

Check warning on line 174 in src/EventLoop/Driver/EvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.1

MixedPropertyTypeCoercion

src/EventLoop/Driver/EvDriver.php:174:21: MixedPropertyTypeCoercion: $this->events expects 'array<string, EvWatcher>', parent type `non-empty-array<array-key, EvWatcher>` provided (see https://psalm.dev/196)
} elseif ($callback instanceof StreamWritableCallback) {
\assert(\is_resource($callback->stream));

$this->events[$id] = $this->handle->io(

Check warning on line 178 in src/EventLoop/Driver/EvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.1

MixedArrayOffset

src/EventLoop/Driver/EvDriver.php:178:21: MixedArrayOffset: Cannot access value on variable $this->events[$id] using mixed offset (see https://psalm.dev/031)
$callback->stream,
\Ev::WRITE,
$this->ioCallback,
Expand Down
5 changes: 3 additions & 2 deletions src/EventLoop/Driver/EventDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace Revolt\EventLoop\Driver;

use Revolt\EventLoop\FiberFactory;
use Revolt\EventLoop\Internal\AbstractDriver;
use Revolt\EventLoop\Internal\DriverCallback;
use Revolt\EventLoop\Internal\SignalCallback;
Expand Down Expand Up @@ -34,9 +35,9 @@ public static function isSupported(): bool
/** @var array<string, \Event> */
private array $signals = [];

public function __construct()
public function __construct(?FiberFactory $fiberFactory = null)
{
parent::__construct();
parent::__construct($fiberFactory);

/** @psalm-suppress TooFewArguments https://github.com/JetBrains/phpstorm-stubs/pull/763 */
$this->handle = new \EventBase();
Expand Down
5 changes: 3 additions & 2 deletions src/EventLoop/Driver/StreamSelectDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace Revolt\EventLoop\Driver;

use Revolt\EventLoop\FiberFactory;
use Revolt\EventLoop\Internal\AbstractDriver;
use Revolt\EventLoop\Internal\DriverCallback;
use Revolt\EventLoop\Internal\SignalCallback;
Expand Down Expand Up @@ -43,9 +44,9 @@ final class StreamSelectDriver extends AbstractDriver

private bool $streamSelectIgnoreResult = false;

public function __construct()
public function __construct(?FiberFactory $fiberFactory = null)
{
parent::__construct();
parent::__construct($fiberFactory);

$this->signalQueue = new \SplQueue();
$this->timerQueue = new TimerQueue();
Expand Down
5 changes: 3 additions & 2 deletions src/EventLoop/Driver/UvDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Revolt\EventLoop\Driver;

use Revolt\EventLoop\FiberFactory;
use Revolt\EventLoop\Internal\AbstractDriver;
use Revolt\EventLoop\Internal\DriverCallback;
use Revolt\EventLoop\Internal\SignalCallback;
Expand Down Expand Up @@ -31,9 +32,9 @@ public static function isSupported(): bool
private readonly \Closure $timerCallback;
private readonly \Closure $signalCallback;

public function __construct()
public function __construct(?FiberFactory $fiberFactory = null)
{
parent::__construct();
parent::__construct($fiberFactory);

$this->handle = \uv_loop_new();

Expand Down
17 changes: 17 additions & 0 deletions src/EventLoop/FiberFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Revolt\EventLoop;

interface FiberFactory
{
/**
* Creates a new fiber instance.
*
* @param callable $callable The callable to invoke when starting the fiber.
*
* @return \Fiber
*/
public function create(callable $callback): \Fiber;
}
11 changes: 5 additions & 6 deletions src/EventLoop/FiberLocal.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
*/
final class FiberLocal
{
/** @var \Fiber|null Dummy fiber for {main} */
private static ?\Fiber $mainFiber = null;
/** @var object|null Dummy object for {main} */
private static ?object $dummyMain = null;
private static ?\WeakMap $localStorage = null;

public static function clear(): void
Expand All @@ -23,7 +23,7 @@ public static function clear(): void
return;
}

$fiber = \Fiber::getCurrent() ?? self::$mainFiber;
$fiber = \Fiber::getCurrent() ?? self::$dummyMain;

if ($fiber === null) {
return;
Expand All @@ -37,9 +37,8 @@ private static function getFiberStorage(): \WeakMap
$fiber = \Fiber::getCurrent();

if ($fiber === null) {
$fiber = self::$mainFiber ??= new \Fiber(static function (): void {
// dummy fiber for main, as we need some object for the WeakMap
});
$fiber = self::$dummyMain ??= new class () {
};
}

$localStorage = self::$localStorage ??= new \WeakMap();
Expand Down
13 changes: 9 additions & 4 deletions src/EventLoop/Internal/AbstractDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
namespace Revolt\EventLoop\Internal;

use Revolt\EventLoop\CallbackType;
use Revolt\EventLoop\DefaultFiberFactory;
use Revolt\EventLoop\Driver;
use Revolt\EventLoop\FiberFactory;
use Revolt\EventLoop\FiberLocal;
use Revolt\EventLoop\InvalidCallbackError;
use Revolt\EventLoop\Suspension;
Expand Down Expand Up @@ -58,13 +60,15 @@ abstract class AbstractDriver implements Driver
/** @var \SplQueue<DriverCallback> */
private readonly \SplQueue $callbackQueue;

private readonly FiberFactory $fiberFactory;

private bool $idle = false;
private bool $stopped = false;

/** @var \WeakMap<object, \WeakReference<DriverSuspension>> */
private \WeakMap $suspensions;

public function __construct()
public function __construct(?FiberFactory $fiberFactory = null)
{
if (\PHP_VERSION_ID < 80117 || \PHP_VERSION_ID >= 80200 && \PHP_VERSION_ID < 80204) {
// PHP GC is broken on early 8.1 and 8.2 versions, see https://github.com/php/php-src/issues/10496
Expand All @@ -73,6 +77,7 @@ public function __construct()
throw new \Error('Your version of PHP is affected by serious garbage collector bugs related to fibers. Please upgrade to a newer version of PHP, i.e. >= 8.1.17 or => 8.2.4');
}
}
$this->fiberFactory = $fiberFactory ?? new DefaultFiberFactory();

$this->suspensions = new \WeakMap();

Expand Down Expand Up @@ -402,7 +407,7 @@ final protected function error(\Closure $closure, \Throwable $exception): void
return;
}

$fiber = new \Fiber($this->errorCallback);
$fiber = $this->fiberFactory->create($this->errorCallback);

/** @noinspection PhpUnhandledExceptionInspection */
$fiber->start($this->errorHandler, $exception);
Expand Down Expand Up @@ -531,7 +536,7 @@ private function invokeInterrupt(): void

private function createLoopFiber(): void
{
$this->fiber = new \Fiber(function (): void {
$this->fiber = $this->fiberFactory->create(function (): void {
$this->stopped = false;

// Invoke microtasks if we have some
Expand All @@ -558,7 +563,7 @@ private function createLoopFiber(): void

private function createCallbackFiber(): void
{
$this->callbackFiber = new \Fiber(function (): void {
$this->callbackFiber = $this->fiberFactory->create(function (): void {
do {
$this->invokeMicrotasks();

Expand Down
63 changes: 63 additions & 0 deletions src/EventLoop/TracingFiberFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace Revolt\EventLoop;

use Countable;
use IteratorAggregate;
use Traversable;

/**
* Fiber factory which collects all created fibers in a weak map.
*
* @implements IteratorAggregate<\Fiber, null>
*/
final class TracingFiberFactory implements FiberFactory, Countable, IteratorAggregate
{
/**
* @var \WeakMap<\Fiber, null>
*/
private readonly \WeakMap $map;

public function __construct(
private readonly FiberFactory $fiberFactory = new DefaultFiberFactory()
) {
/** @var \WeakMap<\Fiber, null> */
$this->map = new \WeakMap();
}

/**
* Creates a new fiber instance.
*
* @param callable $callable The callable to invoke when starting the fiber.
*
* @return \Fiber
*/
public function create(callable $callback): \Fiber
{
$f = $this->fiberFactory->create($callback);
$this->map[$f] = null;

Check failure on line 40 in src/EventLoop/TracingFiberFactory.php

View workflow job for this annotation

GitHub Actions / PHP 8.1

InaccessibleProperty

src/EventLoop/TracingFiberFactory.php:40:9: InaccessibleProperty: Revolt\EventLoop\TracingFiberFactory::$map is marked readonly (see https://psalm.dev/054)
return $f;
}

/**
* Returns the number of running fibers.
*
* @return int
*/
public function count(): int
{
return $this->map->count();
}

/**
* Iterate over all fibers currently in scope.
*
* @return Traversable<\Fiber, null>
*/
public function getIterator(): Traversable
{
return $this->map->getIterator();
}
}
36 changes: 36 additions & 0 deletions test/Driver/TracingFiberFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Revolt\EventLoop\Driver;

use Revolt\EventLoop\Driver;
use Revolt\EventLoop\TracingFiberFactory;

class TracingFiberFactoryTest extends StreamSelectDriverTest
{
private static TracingFiberFactory $factory;
public function getFactory(): callable
{
self::$factory ??= new TracingFiberFactory();
return static function (): StreamSelectDriver {
return new StreamSelectDriver(self::$factory);
};
}

public function testNumberOfFibers(): void
{
self::assertEquals(2, self::$factory->count());
$this->start(static function (Driver $loop): void {
$loop->queue(static function () use ($loop) {
$suspension = $loop->getSuspension();
$loop->delay(1, $suspension->resume(...));
$suspension->suspend();
});
$loop->delay(0.5, function () {
self::assertEquals(3, self::$factory->count());
});
});
self::assertEquals(2, self::$factory->count());
}
}
Loading