1
0
mirror of https://github.com/danog/amp.git synced 2024-12-02 17:37:50 +01:00
amp/lib/Loop/UvDriver.php

303 lines
9.1 KiB
PHP
Raw Normal View History

2016-06-09 19:57:46 +02:00
<?php
namespace Amp\Loop;
use Amp\Coroutine;
use Amp\Promise;
use React\Promise\PromiseInterface as ReactPromise;
use function Amp\Promise\rethrow;
2016-06-09 19:57:46 +02:00
2018-06-18 20:00:01 +02:00
class UvDriver extends Driver
{
2017-02-17 05:36:32 +01:00
/** @var resource 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[] */
2016-06-09 19:57:46 +02:00
private $events = [];
/** @var \Amp\Loop\Watcher[][] */
2016-06-09 19:57:46 +02:00
private $watchers = [];
/** @var resource[] */
private $streams = [];
2017-02-17 05:36:32 +01:00
/** @var callable */
2016-06-09 19:57:46 +02:00
private $ioCallback;
2017-02-17 05:36:32 +01:00
/** @var callable */
2016-06-09 19:57:46 +02:00
private $timerCallback;
2017-01-16 17:39:24 +01:00
2017-02-17 05:36:32 +01:00
/** @var callable */
2016-06-09 19:57:46 +02:00
private $signalCallback;
2018-06-18 20:00:01 +02:00
public function __construct()
{
2016-06-09 19:57:46 +02:00
$this->handle = \uv_loop_new();
$this->ioCallback = function ($event, $status, $events, $resource) {
$watchers = $this->watchers[(int) $event];
2016-06-09 19:57:46 +02:00
switch ($status) {
case 0: // OK
break;
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;
foreach ($this->watchers[(int) $event] as $watcher) {
$flags |= $watcher->enabled ? $watcher->type : 0;
}
\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
if (!($watcher->enabled && ($watcher->type & $events || ($events | 4) === 4))) {
continue;
}
try {
$result = ($watcher->callback)($watcher->id, $resource, $watcher->data);
if ($result === null) {
continue;
}
if ($result instanceof \Generator) {
$result = new Coroutine($result);
}
if ($result instanceof Promise || $result instanceof ReactPromise) {
rethrow($result);
}
} catch (\Throwable $exception) {
$this->error($exception);
}
2016-06-09 19:57:46 +02:00
}
};
$this->timerCallback = function ($event) {
$watcher = $this->watchers[(int) $event];
if ($watcher->type & Watcher::DELAY) {
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) {
// 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);
}
try {
$result = ($watcher->callback)($watcher->id, $watcher->data);
if ($result === null) {
return;
}
2017-03-12 18:03:13 +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);
}
2016-06-09 19:57:46 +02:00
};
$this->signalCallback = function ($event, $signo) {
$watcher = $this->watchers[(int) $event];
try {
$result = ($watcher->callback)($watcher->id, $signo, $watcher->data);
if ($result === null) {
return;
}
2017-03-12 18:03:13 +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);
}
2016-06-09 19:57:46 +02:00
};
}
/**
* {@inheritdoc}
*/
2018-06-18 20:00:01 +02:00
public function cancel(string $watcherId)
{
parent::cancel($watcherId);
if (!isset($this->events[$watcherId])) {
return;
}
$event = $this->events[$watcherId];
$eventId = (int) $event;
if ($this->watchers[$eventId] instanceof Watcher) { // All except IO watchers.
unset($this->watchers[$eventId]);
} else {
$watcher = $this->watchers[$eventId][$watcherId];
unset($this->watchers[$eventId][$watcherId]);
if (empty($this->watchers[$eventId])) {
unset($this->watchers[$eventId], $this->streams[(int) $watcher->value]);
}
}
unset($this->events[$watcherId]);
}
2018-06-18 20:00:01 +02:00
public static function isSupported(): bool
{
return \extension_loaded("uv");
}
/**
* {@inheritdoc}
*/
2018-06-18 20:00:01 +02:00
public function getHandle()
{
return $this->handle;
}
2016-06-09 19:57:46 +02:00
/**
* {@inheritdoc}
*/
2018-06-18 20:00:01 +02:00
protected function dispatch(bool $blocking)
{
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}
*/
2018-06-18 20:00:01 +02:00
protected function activate(array $watchers)
{
2016-06-09 19:57:46 +02:00
foreach ($watchers as $watcher) {
$id = $watcher->id;
switch ($watcher->type) {
case Watcher::READABLE:
case Watcher::WRITABLE:
$streamId = (int) $watcher->value;
if (isset($this->streams[$streamId])) {
$event = $this->streams[$streamId];
2016-06-09 19:57:46 +02:00
} elseif (isset($this->events[$id])) {
$event = $this->streams[$streamId] = $this->events[$id];
2016-06-09 19:57:46 +02:00
} else {
$event = $this->streams[$streamId] = \uv_poll_init_socket($this->handle, $watcher->value);
2016-06-09 19:57:46 +02:00
}
$eventId = (int) $event;
2016-06-09 19:57:46 +02:00
$this->events[$id] = $event;
$this->watchers[$eventId][$id] = $watcher;
2016-06-09 19:57:46 +02:00
$flags = 0;
foreach ($this->watchers[$eventId] as $watcher) {
$flags |= $watcher->enabled ? $watcher->type : 0;
2016-06-09 19:57:46 +02:00
}
\uv_poll_start($event, $flags, $this->ioCallback);
2016-06-09 19:57:46 +02:00
break;
case Watcher::DELAY:
case Watcher::REPEAT:
if (isset($this->events[$id])) {
$event = $this->events[$id];
} else {
$event = $this->events[$id] = \uv_timer_init($this->handle);
}
$this->watchers[(int) $event] = $watcher;
2016-06-09 19:57:46 +02:00
\uv_timer_start(
$event,
$watcher->value,
$watcher->type & Watcher::REPEAT ? $watcher->value : 0,
$this->timerCallback
);
break;
case Watcher::SIGNAL:
if (isset($this->events[$id])) {
$event = $this->events[$id];
} else {
$event = $this->events[$id] = \uv_signal_init($this->handle);
}
2017-01-16 17:39:24 +01:00
$this->watchers[(int) $event] = $watcher;
2016-06-09 19:57:46 +02:00
\uv_signal_start($event, $this->signalCallback, $watcher->value);
break;
default:
// @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}
*/
2018-06-18 20:00:01 +02:00
protected function deactivate(Watcher $watcher)
{
2016-06-09 19:57:46 +02:00
$id = $watcher->id;
if (!isset($this->events[$id])) {
return;
}
$event = $this->events[$id];
if (!\uv_is_active($event)) {
return;
}
2016-06-09 19:57:46 +02:00
switch ($watcher->type) {
case Watcher::READABLE:
case Watcher::WRITABLE:
$flags = 0;
foreach ($this->watchers[(int) $event] as $watcher) {
$flags |= $watcher->enabled ? $watcher->type : 0;
}
2017-04-23 14:39:19 +02:00
if ($flags) {
\uv_poll_start($event, $flags, $this->ioCallback);
} else {
\uv_poll_stop($event);
2016-06-09 19:57:46 +02:00
}
break;
case Watcher::DELAY:
case Watcher::REPEAT:
\uv_timer_stop($event);
2016-06-09 19:57:46 +02:00
break;
case Watcher::SIGNAL:
\uv_signal_stop($event);
2016-06-09 19:57:46 +02:00
break;
default:
// @codeCoverageIgnoreStart
throw new \Error("Unknown watcher type");
2018-06-18 20:00:01 +02:00
// @codeCoverageIgnoreEnd
2016-06-09 19:57:46 +02:00
}
}
}