2016-06-09 19:57:46 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Amp\Loop;
|
|
|
|
|
2017-03-10 23:03:41 +01:00
|
|
|
use Amp\Coroutine;
|
|
|
|
use Amp\Promise;
|
2017-03-11 07:13:03 +01:00
|
|
|
use React\Promise\PromiseInterface as ReactPromise;
|
2017-03-15 17:12:49 +01:00
|
|
|
use function Amp\Promise\rethrow;
|
2016-06-09 19:57:46 +02:00
|
|
|
|
2020-10-09 19:37:37 +02:00
|
|
|
final class UvDriver extends Driver
|
2018-06-18 20:00:01 +02:00
|
|
|
{
|
2020-09-25 05:17:13 +02:00
|
|
|
/** @var resource|\UVLoop A uv_loop resource created with uv_loop_new() */
|
2016-06-09 19:57:46 +02:00
|
|
|
private $handle;
|
|
|
|
|
2017-02-17 05:36:32 +01:00
|
|
|
/** @var resource[] */
|
2020-09-25 05:17:13 +02:00
|
|
|
private array $events = [];
|
2016-06-09 19:57:46 +02:00
|
|
|
|
2020-03-28 12:23:46 +01:00
|
|
|
/** @var Watcher[][] */
|
2020-09-25 05:17:13 +02:00
|
|
|
private array $watchers = [];
|
2016-06-09 19:57:46 +02:00
|
|
|
|
2017-04-25 02:19:22 +02:00
|
|
|
/** @var resource[] */
|
2020-09-25 05:17:13 +02:00
|
|
|
private array $streams = [];
|
2017-04-25 02:19:22 +02:00
|
|
|
|
2020-09-25 05:17:13 +02:00
|
|
|
private \Closure $ioCallback;
|
2016-06-09 19:57:46 +02:00
|
|
|
|
2020-09-25 05:17:13 +02:00
|
|
|
private \Closure $timerCallback;
|
2017-01-16 17:39:24 +01:00
|
|
|
|
2020-09-25 05:17:13 +02:00
|
|
|
private \Closure $signalCallback;
|
2016-06-09 19:57:46 +02:00
|
|
|
|
2018-06-18 20:00:01 +02:00
|
|
|
public function __construct()
|
|
|
|
{
|
2016-06-09 19:57:46 +02:00
|
|
|
$this->handle = \uv_loop_new();
|
|
|
|
|
2020-03-28 22:20:44 +01:00
|
|
|
/**
|
|
|
|
* @param $event
|
|
|
|
* @param $status
|
|
|
|
* @param $events
|
|
|
|
* @param $resource
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2020-09-25 05:17:13 +02:00
|
|
|
$this->ioCallback = function ($event, $status, $events, $resource): void {
|
2017-04-06 18:18:33 +02:00
|
|
|
$watchers = $this->watchers[(int) $event];
|
|
|
|
|
2016-06-09 19:57:46 +02:00
|
|
|
switch ($status) {
|
|
|
|
case 0: // OK
|
|
|
|
break;
|
|
|
|
|
2017-05-24 07:09:04 +02:00
|
|
|
default: // Invoke the callback on errors, as this matches behavior with other loop back-ends.
|
|
|
|
// Re-enable watcher as libuv disables the watcher on non-zero status.
|
2017-05-13 17:11:00 +02:00
|
|
|
$flags = 0;
|
2020-03-28 12:23:46 +01:00
|
|
|
foreach ($watchers as $watcher) {
|
2017-05-13 17:11:00 +02:00
|
|
|
$flags |= $watcher->enabled ? $watcher->type : 0;
|
|
|
|
}
|
2017-05-24 07:09:04 +02:00
|
|
|
\uv_poll_start($event, $flags, $this->ioCallback);
|
|
|
|
break;
|
2016-06-09 19:57:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($watchers as $watcher) {
|
2018-04-08 20:09:38 +02:00
|
|
|
// $events is OR'ed with 4 to trigger watcher if no events are indicated (0) or on UV_DISCONNECT (4).
|
|
|
|
// http://docs.libuv.org/en/v1.x/poll.html
|
2018-04-06 21:22:39 +02:00
|
|
|
if (!($watcher->enabled && ($watcher->type & $events || ($events | 4) === 4))) {
|
2017-04-21 17:54:53 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-03-14 06:20:05 +01:00
|
|
|
try {
|
|
|
|
$result = ($watcher->callback)($watcher->id, $resource, $watcher->data);
|
|
|
|
|
|
|
|
if ($result === null) {
|
2017-03-25 19:52:17 +01:00
|
|
|
continue;
|
2017-03-14 06:20:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($result instanceof \Generator) {
|
|
|
|
$result = new Coroutine($result);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($result instanceof Promise || $result instanceof ReactPromise) {
|
|
|
|
rethrow($result);
|
|
|
|
}
|
|
|
|
} catch (\Throwable $exception) {
|
|
|
|
$this->error($exception);
|
2017-03-10 23:03:41 +01:00
|
|
|
}
|
2016-06-09 19:57:46 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-03-28 22:20:44 +01:00
|
|
|
/**
|
|
|
|
* @param $event
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2020-09-25 05:17:13 +02:00
|
|
|
$this->timerCallback = function ($event): void {
|
2020-03-28 12:23:46 +01:00
|
|
|
$watcher = $this->watchers[(int) $event][0];
|
2016-06-09 19:57:46 +02:00
|
|
|
|
|
|
|
if ($watcher->type & Watcher::DELAY) {
|
2017-05-15 19:41:43 +02:00
|
|
|
unset($this->events[$watcher->id], $this->watchers[(int) $event]); // Avoid call to uv_is_active().
|
|
|
|
$this->cancel($watcher->id); // Remove reference to watcher in parent.
|
2017-05-23 20:17:34 +02:00
|
|
|
} elseif ($watcher->value === 0) {
|
2017-05-23 19:46:23 +02:00
|
|
|
// Disable and re-enable so it's not executed repeatedly in the same tick
|
|
|
|
// See https://github.com/amphp/amp/issues/131
|
|
|
|
$this->disable($watcher->id);
|
|
|
|
$this->enable($watcher->id);
|
|
|
|
}
|
|
|
|
|
2017-03-14 06:20:05 +01:00
|
|
|
try {
|
|
|
|
$result = ($watcher->callback)($watcher->id, $watcher->data);
|
2017-03-10 23:03:41 +01:00
|
|
|
|
2017-03-14 06:20:05 +01:00
|
|
|
if ($result === null) {
|
|
|
|
return;
|
|
|
|
}
|
2017-03-12 18:03:13 +01:00
|
|
|
|
2017-03-14 06:20:05 +01:00
|
|
|
if ($result instanceof \Generator) {
|
|
|
|
$result = new Coroutine($result);
|
|
|
|
}
|
2017-03-10 23:03:41 +01:00
|
|
|
|
2017-03-14 06:20:05 +01:00
|
|
|
if ($result instanceof Promise || $result instanceof ReactPromise) {
|
|
|
|
rethrow($result);
|
|
|
|
}
|
|
|
|
} catch (\Throwable $exception) {
|
|
|
|
$this->error($exception);
|
2017-03-10 23:03:41 +01:00
|
|
|
}
|
2016-06-09 19:57:46 +02:00
|
|
|
};
|
|
|
|
|
2020-03-28 22:20:44 +01:00
|
|
|
/**
|
|
|
|
* @param $event
|
|
|
|
* @param $signo
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2020-09-25 05:17:13 +02:00
|
|
|
$this->signalCallback = function ($event, $signo): void {
|
2020-03-28 12:23:46 +01:00
|
|
|
$watcher = $this->watchers[(int) $event][0];
|
2016-06-09 19:57:46 +02:00
|
|
|
|
2017-03-14 06:20:05 +01:00
|
|
|
try {
|
|
|
|
$result = ($watcher->callback)($watcher->id, $signo, $watcher->data);
|
2017-03-10 23:03:41 +01:00
|
|
|
|
2017-03-14 06:20:05 +01:00
|
|
|
if ($result === null) {
|
|
|
|
return;
|
|
|
|
}
|
2017-03-12 18:03:13 +01:00
|
|
|
|
2017-03-14 06:20:05 +01:00
|
|
|
if ($result instanceof \Generator) {
|
|
|
|
$result = new Coroutine($result);
|
|
|
|
}
|
2017-03-10 23:03:41 +01:00
|
|
|
|
2017-03-14 06:20:05 +01:00
|
|
|
if ($result instanceof Promise || $result instanceof ReactPromise) {
|
|
|
|
rethrow($result);
|
|
|
|
}
|
|
|
|
} catch (\Throwable $exception) {
|
|
|
|
$this->error($exception);
|
2017-03-10 23:03:41 +01:00
|
|
|
}
|
2016-06-09 19:57:46 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-03-10 21:58:46 +01:00
|
|
|
/**
|
|
|
|
* {@inheritdoc}
|
|
|
|
*/
|
2020-09-24 18:52:22 +02:00
|
|
|
public function cancel(string $watcherId): void
|
2018-06-18 20:00:01 +02:00
|
|
|
{
|
2017-03-10 21:58:46 +01:00
|
|
|
parent::cancel($watcherId);
|
|
|
|
|
|
|
|
if (!isset($this->events[$watcherId])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$event = $this->events[$watcherId];
|
2017-04-25 02:19:22 +02:00
|
|
|
$eventId = (int) $event;
|
2017-03-10 21:58:46 +01:00
|
|
|
|
2020-03-28 12:23:46 +01:00
|
|
|
if (isset($this->watchers[$eventId][0])) { // All except IO watchers.
|
2017-05-11 17:39:31 +02:00
|
|
|
unset($this->watchers[$eventId]);
|
2020-07-13 17:12:25 +02:00
|
|
|
} elseif (isset($this->watchers[$eventId][$watcherId])) {
|
2017-05-16 18:22:40 +02:00
|
|
|
$watcher = $this->watchers[$eventId][$watcherId];
|
2017-05-11 17:39:31 +02:00
|
|
|
unset($this->watchers[$eventId][$watcherId]);
|
|
|
|
|
|
|
|
if (empty($this->watchers[$eventId])) {
|
2017-05-16 18:22:40 +02:00
|
|
|
unset($this->watchers[$eventId], $this->streams[(int) $watcher->value]);
|
2017-05-11 17:39:31 +02:00
|
|
|
}
|
2017-03-10 21:58:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
unset($this->events[$watcherId]);
|
|
|
|
}
|
|
|
|
|
2018-06-18 20:00:01 +02:00
|
|
|
public static function isSupported(): bool
|
|
|
|
{
|
2017-03-10 21:58:46 +01:00
|
|
|
return \extension_loaded("uv");
|
|
|
|
}
|
|
|
|
|
2018-01-06 03:32:57 +01:00
|
|
|
/**
|
|
|
|
* {@inheritdoc}
|
|
|
|
*/
|
|
|
|
public function now(): int
|
|
|
|
{
|
2020-07-14 21:45:35 +02:00
|
|
|
\uv_update_time($this->handle);
|
|
|
|
|
2020-03-28 20:27:42 +01:00
|
|
|
/** @psalm-suppress TooManyArguments */
|
2018-01-06 03:32:57 +01:00
|
|
|
return \uv_now($this->handle);
|
|
|
|
}
|
|
|
|
|
2017-03-10 21:58:46 +01:00
|
|
|
/**
|
|
|
|
* {@inheritdoc}
|
|
|
|
*/
|
2018-06-18 20:00:01 +02:00
|
|
|
public function getHandle()
|
|
|
|
{
|
2017-03-10 21:58:46 +01:00
|
|
|
return $this->handle;
|
|
|
|
}
|
|
|
|
|
2016-06-09 19:57:46 +02:00
|
|
|
/**
|
|
|
|
* {@inheritdoc}
|
2020-03-28 22:20:44 +01:00
|
|
|
*
|
|
|
|
* @return void
|
2016-06-09 19:57:46 +02:00
|
|
|
*/
|
2020-09-24 18:52:22 +02:00
|
|
|
protected function dispatch(bool $blocking): void
|
2018-06-18 20:00:01 +02:00
|
|
|
{
|
2020-03-28 20:27:42 +01:00
|
|
|
/** @psalm-suppress TooManyArguments */
|
2016-12-28 23:16:09 +01:00
|
|
|
\uv_run($this->handle, $blocking ? \UV::RUN_ONCE : \UV::RUN_NOWAIT);
|
2016-06-09 19:57:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* {@inheritdoc}
|
2020-03-28 22:20:44 +01:00
|
|
|
*
|
|
|
|
* @return void
|
2016-06-09 19:57:46 +02:00
|
|
|
*/
|
2020-09-24 18:52:22 +02:00
|
|
|
protected function activate(array $watchers): void
|
2018-06-18 20:00:01 +02:00
|
|
|
{
|
2020-07-14 21:45:35 +02:00
|
|
|
$now = $this->now();
|
|
|
|
|
2016-06-09 19:57:46 +02:00
|
|
|
foreach ($watchers as $watcher) {
|
|
|
|
$id = $watcher->id;
|
|
|
|
|
|
|
|
switch ($watcher->type) {
|
|
|
|
case Watcher::READABLE:
|
|
|
|
case Watcher::WRITABLE:
|
2020-04-19 15:38:22 +02:00
|
|
|
\assert(\is_resource($watcher->value));
|
|
|
|
|
2016-06-09 19:57:46 +02:00
|
|
|
$streamId = (int) $watcher->value;
|
|
|
|
|
2017-04-25 02:19:22 +02:00
|
|
|
if (isset($this->streams[$streamId])) {
|
|
|
|
$event = $this->streams[$streamId];
|
2016-06-09 19:57:46 +02:00
|
|
|
} elseif (isset($this->events[$id])) {
|
2017-04-25 02:19:22 +02:00
|
|
|
$event = $this->streams[$streamId] = $this->events[$id];
|
2016-06-09 19:57:46 +02:00
|
|
|
} else {
|
2020-04-19 15:38:22 +02:00
|
|
|
/** @psalm-suppress UndefinedFunction */
|
2017-04-25 02:19:22 +02:00
|
|
|
$event = $this->streams[$streamId] = \uv_poll_init_socket($this->handle, $watcher->value);
|
2016-06-09 19:57:46 +02:00
|
|
|
}
|
|
|
|
|
2017-04-21 17:54:53 +02:00
|
|
|
$eventId = (int) $event;
|
2016-06-09 19:57:46 +02:00
|
|
|
$this->events[$id] = $event;
|
2017-04-21 17:54:53 +02:00
|
|
|
$this->watchers[$eventId][$id] = $watcher;
|
2016-06-09 19:57:46 +02:00
|
|
|
|
2017-04-21 17:54:53 +02:00
|
|
|
$flags = 0;
|
2020-03-28 12:23:46 +01:00
|
|
|
foreach ($this->watchers[$eventId] as $w) {
|
|
|
|
$flags |= $w->enabled ? $w->type : 0;
|
2016-06-09 19:57:46 +02:00
|
|
|
}
|
2017-04-21 17:54:53 +02:00
|
|
|
\uv_poll_start($event, $flags, $this->ioCallback);
|
2016-06-09 19:57:46 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case Watcher::DELAY:
|
|
|
|
case Watcher::REPEAT:
|
2020-04-19 15:38:22 +02:00
|
|
|
\assert(\is_int($watcher->value));
|
|
|
|
|
2016-06-09 19:57:46 +02:00
|
|
|
if (isset($this->events[$id])) {
|
|
|
|
$event = $this->events[$id];
|
|
|
|
} else {
|
|
|
|
$event = $this->events[$id] = \uv_timer_init($this->handle);
|
|
|
|
}
|
|
|
|
|
2020-03-28 12:23:46 +01:00
|
|
|
$this->watchers[(int) $event] = [$watcher];
|
2016-12-30 00:55:06 +01:00
|
|
|
|
2016-06-09 19:57:46 +02:00
|
|
|
\uv_timer_start(
|
|
|
|
$event,
|
2020-07-14 21:45:35 +02:00
|
|
|
\max(0, $watcher->expiration - $now),
|
2020-03-28 12:23:46 +01:00
|
|
|
($watcher->type & Watcher::REPEAT) ? $watcher->value : 0,
|
2016-06-09 19:57:46 +02:00
|
|
|
$this->timerCallback
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Watcher::SIGNAL:
|
2020-04-19 15:38:22 +02:00
|
|
|
\assert(\is_int($watcher->value));
|
|
|
|
|
2016-06-09 19:57:46 +02:00
|
|
|
if (isset($this->events[$id])) {
|
|
|
|
$event = $this->events[$id];
|
|
|
|
} else {
|
2020-04-19 15:38:22 +02:00
|
|
|
/** @psalm-suppress UndefinedFunction */
|
2016-06-09 19:57:46 +02:00
|
|
|
$event = $this->events[$id] = \uv_signal_init($this->handle);
|
|
|
|
}
|
2017-01-16 17:39:24 +01:00
|
|
|
|
2020-03-28 12:23:46 +01:00
|
|
|
$this->watchers[(int) $event] = [$watcher];
|
2016-06-09 19:57:46 +02:00
|
|
|
|
2020-04-19 15:38:22 +02:00
|
|
|
/** @psalm-suppress UndefinedFunction */
|
2016-06-09 19:57:46 +02:00
|
|
|
\uv_signal_start($event, $this->signalCallback, $watcher->value);
|
|
|
|
break;
|
|
|
|
|
2017-03-10 21:58:46 +01:00
|
|
|
default:
|
2017-03-14 17:44:19 +01:00
|
|
|
// @codeCoverageIgnoreStart
|
|
|
|
throw new \Error("Unknown watcher type");
|
2018-06-18 20:00:01 +02:00
|
|
|
// @codeCoverageIgnoreEnd
|
2016-06-09 19:57:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* {@inheritdoc}
|
2020-03-28 22:20:44 +01:00
|
|
|
*
|
|
|
|
* @return void
|
2016-06-09 19:57:46 +02:00
|
|
|
*/
|
2020-09-24 18:52:22 +02:00
|
|
|
protected function deactivate(Watcher $watcher): void
|
2018-06-18 20:00:01 +02:00
|
|
|
{
|
2016-06-09 19:57:46 +02:00
|
|
|
$id = $watcher->id;
|
|
|
|
|
|
|
|
if (!isset($this->events[$id])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$event = $this->events[$id];
|
2017-05-16 18:17:12 +02:00
|
|
|
|
|
|
|
if (!\uv_is_active($event)) {
|
|
|
|
return;
|
|
|
|
}
|
2016-06-09 19:57:46 +02:00
|
|
|
|
|
|
|
switch ($watcher->type) {
|
|
|
|
case Watcher::READABLE:
|
|
|
|
case Watcher::WRITABLE:
|
2017-05-11 17:39:31 +02:00
|
|
|
$flags = 0;
|
2020-03-28 12:23:46 +01:00
|
|
|
foreach ($this->watchers[(int) $event] as $w) {
|
|
|
|
$flags |= $w->enabled ? $w->type : 0;
|
2017-05-11 17:39:31 +02:00
|
|
|
}
|
2017-04-23 14:39:19 +02:00
|
|
|
|
2017-05-11 17:39:31 +02:00
|
|
|
if ($flags) {
|
2017-04-21 17:54:53 +02:00
|
|
|
\uv_poll_start($event, $flags, $this->ioCallback);
|
2017-05-16 18:17:12 +02:00
|
|
|
} else {
|
2017-05-11 17:39:31 +02:00
|
|
|
\uv_poll_stop($event);
|
2016-06-09 19:57:46 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Watcher::DELAY:
|
|
|
|
case Watcher::REPEAT:
|
2017-05-16 18:17:12 +02:00
|
|
|
\uv_timer_stop($event);
|
2016-06-09 19:57:46 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case Watcher::SIGNAL:
|
2017-05-16 18:17:12 +02:00
|
|
|
\uv_signal_stop($event);
|
2016-06-09 19:57:46 +02:00
|
|
|
break;
|
|
|
|
|
2017-03-10 21:58:46 +01:00
|
|
|
default:
|
2017-03-14 17:44:19 +01:00
|
|
|
// @codeCoverageIgnoreStart
|
|
|
|
throw new \Error("Unknown watcher type");
|
2018-06-18 20:00:01 +02:00
|
|
|
// @codeCoverageIgnoreEnd
|
2016-06-09 19:57:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|