2014-06-11 18:33:15 +02:00
|
|
|
<?php
|
|
|
|
|
2014-09-23 04:38:32 +02:00
|
|
|
namespace Amp;
|
2014-06-11 18:33:15 +02:00
|
|
|
|
2014-08-06 06:17:03 +02:00
|
|
|
class UvReactor implements SignalReactor {
|
2014-12-02 00:08:49 +01:00
|
|
|
use GeneratorResolver;
|
|
|
|
|
2014-06-11 18:33:15 +02:00
|
|
|
private $loop;
|
2014-08-21 19:54:02 +02:00
|
|
|
private $lastWatcherId = 1;
|
2014-08-06 20:17:23 +02:00
|
|
|
private $watchers;
|
2014-12-02 00:08:49 +01:00
|
|
|
private $enabledWatcherCount = 0;
|
2014-11-11 22:07:15 +01:00
|
|
|
private $streamIdPollMap = [];
|
2014-06-11 18:33:15 +02:00
|
|
|
private $gcWatcher;
|
|
|
|
private $gcCallback;
|
|
|
|
private $garbage = [];
|
2014-08-06 05:45:33 +02:00
|
|
|
private $isGcScheduled = false;
|
|
|
|
private $isRunning = false;
|
2014-06-11 18:33:15 +02:00
|
|
|
private $stopException;
|
|
|
|
private $resolution = 1000;
|
2014-08-06 05:45:33 +02:00
|
|
|
private $isWindows;
|
2014-08-08 22:39:11 +02:00
|
|
|
private $immediates = [];
|
2014-12-02 00:08:49 +01:00
|
|
|
private $onError;
|
|
|
|
private $onCallbackResolution;
|
2014-06-11 18:33:15 +02:00
|
|
|
|
2014-11-26 22:27:30 +01:00
|
|
|
private static $instanceCount = 0;
|
2014-11-11 18:00:01 +01:00
|
|
|
|
2014-12-01 21:39:57 +01:00
|
|
|
public function __construct() {
|
|
|
|
$this->loop = uv_loop_new();
|
2014-06-11 18:33:15 +02:00
|
|
|
$this->gcWatcher = uv_timer_init($this->loop);
|
|
|
|
$this->gcCallback = function() { $this->collectGarbage(); };
|
2014-08-06 05:45:33 +02:00
|
|
|
$this->isWindows = (stripos(PHP_OS, 'win') === 0);
|
2014-12-02 00:08:49 +01:00
|
|
|
$this->onCallbackResolution = function($e = null, $r = null) {
|
|
|
|
if (empty($e)) {
|
|
|
|
return;
|
|
|
|
} elseif ($onError = $this->onError) {
|
|
|
|
$onError($e);
|
|
|
|
} else {
|
2014-09-22 22:47:48 +02:00
|
|
|
throw $e;
|
|
|
|
}
|
|
|
|
};
|
2014-11-26 22:27:30 +01:00
|
|
|
self::$instanceCount++;
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private function collectGarbage() {
|
|
|
|
$this->garbage = [];
|
2014-08-06 05:45:33 +02:00
|
|
|
$this->isGcScheduled = false;
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start the event reactor and assume program flow control
|
|
|
|
*
|
2014-08-06 05:45:33 +02:00
|
|
|
* @param callable $onStart Optional callback to invoke immediately upon reactor start
|
|
|
|
* @throws \Exception Will throw if code executed during the event loop throws
|
|
|
|
* @return void
|
2014-06-11 18:33:15 +02:00
|
|
|
*/
|
2014-08-06 05:45:33 +02:00
|
|
|
public function run(callable $onStart = null) {
|
2014-06-11 18:33:15 +02:00
|
|
|
if ($this->isRunning) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-08-06 06:17:03 +02:00
|
|
|
$this->isRunning = true;
|
2014-08-08 20:15:23 +02:00
|
|
|
if ($onStart) {
|
2014-10-23 00:14:01 +02:00
|
|
|
$this->immediately($onStart);
|
2014-08-08 20:15:23 +02:00
|
|
|
}
|
2014-08-08 22:39:11 +02:00
|
|
|
|
|
|
|
while ($this->isRunning) {
|
|
|
|
if ($this->immediates && !$this->doImmediates()) {
|
|
|
|
break;
|
|
|
|
}
|
2014-12-02 00:08:49 +01:00
|
|
|
if (empty($this->enabledWatcherCount)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
uv_run($this->loop, \UV::RUN_DEFAULT | \UV::RUN_ONCE);
|
2014-08-08 22:39:11 +02:00
|
|
|
}
|
2014-06-11 18:33:15 +02:00
|
|
|
|
|
|
|
if ($this->stopException) {
|
|
|
|
$e = $this->stopException;
|
2014-08-06 05:45:33 +02:00
|
|
|
$this->stopException = null;
|
2014-06-11 18:33:15 +02:00
|
|
|
throw $e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-08 22:39:11 +02:00
|
|
|
private function doImmediates() {
|
|
|
|
$immediates = $this->immediates;
|
|
|
|
foreach ($immediates as $watcherId => $callback) {
|
2014-12-02 00:08:49 +01:00
|
|
|
try {
|
|
|
|
$this->enabledWatcherCount--;
|
|
|
|
unset(
|
|
|
|
$this->immediates[$watcherId],
|
|
|
|
$this->watchers[$watcherId]
|
|
|
|
);
|
|
|
|
$result = $callback($this, $watcherId);
|
|
|
|
if ($result instanceof \Generator) {
|
|
|
|
$this->resolveGenerator($result)->when($this->onCallbackResolution);
|
|
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
$this->handleRunError($e);
|
2014-09-22 22:47:48 +02:00
|
|
|
}
|
2014-12-02 00:08:49 +01:00
|
|
|
|
2014-08-08 22:39:11 +02:00
|
|
|
if (!$this->isRunning) {
|
|
|
|
// If a watcher stops the reactor break out of the loop
|
2014-12-02 00:08:49 +01:00
|
|
|
return false;
|
2014-08-08 22:39:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->isRunning;
|
|
|
|
}
|
|
|
|
|
2014-06-11 18:33:15 +02:00
|
|
|
/**
|
|
|
|
* Execute a single event loop iteration
|
2014-08-06 22:01:24 +02:00
|
|
|
*
|
2014-11-26 19:42:36 +01:00
|
|
|
* @param bool $noWait If TRUE, return immediately when no watchers are immediately ready to trigger
|
2014-08-06 22:01:24 +02:00
|
|
|
* @return void
|
2014-06-11 18:33:15 +02:00
|
|
|
*/
|
2014-11-26 19:42:36 +01:00
|
|
|
public function tick($noWait = false) {
|
2014-08-08 22:39:11 +02:00
|
|
|
if ($this->isRunning) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-08-06 06:17:03 +02:00
|
|
|
$this->isRunning = true;
|
2014-08-08 22:39:11 +02:00
|
|
|
|
|
|
|
if (empty($this->immediates) || $this->doImmediates()) {
|
2014-11-26 19:42:36 +01:00
|
|
|
$flags = $noWait ? (\UV::RUN_NOWAIT | \UV::RUN_ONCE) : \UV::RUN_ONCE;
|
|
|
|
uv_run($this->loop, $flags);
|
2014-08-08 22:39:11 +02:00
|
|
|
}
|
|
|
|
|
2014-08-06 05:45:33 +02:00
|
|
|
$this->isRunning = false;
|
2014-06-11 18:33:15 +02:00
|
|
|
|
|
|
|
if ($this->stopException) {
|
|
|
|
$e = $this->stopException;
|
2014-08-06 05:45:33 +02:00
|
|
|
$this->stopException = null;
|
2014-06-11 18:33:15 +02:00
|
|
|
throw $e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop the event reactor
|
2014-08-06 22:01:24 +02:00
|
|
|
*
|
|
|
|
* @return void
|
2014-06-11 18:33:15 +02:00
|
|
|
*/
|
|
|
|
public function stop() {
|
|
|
|
uv_stop($this->loop);
|
2014-08-08 22:39:11 +02:00
|
|
|
$this->isRunning = false;
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Schedule a callback for immediate invocation in the next event loop iteration
|
|
|
|
*
|
|
|
|
* @param callable $callback Any valid PHP callable
|
2014-08-21 19:54:02 +02:00
|
|
|
* @return string Returns a unique watcher ID
|
2014-06-11 18:33:15 +02:00
|
|
|
*/
|
|
|
|
public function immediately(callable $callback) {
|
2014-12-02 00:08:49 +01:00
|
|
|
$this->enabledWatcherCount++;
|
2014-08-21 19:54:02 +02:00
|
|
|
$watcherId = (string) $this->lastWatcherId++;
|
2014-08-08 22:39:11 +02:00
|
|
|
$this->immediates[$watcherId] = $callback;
|
|
|
|
|
|
|
|
$watcher = new \StdClass;
|
|
|
|
$watcher->id = $watcherId;
|
2014-11-26 22:27:30 +01:00
|
|
|
$watcher->type = Watcher::IMMEDIATE;
|
2014-08-08 22:39:11 +02:00
|
|
|
$watcher->callback = $callback;
|
|
|
|
$watcher->isEnabled = true;
|
|
|
|
|
|
|
|
$this->watchers[$watcher->id] = $watcher;
|
|
|
|
|
|
|
|
return $watcherId;
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Schedule a callback to execute once
|
|
|
|
*
|
|
|
|
* @param callable $callback Any valid PHP callable
|
|
|
|
* @param int $msDelay The delay in milliseconds before the callback will trigger (may be zero)
|
2014-08-21 19:54:02 +02:00
|
|
|
* @return string Returns a unique watcher ID
|
2014-06-11 18:33:15 +02:00
|
|
|
*/
|
|
|
|
public function once(callable $callback, $msDelay) {
|
2014-11-26 22:27:30 +01:00
|
|
|
return $this->startTimer($callback, $msDelay, $msInterval = 0, Watcher::TIMER_ONCE);
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-08-08 23:08:28 +02:00
|
|
|
* Schedule a recurring callback to execute every $msInterval seconds until cancelled
|
2014-06-11 18:33:15 +02:00
|
|
|
*
|
|
|
|
* @param callable $callback Any valid PHP callable
|
|
|
|
* @param int $msInterval The interval in milliseconds between callback invocations
|
2014-08-21 19:54:02 +02:00
|
|
|
* @return string Returns a unique watcher ID
|
2014-06-11 18:33:15 +02:00
|
|
|
*/
|
|
|
|
public function repeat(callable $callback, $msInterval) {
|
2014-08-08 23:08:28 +02:00
|
|
|
// A zero interval is interpreted as a "non-repeating" timer by php-uv. Here
|
|
|
|
// we use a hack to notify on STDOUT writability for 0 interval repeating
|
|
|
|
// callbacks because it's much more performant than churning 1ms timers.
|
|
|
|
$msInterval = ($msInterval && $msInterval > 0) ? (int) $msInterval : -1;
|
|
|
|
|
|
|
|
return ($msInterval === -1)
|
2014-11-26 22:27:30 +01:00
|
|
|
? $this->watchStream(STDOUT, $callback, Watcher::IO_WRITER, true)
|
|
|
|
: $this->startTimer($callback, $msInterval, $msInterval, Watcher::TIMER_REPEAT);
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
|
2014-11-26 22:27:30 +01:00
|
|
|
private function startTimer($callback, $msDelay, $msInterval, $type) {
|
2014-12-02 00:08:49 +01:00
|
|
|
$this->enabledWatcherCount++;
|
2014-06-11 18:33:15 +02:00
|
|
|
$watcher = new UvTimerWatcher;
|
2014-08-21 19:54:02 +02:00
|
|
|
$watcher->id = (string) $this->lastWatcherId++;
|
2014-11-26 22:27:30 +01:00
|
|
|
$watcher->type = $type;
|
2014-06-11 18:33:15 +02:00
|
|
|
$watcher->uvStruct = uv_timer_init($this->loop);
|
|
|
|
$watcher->callback = $this->wrapTimerCallback($watcher, $callback);
|
|
|
|
$watcher->msDelay = ($msDelay > 0) ? (int) $msDelay : 0;
|
|
|
|
$watcher->msInterval = ($msInterval > 0) ? (int) $msInterval : 0;
|
2014-08-06 06:17:03 +02:00
|
|
|
$watcher->isEnabled = true;
|
2014-06-11 18:33:15 +02:00
|
|
|
|
|
|
|
$this->watchers[$watcher->id] = $watcher;
|
|
|
|
|
|
|
|
uv_timer_start($watcher->uvStruct, $watcher->msDelay, $watcher->msInterval, $watcher->callback);
|
|
|
|
|
|
|
|
return $watcher->id;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function wrapTimerCallback($watcher, $callback) {
|
|
|
|
return function() use ($watcher, $callback) {
|
|
|
|
try {
|
2014-09-22 22:47:48 +02:00
|
|
|
$result = $callback($this, $watcher->id);
|
|
|
|
if ($result instanceof \Generator) {
|
2014-12-02 00:08:49 +01:00
|
|
|
$this->resolveGenerator($result)->when($this->onCallbackResolution);
|
2014-09-22 22:47:48 +02:00
|
|
|
}
|
2014-11-26 22:27:30 +01:00
|
|
|
if ($watcher->type === Watcher::TIMER_ONCE) {
|
2014-08-06 05:48:41 +02:00
|
|
|
$this->clearWatcher($watcher->id);
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
2014-12-02 00:08:49 +01:00
|
|
|
$this->handleRunError($e);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private function handleRunError(\Exception $e) {
|
|
|
|
try {
|
|
|
|
if (empty($this->onError)) {
|
2014-06-11 18:33:15 +02:00
|
|
|
$this->stopException = $e;
|
|
|
|
$this->stop();
|
2014-12-02 00:08:49 +01:00
|
|
|
} else {
|
|
|
|
$handler = $this->onCallbackResolution;
|
|
|
|
$handler($e);
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
2014-12-02 00:08:49 +01:00
|
|
|
} catch (\Exception $e) {
|
|
|
|
$this->stopException = $e;
|
|
|
|
$this->stop();
|
|
|
|
}
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Schedule an event to trigger once at the specified time
|
|
|
|
*
|
|
|
|
* @param callable $callback Any valid PHP callable
|
2014-08-07 03:29:08 +02:00
|
|
|
* @param mixed[int|string] $unixTimeOrStr A future unix timestamp or string parsable by strtotime()
|
|
|
|
* @throws \InvalidArgumentException On invalid future time
|
2014-08-21 19:54:02 +02:00
|
|
|
* @return string Returns a unique watcher ID
|
2014-06-11 18:33:15 +02:00
|
|
|
*/
|
2014-08-07 03:29:08 +02:00
|
|
|
public function at(callable $callback, $unixTimeOrStr) {
|
2014-06-11 18:33:15 +02:00
|
|
|
$now = time();
|
2014-08-07 03:29:08 +02:00
|
|
|
if (is_int($unixTimeOrStr) && $unixTimeOrStr > $now) {
|
|
|
|
$secondsUntil = ($unixTimeOrStr - $now);
|
|
|
|
} elseif (($executeAt = @strtotime($unixTimeOrStr)) && $executeAt > $now) {
|
|
|
|
$secondsUntil = ($executeAt - $now);
|
|
|
|
} else {
|
2014-06-11 18:33:15 +02:00
|
|
|
throw new \InvalidArgumentException(
|
2014-08-07 03:29:08 +02:00
|
|
|
'Unix timestamp or future time string (parsable by strtotime()) required'
|
2014-06-11 18:33:15 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2014-08-07 03:29:08 +02:00
|
|
|
$msDelay = $secondsUntil * $this->resolution;
|
2014-06-11 18:33:15 +02:00
|
|
|
|
|
|
|
return $this->once($callback, $msDelay);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Watch a stream resource for IO readable data and trigger the callback when actionable
|
|
|
|
*
|
|
|
|
* @param resource $stream A stream resource to watch for readable data
|
|
|
|
* @param callable $callback Any valid PHP callable
|
|
|
|
* @param bool $enableNow Should the watcher be enabled now or held for later use?
|
2014-08-21 19:54:02 +02:00
|
|
|
* @return string Returns a unique watcher ID
|
2014-06-11 18:33:15 +02:00
|
|
|
*/
|
2014-08-06 06:17:03 +02:00
|
|
|
public function onReadable($stream, callable $callback, $enableNow = true) {
|
2014-11-26 22:27:30 +01:00
|
|
|
return $this->watchStream($stream, $callback, Watcher::IO_READER, (bool) $enableNow);
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Watch a stream resource to become writable and trigger the callback when actionable
|
|
|
|
*
|
|
|
|
* @param resource $stream A stream resource to watch for writability
|
|
|
|
* @param callable $callback Any valid PHP callable
|
|
|
|
* @param bool $enableNow Should the watcher be enabled now or held for later use?
|
2014-08-21 19:54:02 +02:00
|
|
|
* @return string Returns a unique watcher ID
|
2014-06-11 18:33:15 +02:00
|
|
|
*/
|
2014-08-06 06:17:03 +02:00
|
|
|
public function onWritable($stream, callable $callback, $enableNow = true) {
|
2014-11-26 22:27:30 +01:00
|
|
|
return $this->watchStream($stream, $callback, Watcher::IO_WRITER, (bool) $enableNow);
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
|
2014-11-26 22:27:30 +01:00
|
|
|
private function watchStream($stream, callable $callback, $type, $enableNow) {
|
2014-12-02 00:08:49 +01:00
|
|
|
$this->enabledWatcherCount += $enableNow;
|
2014-11-11 22:07:15 +01:00
|
|
|
$streamId = (int) $stream;
|
|
|
|
$poll = isset($this->streamIdPollMap[$streamId])
|
|
|
|
? $this->streamIdPollMap[$streamId]
|
|
|
|
: $this->makePollHandle($stream);
|
|
|
|
|
|
|
|
$watcherId = $this->lastWatcherId;
|
|
|
|
$this->watchers[$watcherId] = $watcher = new UvIoWatcher;
|
|
|
|
$watcher->id = $watcherId = $this->lastWatcherId++;
|
2014-11-26 22:27:30 +01:00
|
|
|
$watcher->type = $type;
|
2014-11-11 22:07:15 +01:00
|
|
|
$watcher->poll = $poll;
|
|
|
|
$watcher->stream = $stream;
|
|
|
|
$watcher->callback = $callback;
|
|
|
|
$watcher->isEnabled = $enableNow;
|
|
|
|
|
|
|
|
if ($enableNow === false) {
|
|
|
|
$poll->disable[$watcherId] = $watcher;
|
|
|
|
return $watcherId;
|
|
|
|
}
|
2014-06-11 18:33:15 +02:00
|
|
|
|
2014-11-26 22:27:30 +01:00
|
|
|
if ($type === Watcher::IO_READER) {
|
2014-11-11 22:07:15 +01:00
|
|
|
$poll->readers[$watcherId] = $watcher;
|
2014-06-11 18:33:15 +02:00
|
|
|
} else {
|
2014-11-11 22:07:15 +01:00
|
|
|
$poll->writers[$watcherId] = $watcher;
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
|
2014-11-12 19:17:08 +01:00
|
|
|
$newFlags = 0;
|
2014-11-11 22:07:15 +01:00
|
|
|
if ($poll->readers) {
|
2014-11-12 19:17:08 +01:00
|
|
|
$newFlags |= \UV::READABLE;
|
2014-11-11 22:07:15 +01:00
|
|
|
}
|
|
|
|
if ($poll->writers) {
|
2014-11-12 19:17:08 +01:00
|
|
|
$newFlags |= \UV::WRITABLE;
|
2014-11-11 22:07:15 +01:00
|
|
|
}
|
2014-11-12 19:17:08 +01:00
|
|
|
if ($newFlags != $poll->flags) {
|
|
|
|
$poll->flags = $newFlags;
|
|
|
|
uv_poll_start($poll->handle, $newFlags, $poll->callback);
|
2014-11-11 22:07:15 +01:00
|
|
|
}
|
2014-06-11 18:33:15 +02:00
|
|
|
|
2014-11-11 22:07:15 +01:00
|
|
|
return $watcherId;
|
|
|
|
}
|
2014-06-11 18:33:15 +02:00
|
|
|
|
2014-11-11 22:07:15 +01:00
|
|
|
private function makePollHandle($stream) {
|
|
|
|
// Windows needs the socket-specific init function, so make sure we use
|
|
|
|
// it when dealing with tcp/ssl streams.
|
|
|
|
$pollInitFunc = $this->isWindows
|
|
|
|
? $this->chooseWindowsPollingFunction($stream)
|
|
|
|
: 'uv_poll_init';
|
2014-06-11 18:33:15 +02:00
|
|
|
|
2014-11-11 22:07:15 +01:00
|
|
|
$streamId = (int) $stream;
|
|
|
|
$this->streamIdPollMap[$streamId] = $poll = new UvPoll;
|
|
|
|
$poll->flags = 0;
|
|
|
|
$poll->handle = $pollInitFunc($this->loop, $stream);
|
|
|
|
$poll->callback = function($uvHandle, $stat, $events) use ($poll) {
|
|
|
|
if ($events & \UV::READABLE) {
|
|
|
|
foreach ($poll->readers as $watcher) {
|
|
|
|
$this->invokePollWatcher($watcher);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($events & \UV::WRITABLE) {
|
|
|
|
foreach ($poll->writers as $watcher) {
|
|
|
|
$this->invokePollWatcher($watcher);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2014-06-11 18:33:15 +02:00
|
|
|
|
2014-11-11 22:07:15 +01:00
|
|
|
return $poll;
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
|
2014-08-06 05:45:33 +02:00
|
|
|
private function chooseWindowsPollingFunction($stream) {
|
2014-11-11 17:08:46 +01:00
|
|
|
$streamType = stream_get_meta_data($stream)['stream_type'];
|
|
|
|
|
|
|
|
return ($streamType === 'tcp_socket/ssl' || $streamType === 'tcp_socket')
|
2014-08-06 05:45:33 +02:00
|
|
|
? 'uv_poll_init_socket'
|
|
|
|
: 'uv_poll_init';
|
|
|
|
}
|
|
|
|
|
2014-11-11 22:07:15 +01:00
|
|
|
private function invokePollWatcher(UvIoWatcher $watcher) {
|
|
|
|
try {
|
|
|
|
$callback = $watcher->callback;
|
|
|
|
$result = $callback($this, $watcher->id, $watcher->stream);
|
|
|
|
if ($result instanceof \Generator) {
|
2014-12-02 00:08:49 +01:00
|
|
|
$this->resolveGenerator($result)->when($this->onCallbackResolution);
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
2014-11-11 22:07:15 +01:00
|
|
|
} catch (\Exception $e) {
|
2014-12-02 00:08:49 +01:00
|
|
|
$this->handleRunError($e);
|
2014-11-11 22:07:15 +01:00
|
|
|
}
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
|
2014-08-06 06:17:03 +02:00
|
|
|
/**
|
|
|
|
* React to process control signals
|
|
|
|
*
|
|
|
|
* @param int $signo The signal number to watch for (e.g. 2 for Uv::SIGINT)
|
|
|
|
* @param callable $onSignal
|
2014-08-21 19:54:02 +02:00
|
|
|
* @return string Returns a unique watcher ID
|
2014-08-06 06:17:03 +02:00
|
|
|
*/
|
|
|
|
public function onSignal($signo, callable $onSignal) {
|
2014-12-02 00:08:49 +01:00
|
|
|
$this->enabledWatcherCount++;
|
2014-08-06 06:17:03 +02:00
|
|
|
$watcher = new UvSignalWatcher;
|
2014-08-21 19:54:02 +02:00
|
|
|
$watcher->id = (string) $this->lastWatcherId++;
|
2014-11-26 22:27:30 +01:00
|
|
|
$watcher->type = Watcher::SIGNAL;
|
2014-08-06 06:17:03 +02:00
|
|
|
$watcher->signo = $signo;
|
|
|
|
$watcher->uvStruct = uv_signal_init($this->loop);
|
|
|
|
$watcher->callback = $this->wrapSignalCallback($watcher, $onSignal);
|
|
|
|
$watcher->isEnabled = true;
|
|
|
|
|
2014-08-06 20:33:27 +02:00
|
|
|
uv_signal_start($watcher->uvStruct, $watcher->callback, $watcher->signo);
|
2014-08-06 06:17:03 +02:00
|
|
|
|
|
|
|
$this->watchers[$watcher->id] = $watcher;
|
|
|
|
|
|
|
|
return $watcher->id;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function wrapSignalCallback($watcher, $callback) {
|
|
|
|
return function() use ($watcher, $callback) {
|
|
|
|
try {
|
2014-09-22 22:47:48 +02:00
|
|
|
$result = $callback($this, $watcher->id, $watcher->signo);
|
|
|
|
if ($result instanceof \Generator) {
|
2014-12-02 00:08:49 +01:00
|
|
|
$this->resolveGenerator($result)->when($this->onCallbackResolution);
|
2014-09-22 22:47:48 +02:00
|
|
|
}
|
2014-08-06 06:17:03 +02:00
|
|
|
} catch (\Exception $e) {
|
2014-12-02 00:08:49 +01:00
|
|
|
$this->handleRunError($e);
|
2014-08-06 06:17:03 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2014-06-11 18:33:15 +02:00
|
|
|
/**
|
|
|
|
* Cancel an existing watcher
|
|
|
|
*
|
|
|
|
* @param int $watcherId
|
2014-08-06 06:17:03 +02:00
|
|
|
* @return void
|
2014-06-11 18:33:15 +02:00
|
|
|
*/
|
|
|
|
public function cancel($watcherId) {
|
|
|
|
if (isset($this->watchers[$watcherId])) {
|
|
|
|
$this->clearWatcher($watcherId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function clearWatcher($watcherId) {
|
|
|
|
$watcher = $this->watchers[$watcherId];
|
|
|
|
unset($this->watchers[$watcherId]);
|
|
|
|
|
|
|
|
if ($watcher->isEnabled) {
|
2014-12-02 00:08:49 +01:00
|
|
|
$this->enabledWatcherCount--;
|
2014-11-26 22:27:30 +01:00
|
|
|
switch ($watcher->type) {
|
|
|
|
case Watcher::IO_READER:
|
2014-11-11 22:07:15 +01:00
|
|
|
// fallthrough
|
2014-11-26 22:27:30 +01:00
|
|
|
case Watcher::IO_WRITER:
|
2014-11-11 22:07:15 +01:00
|
|
|
$this->clearPollFromWatcher($watcher);
|
2014-08-06 06:17:03 +02:00
|
|
|
break;
|
2014-11-26 22:27:30 +01:00
|
|
|
case Watcher::SIGNAL:
|
2014-08-06 06:17:03 +02:00
|
|
|
uv_signal_stop($watcher->uvStruct);
|
|
|
|
break;
|
2014-11-26 22:27:30 +01:00
|
|
|
case Watcher::IMMEDIATE:
|
2014-08-08 22:39:11 +02:00
|
|
|
unset($this->immediates[$watcherId]);
|
|
|
|
break;
|
2014-08-06 06:17:03 +02:00
|
|
|
default:
|
|
|
|
uv_timer_stop($watcher->uvStruct);
|
|
|
|
break;
|
|
|
|
}
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$this->garbage[] = $watcher;
|
|
|
|
|
|
|
|
if (!$this->isGcScheduled) {
|
|
|
|
uv_timer_start($this->gcWatcher, 250, 0, $this->gcCallback);
|
2014-08-06 06:17:03 +02:00
|
|
|
$this->isGcScheduled = true;
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-11 22:07:15 +01:00
|
|
|
private function clearPollFromWatcher(UvIoWatcher $watcher) {
|
|
|
|
$poll = $watcher->poll;
|
|
|
|
$watcherId = $watcher->id;
|
|
|
|
|
|
|
|
unset(
|
|
|
|
$poll->readers[$watcherId],
|
|
|
|
$poll->writers[$watcherId],
|
|
|
|
$poll->disable[$watcherId]
|
|
|
|
);
|
|
|
|
|
|
|
|
// If any watchers are still enabled for this stream we're finished here
|
|
|
|
$hasEnabledWatchers = ((int) $poll->readers) + ((int) $poll->writers);
|
|
|
|
if ($hasEnabledWatchers) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Always stop polling if no enabled watchers remain
|
|
|
|
uv_poll_stop($poll->handle);
|
|
|
|
|
|
|
|
// If all watchers are disabled we can pull out here
|
|
|
|
$hasDisabledWatchers = (bool) $poll->disable;
|
|
|
|
if ($hasDisabledWatchers) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise there are no watchers left for this poll and we should clear it
|
|
|
|
$streamId = (int) $watcher->stream;
|
|
|
|
unset($this->streamIdPollMap[$streamId]);
|
|
|
|
}
|
|
|
|
|
2014-06-11 18:33:15 +02:00
|
|
|
/**
|
|
|
|
* Temporarily disable (but don't cancel) an existing timer/stream watcher
|
|
|
|
*
|
|
|
|
* @param int $watcherId
|
2014-08-06 06:17:03 +02:00
|
|
|
* @return void
|
2014-06-11 18:33:15 +02:00
|
|
|
*/
|
|
|
|
public function disable($watcherId) {
|
|
|
|
if (!isset($this->watchers[$watcherId])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$watcher = $this->watchers[$watcherId];
|
|
|
|
|
2014-08-06 06:17:03 +02:00
|
|
|
if (!$watcher->isEnabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-12-02 00:08:49 +01:00
|
|
|
$this->enabledWatcherCount--;
|
2014-11-26 22:27:30 +01:00
|
|
|
switch ($watcher->type) {
|
|
|
|
case Watcher::IO_READER:
|
2014-11-11 22:07:15 +01:00
|
|
|
// fallthrough
|
2014-11-26 22:27:30 +01:00
|
|
|
case Watcher::IO_WRITER:
|
2014-11-11 22:07:15 +01:00
|
|
|
$this->disablePollFromWatcher($watcher);
|
2014-08-06 06:17:03 +02:00
|
|
|
break;
|
2014-11-26 22:27:30 +01:00
|
|
|
case Watcher::SIGNAL:
|
2014-08-06 06:17:03 +02:00
|
|
|
uv_signal_stop($watcher->uvStruct);
|
|
|
|
break;
|
2014-11-26 22:27:30 +01:00
|
|
|
case Watcher::IMMEDIATE:
|
2014-08-08 22:39:11 +02:00
|
|
|
unset($this->immediates[$watcher->id]);
|
|
|
|
break;
|
2014-08-06 06:17:03 +02:00
|
|
|
default:
|
|
|
|
uv_timer_stop($watcher->uvStruct);
|
|
|
|
break;
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
2014-08-06 06:17:03 +02:00
|
|
|
|
|
|
|
$watcher->isEnabled = false;
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
|
2014-11-11 22:07:15 +01:00
|
|
|
private function disablePollFromWatcher(UvIoWatcher $watcher) {
|
|
|
|
$poll = $watcher->poll;
|
|
|
|
$watcherId = $watcher->id;
|
|
|
|
|
2014-11-12 19:17:08 +01:00
|
|
|
unset(
|
|
|
|
$poll->readers[$watcherId],
|
|
|
|
$poll->writers[$watcherId]
|
|
|
|
);
|
2014-11-11 22:07:15 +01:00
|
|
|
|
|
|
|
$poll->disable[$watcherId] = $watcher;
|
|
|
|
|
2014-11-12 19:17:08 +01:00
|
|
|
if (!($poll->readers || $poll->writers)) {
|
2014-11-11 22:07:15 +01:00
|
|
|
uv_poll_stop($poll->handle);
|
2014-11-12 19:17:08 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we're still here we may need to update the polling flags
|
|
|
|
$newFlags = 0;
|
|
|
|
if ($poll->readers) {
|
|
|
|
$newFlags |= \UV::READABLE;
|
|
|
|
}
|
|
|
|
if ($poll->writers) {
|
|
|
|
$newFlags |= \UV::WRITABLE;
|
|
|
|
}
|
|
|
|
if ($poll->flags != $newFlags) {
|
|
|
|
$poll->flags = $newFlags;
|
|
|
|
uv_poll_start($poll->handle, $newFlags, $poll->callback);
|
2014-11-11 22:07:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-11 18:33:15 +02:00
|
|
|
/**
|
|
|
|
* Enable a disabled timer/stream watcher
|
|
|
|
*
|
|
|
|
* @param int $watcherId
|
2014-08-06 06:17:03 +02:00
|
|
|
* @return void
|
2014-06-11 18:33:15 +02:00
|
|
|
*/
|
|
|
|
public function enable($watcherId) {
|
|
|
|
if (!isset($this->watchers[$watcherId])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$watcher = $this->watchers[$watcherId];
|
|
|
|
|
|
|
|
if ($watcher->isEnabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-12-02 00:08:49 +01:00
|
|
|
$this->enabledWatcherCount++;
|
|
|
|
|
2014-11-26 22:27:30 +01:00
|
|
|
switch ($watcher->type) {
|
|
|
|
case Watcher::IO_READER:
|
2014-11-11 22:07:15 +01:00
|
|
|
// fallthrough
|
2014-11-26 22:27:30 +01:00
|
|
|
case Watcher::IO_WRITER:
|
2014-11-11 22:07:15 +01:00
|
|
|
$this->enablePollFromWatcher($watcher);
|
2014-08-06 06:17:03 +02:00
|
|
|
break;
|
2014-11-26 22:27:30 +01:00
|
|
|
case Watcher::SIGNAL:
|
2014-08-06 06:17:03 +02:00
|
|
|
uv_signal_start($watcher->uvStruct, $watcher->callback, $watcher->signo);
|
|
|
|
break;
|
2014-11-26 22:27:30 +01:00
|
|
|
case Watcher::IMMEDIATE:
|
2014-08-08 22:39:11 +02:00
|
|
|
$this->immediates[$watcher->id] = $watcher->callback;
|
|
|
|
break;
|
2014-08-06 06:17:03 +02:00
|
|
|
default:
|
|
|
|
uv_timer_start($watcher->uvStruct, $watcher->msDelay, $watcher->msInterval, $watcher->callback);
|
|
|
|
break;
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
|
|
|
|
2014-08-06 06:17:03 +02:00
|
|
|
$watcher->isEnabled = true;
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|
2014-08-19 20:20:04 +02:00
|
|
|
|
2014-11-11 22:07:15 +01:00
|
|
|
private function enablePollFromWatcher(UvIoWatcher $watcher) {
|
|
|
|
$poll = $watcher->poll;
|
|
|
|
$watcherId = $watcher->id;
|
|
|
|
|
|
|
|
unset($poll->disable[$watcherId]);
|
|
|
|
|
|
|
|
$preexistingFlags = $poll->flags;
|
|
|
|
|
2014-11-26 22:27:30 +01:00
|
|
|
if ($watcher->type === Watcher::IO_READER) {
|
2014-11-11 22:07:15 +01:00
|
|
|
$poll->flags |= \UV::READABLE;
|
|
|
|
$poll->readers[$watcherId] = $watcher;
|
|
|
|
} else {
|
|
|
|
$poll->flags |= \UV::WRITABLE;
|
|
|
|
$poll->writers[$watcherId] = $watcher;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($preexistingFlags != $poll->flags) {
|
|
|
|
uv_poll_start($poll->handle, $poll->flags, $poll->callback);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-19 20:20:04 +02:00
|
|
|
/**
|
|
|
|
* Access the underlying php-uv extension loop resource
|
|
|
|
*
|
|
|
|
* This method exists outside the base Reactor API. It provides access to the underlying php-uv
|
|
|
|
* event loop resource for code that wishes to interact with lower-level php-uv extension
|
|
|
|
* functionality.
|
|
|
|
*
|
|
|
|
* @return resource
|
|
|
|
*/
|
|
|
|
public function getUnderlyingLoop() {
|
|
|
|
return $this->loop;
|
|
|
|
}
|
2014-12-02 00:08:49 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* An optional "last-chance" exception handler for errors resulting during callback invocation
|
|
|
|
*
|
|
|
|
* If a reactor callback throws and no onError() callback is specified the exception will
|
|
|
|
* bubble up the stack. onError() callbacks are passed a single parameter: the uncaught
|
|
|
|
* exception that resulted in the callback's invocation.
|
|
|
|
*
|
|
|
|
* @param callable $onErrorCallback
|
|
|
|
* @return self
|
|
|
|
*/
|
|
|
|
public function onError(callable $onErrorCallback) {
|
|
|
|
$this->onError = $onErrorCallback;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
2014-12-02 07:06:36 +01:00
|
|
|
|
2014-11-26 22:27:30 +01:00
|
|
|
public function __destruct() {
|
|
|
|
self::$instanceCount--;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function __debugInfo() {
|
|
|
|
$immediates = $timers = $readers = $writers = $signals = $disabled = 0;
|
|
|
|
foreach ($this->watchers as $watcher) {
|
|
|
|
switch ($watcher->type) {
|
|
|
|
case Watcher::IMMEDIATE:
|
|
|
|
$immediates++;
|
|
|
|
break;
|
|
|
|
case Watcher::TIMER_ONCE:
|
|
|
|
case Watcher::TIMER_REPEAT:
|
|
|
|
$timers++;
|
|
|
|
break;
|
|
|
|
case Watcher::IO_READER:
|
|
|
|
$readers++;
|
|
|
|
break;
|
|
|
|
case Watcher::IO_WRITER:
|
|
|
|
$writers++;
|
|
|
|
break;
|
|
|
|
case Watcher::SIGNAL:
|
|
|
|
$signals++;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new \DomainException(
|
|
|
|
"Unexpected watcher type: {$watcher->type}"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$disabled += !$watcher->isEnabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
return [
|
|
|
|
'timers' => $timers,
|
|
|
|
'immediates' => $immediates,
|
|
|
|
'io_readers' => $readers,
|
|
|
|
'io_writers' => $writers,
|
|
|
|
'signals' => $signals,
|
|
|
|
'disabled' => $disabled,
|
|
|
|
'last_watcher_id' => $this->lastWatcherId,
|
|
|
|
'instances' => self::$instanceCount,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class UvIoWatcher extends Watcher {
|
|
|
|
// Inherited:
|
|
|
|
// public $id;
|
|
|
|
// public $type;
|
|
|
|
// public $isEnabled;
|
|
|
|
public $poll;
|
|
|
|
public $stream;
|
|
|
|
public $callback;
|
|
|
|
}
|
|
|
|
|
|
|
|
class UvPoll extends Struct {
|
|
|
|
public $flags;
|
|
|
|
public $handle;
|
|
|
|
public $callback;
|
|
|
|
public $readers = [];
|
|
|
|
public $writers = [];
|
|
|
|
public $disable = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
class UvSignalWatcher extends Watcher {
|
|
|
|
// Inherited:
|
|
|
|
// public $id;
|
|
|
|
// public $type;
|
|
|
|
// public $isEnabled;
|
|
|
|
public $signo;
|
|
|
|
public $uvStruct;
|
|
|
|
public $callback;
|
|
|
|
}
|
|
|
|
|
|
|
|
class UvTimerWatcher extends Watcher {
|
|
|
|
// Inherited:
|
|
|
|
// public $id;
|
|
|
|
// public $type;
|
|
|
|
// public $isEnabled;
|
|
|
|
public $uvStruct;
|
|
|
|
public $callback;
|
|
|
|
public $msDelay;
|
|
|
|
public $msInterval;
|
2014-06-11 18:33:15 +02:00
|
|
|
}
|