Skip to content
Merged
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
1 change: 1 addition & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public function getRules(): array
'uv_now',
'uv_run',
'uv_poll_init_socket',
'uv_stop',
'uv_timer_init',
'uv_timer_start',
'uv_timer_stop',
Expand Down
36 changes: 35 additions & 1 deletion src/EventLoop/Driver/UvDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,39 @@

final class UvDriver extends AbstractDriver
{
private static ?bool $libuvStopWorkaround = null;

public static function isSupported(): bool
{
return \extension_loaded("uv");
}

private static function getLibuvVersion(): ?string
{
\ob_start();
\phpinfo(\INFO_MODULES);
$info = (string) \ob_get_clean();

if (\preg_match('/libuv Version[^0-9]+([0-9.]+)/', $info, $matches)) {
return $matches[1];
}

return null;
}

private static function needsStopWorkaround(): bool
{
if (self::$libuvStopWorkaround === null) {
// libuv < 1.45 runs the due timers before polling in UV_RUN_ONCE, then polls with the timeout of the
// next timer: without one, it blocks until an I/O event while another handle keeps the loop alive.
// If we can't determine the version, assume it is a newer version including the fix.
$libuvVersion = self::getLibuvVersion();
self::$libuvStopWorkaround = $libuvVersion !== null && \version_compare($libuvVersion, '1.45', '<');
}

return self::$libuvStopWorkaround;
}

/** @var resource|\UVLoop A uv_loop resource created with uv_loop_new() */
private $handle;
/** @var array<string, resource> */
Expand All @@ -35,6 +63,8 @@
{
parent::__construct();

$stopWorkaround = self::needsStopWorkaround();

$this->handle = \uv_loop_new();

/** @psalm-suppress UnusedClosureParam */
Expand All @@ -50,7 +80,7 @@

$flags |= $callback->invokable ? $this->getStreamCallbackFlags($callback) : 0;
}
\uv_poll_start($event, $flags, $this->ioCallback);

Check warning on line 83 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.5

MixedArgument

src/EventLoop/Driver/UvDriver.php:83:32: MixedArgument: Argument 1 of uv_poll_start cannot be mixed, expecting resource (see https://psalm.dev/030)

Check warning on line 83 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

MixedArgument

src/EventLoop/Driver/UvDriver.php:83:32: MixedArgument: Argument 1 of uv_poll_start cannot be mixed, expecting resource (see https://psalm.dev/030)

Check warning on line 83 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.4

MixedArgument

src/EventLoop/Driver/UvDriver.php:83:32: MixedArgument: Argument 1 of uv_poll_start cannot be mixed, expecting resource (see https://psalm.dev/030)

Check warning on line 83 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.2

MixedArgument

src/EventLoop/Driver/UvDriver.php:83:32: MixedArgument: Argument 1 of uv_poll_start cannot be mixed, expecting resource (see https://psalm.dev/030)
}

foreach ($callbacks as $callback) {
Expand All @@ -58,7 +88,7 @@

// $events is ORed with 4 to trigger callback if no events are indicated (0) or on UV_DISCONNECT (4).
// http://docs.libuv.org/en/v1.x/poll.html
if (!($this->getStreamCallbackFlags($callback) & $events || ($events | 4) === 4)) {

Check warning on line 91 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.5

MixedOperand

src/EventLoop/Driver/UvDriver.php:91:78: MixedOperand: Left operand cannot be mixed (see https://psalm.dev/059)

Check warning on line 91 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.5

MixedOperand

src/EventLoop/Driver/UvDriver.php:91:66: MixedOperand: Right operand cannot be mixed (see https://psalm.dev/059)

Check warning on line 91 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

MixedOperand

src/EventLoop/Driver/UvDriver.php:91:78: MixedOperand: Left operand cannot be mixed (see https://psalm.dev/059)

Check warning on line 91 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

MixedOperand

src/EventLoop/Driver/UvDriver.php:91:66: MixedOperand: Right operand cannot be mixed (see https://psalm.dev/059)

Check warning on line 91 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.4

MixedOperand

src/EventLoop/Driver/UvDriver.php:91:78: MixedOperand: Left operand cannot be mixed (see https://psalm.dev/059)

Check warning on line 91 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.4

MixedOperand

src/EventLoop/Driver/UvDriver.php:91:66: MixedOperand: Right operand cannot be mixed (see https://psalm.dev/059)

Check warning on line 91 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.2

MixedOperand

src/EventLoop/Driver/UvDriver.php:91:78: MixedOperand: Left operand cannot be mixed (see https://psalm.dev/059)

Check warning on line 91 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.2

MixedOperand

src/EventLoop/Driver/UvDriver.php:91:66: MixedOperand: Right operand cannot be mixed (see https://psalm.dev/059)
continue;
}

Expand All @@ -66,12 +96,16 @@
}
};

$this->timerCallback = function ($event): void {
$this->timerCallback = function ($event) use ($stopWorkaround): void {
$callback = $this->uvCallbacks[(int) $event][0];

\assert($callback instanceof TimerCallback);

$this->enqueueCallback($callback);

if ($stopWorkaround) {
\uv_stop($this->handle);
}
};

$this->signalCallback = function ($event): void {
Expand Down Expand Up @@ -159,9 +193,9 @@
$streamId = (int) $callback->stream;

if (isset($this->streams[$streamId])) {
$event = $this->streams[$streamId];

Check warning on line 196 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.5

MixedAssignment

src/EventLoop/Driver/UvDriver.php:196:21: MixedAssignment: Unable to determine the type that $event is being assigned to (see https://psalm.dev/032)

Check warning on line 196 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

MixedAssignment

src/EventLoop/Driver/UvDriver.php:196:21: MixedAssignment: Unable to determine the type that $event is being assigned to (see https://psalm.dev/032)

Check warning on line 196 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.4

MixedAssignment

src/EventLoop/Driver/UvDriver.php:196:21: MixedAssignment: Unable to determine the type that $event is being assigned to (see https://psalm.dev/032)

Check warning on line 196 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.2

MixedAssignment

src/EventLoop/Driver/UvDriver.php:196:21: MixedAssignment: Unable to determine the type that $event is being assigned to (see https://psalm.dev/032)
} elseif (isset($this->events[$id])) {
$event = $this->streams[$streamId] = $this->events[$id];

Check warning on line 198 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.5

MixedPropertyTypeCoercion

src/EventLoop/Driver/UvDriver.php:198:30: MixedPropertyTypeCoercion: $this->streams expects 'array<int, resource>', parent type `non-empty-array<int, mixed|resource>` provided (see https://psalm.dev/196)

Check warning on line 198 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.5

MixedAssignment

src/EventLoop/Driver/UvDriver.php:198:21: MixedAssignment: Unable to determine the type that $event is being assigned to (see https://psalm.dev/032)

Check warning on line 198 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

MixedPropertyTypeCoercion

src/EventLoop/Driver/UvDriver.php:198:30: MixedPropertyTypeCoercion: $this->streams expects 'array<int, resource>', parent type `non-empty-array<int, mixed|resource>` provided (see https://psalm.dev/196)

Check warning on line 198 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

MixedAssignment

src/EventLoop/Driver/UvDriver.php:198:21: MixedAssignment: Unable to determine the type that $event is being assigned to (see https://psalm.dev/032)

Check warning on line 198 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.4

MixedPropertyTypeCoercion

src/EventLoop/Driver/UvDriver.php:198:30: MixedPropertyTypeCoercion: $this->streams expects 'array<int, resource>', parent type `non-empty-array<int, mixed|resource>` provided (see https://psalm.dev/196)

Check warning on line 198 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.4

MixedAssignment

src/EventLoop/Driver/UvDriver.php:198:21: MixedAssignment: Unable to determine the type that $event is being assigned to (see https://psalm.dev/032)

Check warning on line 198 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.2

MixedPropertyTypeCoercion

src/EventLoop/Driver/UvDriver.php:198:30: MixedPropertyTypeCoercion: $this->streams expects 'array<int, resource>', parent type `non-empty-array<int, mixed|resource>` provided (see https://psalm.dev/196)

Check warning on line 198 in src/EventLoop/Driver/UvDriver.php

View workflow job for this annotation

GitHub Actions / PHP 8.2

MixedAssignment

src/EventLoop/Driver/UvDriver.php:198:21: MixedAssignment: Unable to determine the type that $event is being assigned to (see https://psalm.dev/032)
} else {
/** @psalm-suppress TooManyArguments */
$event = $this->streams[$streamId] = \uv_poll_init_socket($this->handle, $callback->stream);
Expand Down
41 changes: 41 additions & 0 deletions test/Driver/UvDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,45 @@ public function testSupported(): void
{
self::assertTrue(UvDriver::isSupported());
}

/**
* Short timers with another referenced watcher: on libuv < 1.45, a timer due when the driver entered its
* blocking run left the loop blocked forever. Run in a child process, killed if it hangs.
*/
public function testShortTimersWithASignalWatcher(): void
{
$code = <<<'PHP'
require $argv[1];
$loop = new Revolt\EventLoop\Driver\UvDriver();
Revolt\EventLoop::setDriver($loop);
Revolt\EventLoop::onSignal(\defined('SIGUSR2') ? SIGUSR2 : 31, static fn () => null);
for ($i = 0; $i < 3000; $i++) {
$suspension = Revolt\EventLoop::getSuspension();
Revolt\EventLoop::delay(0.001, $suspension->resume(...));
$suspension->suspend();
}
echo 'done';
PHP;

$process = \proc_open(
[\PHP_BINARY, '-r', $code, '--', __DIR__ . '/../../vendor/autoload.php'],
[1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
$pipes,
);
self::assertIsResource($process);

$deadline = \hrtime(true) + 30_000_000_000;
while (\proc_get_status($process)['running'] && \hrtime(true) < $deadline) {
\usleep(50_000);
}
$running = \proc_get_status($process)['running'];
if ($running) {
\proc_terminate($process, 9);
}
$output = \stream_get_contents($pipes[1]) . \stream_get_contents($pipes[2]);
\proc_close($process);

self::assertFalse($running, "3000 delays of 1 ms didn't complete in 30 seconds: $output");
self::assertSame('done', $output);
}
}
Loading