1
0
mirror of https://github.com/danog/amp.git synced 2024-11-27 12:35:02 +01:00
amp/lib/LibeventReactor.php

394 lines
12 KiB
PHP
Raw Normal View History

2013-08-05 22:05:08 +02:00
<?php
namespace Alert;
2014-04-23 05:09:42 +02:00
class LibeventReactor implements SignalReactor {
2013-08-05 22:05:08 +02:00
private $base;
private $watchers = [];
2014-06-11 18:21:46 +02:00
private $lastWatcherId = 0;
private $resolution = 1000;
private $isRunning = false;
private $isGCScheduled = false;
private $garbage = [];
2013-08-05 22:05:08 +02:00
private $gcEvent;
private $stopException;
2014-02-23 22:26:28 +01:00
public function __construct() {
$this->base = event_base_new();
2013-08-05 22:05:08 +02:00
$this->gcEvent = event_new();
event_timer_set($this->gcEvent, [$this, 'collectGarbage']);
event_base_set($this->gcEvent, $this->base);
}
2014-08-06 22:01:24 +02:00
/**
* Start the event reactor and assume program flow control
*
* @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
*/
public function run(callable $onStart = null) {
if ($this->isRunning) {
return;
}
if ($onStart) {
$this->immediately(function() use ($onStart) { $onStart($this); });
2013-08-05 22:05:08 +02:00
}
$this->doRun();
2013-08-05 22:05:08 +02:00
}
2014-08-06 22:01:24 +02:00
/**
* Execute a single event loop iteration
*
* @throws \Exception will throw any uncaught exception encountered during the loop iteration
* @return void
*/
2014-02-23 22:26:28 +01:00
public function tick() {
2013-08-05 22:05:08 +02:00
if (!$this->isRunning) {
$this->doRun(EVLOOP_ONCE | EVLOOP_NONBLOCK);
}
}
private function doRun($flags = 0) {
$this->isRunning = true;
2013-08-05 22:05:08 +02:00
event_base_loop($this->base, $flags);
$this->isRunning = false;
2013-08-05 22:05:08 +02:00
if ($this->stopException) {
$e = $this->stopException;
$this->stopException = null;
2013-08-05 22:05:08 +02:00
throw $e;
}
}
2014-08-06 22:01:24 +02:00
/**
* Stop the event reactor
*
* @return void
*/
2014-02-23 22:26:28 +01:00
public function stop() {
2013-08-05 22:05:08 +02:00
event_base_loopexit($this->base);
}
2014-08-06 22:01:24 +02:00
/**
* Schedule an event to trigger once at the specified time
*
* @param callable $callback Any valid PHP callable
* @param string $timeString Any string that can be parsed by strtotime() and is in the future
* @throws \InvalidArgumentException if $timeString parse fails
* @return int Returns a unique integer watcher ID
*/
2014-02-23 22:26:28 +01:00
public function at(callable $callback, $timeString) {
2013-08-05 22:05:08 +02:00
$now = time();
$executeAt = @strtotime($timeString);
if ($executeAt === false || $executeAt <= $now) {
2013-08-05 22:05:08 +02:00
throw new \InvalidArgumentException(
'Valid future time string (parsable by strtotime()) required'
);
}
$msDelay = ($executeAt - $now) * $this->resolution;
return $this->once($callback, $msDelay);
2013-08-05 22:05:08 +02:00
}
2014-08-06 22:01:24 +02:00
/**
* Schedule a callback for immediate invocation in the next event loop iteration
*
* @param callable $callback Any valid PHP callable
* @return int Returns a unique integer watcher ID
*/
2014-02-23 22:26:28 +01:00
public function immediately(callable $callback) {
2014-06-11 18:21:46 +02:00
return $this->once($callback, $msDelay = 0);
2013-08-05 22:05:08 +02:00
}
2014-08-06 22:01:24 +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)
* @return int Returns a unique integer watcher ID
*/
2014-06-11 18:21:46 +02:00
public function once(callable $callback, $msDelay) {
$watcherId = $this->lastWatcherId++;
$eventResource = event_new();
2014-06-11 18:21:46 +02:00
$msDelay = ($msDelay > 0) ? ($msDelay * $this->resolution) : 0;
2013-08-05 22:05:08 +02:00
$watcher = new LibeventWatcher;
$watcher->id = $watcherId;
$watcher->eventResource = $eventResource;
2014-06-11 18:21:46 +02:00
$watcher->msDelay = $msDelay;
$watcher->callback = $callback;
$watcher->wrapper = $this->wrapOnceCallback($watcher);
$this->watchers[$watcherId] = $watcher;
event_timer_set($eventResource, $watcher->wrapper);
event_base_set($eventResource, $this->base);
2014-06-11 18:21:46 +02:00
event_add($eventResource, $msDelay);
return $watcherId;
}
private function wrapOnceCallback(LibeventWatcher $watcher) {
$callback = $watcher->callback;
$watcherId = $watcher->id;
return function() use ($callback, $watcherId) {
2013-08-05 22:05:08 +02:00
try {
$callback($watcherId, $this);
2013-08-05 22:05:08 +02:00
$this->cancel($watcherId);
} catch (\Exception $e) {
$this->stopException = $e;
$this->stop();
}
};
}
2013-08-05 22:05:08 +02:00
2014-08-06 22:01:24 +02:00
/**
* Schedule a recurring callback to execute every $interval seconds until cancelled
*
* @param callable $callback Any valid PHP callable
* @param int $msDelay The interval in milliseconds between callback invocations
* @return int Returns a unique integer watcher ID
*/
2014-06-11 18:21:46 +02:00
public function repeat(callable $callback, $msDelay) {
$watcherId = $this->lastWatcherId++;
$msDelay = ($msDelay > 0) ? ($msDelay * $this->resolution) : 0;
$eventResource = event_new();
$watcher = new LibeventWatcher;
$watcher->id = $watcherId;
$watcher->eventResource = $eventResource;
2014-06-11 18:21:46 +02:00
$watcher->msDelay = $msDelay;
$watcher->callback = $callback;
2013-11-27 17:56:29 +01:00
$watcher->wrapper = $this->wrapRepeatingCallback($watcher);
2013-08-05 22:05:08 +02:00
$this->watchers[$watcherId] = $watcher;
event_timer_set($eventResource, $watcher->wrapper);
event_base_set($eventResource, $this->base);
2014-06-11 18:21:46 +02:00
event_add($eventResource, $msDelay);
2013-08-05 22:05:08 +02:00
return $watcherId;
}
private function wrapRepeatingCallback(LibeventWatcher $watcher) {
$callback = $watcher->callback;
$watcherId = $watcher->id;
$eventResource = $watcher->eventResource;
2014-06-11 18:21:46 +02:00
$msDelay = $watcher->msDelay;
2013-08-05 22:05:08 +02:00
2014-06-11 18:21:46 +02:00
return function() use ($callback, $eventResource, $msDelay, $watcherId) {
2013-08-05 22:05:08 +02:00
try {
$callback($watcherId, $this);
2014-06-11 18:21:46 +02:00
event_add($eventResource, $msDelay);
2013-08-05 22:05:08 +02:00
} catch (\Exception $e) {
$this->stopException = $e;
$this->stop();
}
};
}
2014-08-06 22:01:24 +02:00
/**
* 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?
* @return int Returns a unique integer watcher ID
*/
public function onReadable($stream, callable $callback, $enableNow = true) {
2013-08-05 22:05:08 +02:00
return $this->watchIoStream($stream, EV_READ | EV_PERSIST, $callback, $enableNow);
}
2014-08-06 22:01:24 +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?
* @return int Returns a unique integer watcher ID
*/
public function onWritable($stream, callable $callback, $enableNow = true) {
2013-08-05 22:05:08 +02:00
return $this->watchIoStream($stream, EV_WRITE | EV_PERSIST, $callback, $enableNow);
}
private function watchIoStream($stream, $flags, callable $callback, $enableNow) {
2014-06-11 18:21:46 +02:00
$watcherId = $this->lastWatcherId++;
$eventResource = event_new();
$watcher = new LibeventWatcher;
$watcher->id = $watcherId;
$watcher->stream = $stream;
$watcher->callback = $callback;
$watcher->wrapper = $this->wrapStreamCallback($watcher);
$watcher->isEnabled = (bool) $enableNow;
$watcher->eventResource = $eventResource;
2013-08-05 22:05:08 +02:00
$this->watchers[$watcherId] = $watcher;
event_set($eventResource, $stream, $flags, $watcher->wrapper);
event_base_set($eventResource, $this->base);
if ($enableNow) {
event_add($eventResource);
}
return $watcherId;
}
private function wrapStreamCallback(LibeventWatcher $watcher) {
$callback = $watcher->callback;
$watcherId = $watcher->id;
return function($stream) use ($callback, $watcherId) {
2013-08-05 22:05:08 +02:00
try {
$callback($watcherId, $stream, $this);
2013-08-05 22:05:08 +02:00
} catch (\Exception $e) {
$this->stopException = $e;
$this->stop();
}
};
}
2014-08-06 22:01:24 +02:00
/**
* Watch a stream resource for reads or writes (but not both) with additional option flags
*
* @param resource $stream
* @param callable $callback
* @param int $flags A bitmask of watch flags
* @throws \DomainException if no read/write flag specified
* @return int Returns a unique integer watcher ID
*/
2014-08-06 21:52:43 +02:00
public function watchStream($stream, callable $callback, $flags) {
2014-06-11 18:21:46 +02:00
$flags = (int) $flags;
$enableNow = ($flags & self::WATCH_NOW);
2014-06-11 18:21:46 +02:00
if ($flags & self::WATCH_READ) {
2014-06-11 18:21:46 +02:00
return $this->onWritable($stream, $callback, $enableNow);
} elseif ($flags & self::WATCH_WRITE) {
2014-06-11 18:21:46 +02:00
return $this->onWritable($stream, $callback, $enableNow);
} else {
throw new \DomainException(
'Stream watchers must specify either a WATCH_READ or WATCH_WRITE flag'
2014-06-11 18:21:46 +02:00
);
}
}
2014-08-06 22:01:24 +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
* @return int Returns a unique integer watcher ID
*/
public function onSignal($signo, callable $onSignal) {
$signo = (int) $signo;
2014-06-11 18:21:46 +02:00
$watcherId = $this->lastWatcherId++;
2014-04-23 02:45:20 +02:00
$eventResource = event_new();
$watcher = new LibeventWatcher;
$watcher->id = $watcherId;
$watcher->eventResource = $eventResource;
2014-08-06 22:01:24 +02:00
$watcher->callback = $onSignal;
2014-04-23 02:45:20 +02:00
$watcher->wrapper = $this->wrapSignalCallback($watcher);
$this->watchers[$watcherId] = $watcher;
2014-08-06 22:01:24 +02:00
event_set($eventResource, $signo, EV_SIGNAL | EV_PERSIST, $watcher->wrapper);
2014-04-23 02:45:20 +02:00
event_base_set($eventResource, $this->base);
event_add($eventResource);
return $watcherId;
}
private function wrapSignalCallback(LibeventWatcher $watcher) {
$callback = $watcher->callback;
$watcherId = $watcher->id;
return function() use ($callback, $watcherId) {
try {
$callback($watcherId, $this);
} catch (\Exception $e) {
$this->stopException = $e;
$this->stop();
}
};
}
2014-08-06 22:01:24 +02:00
/**
* Cancel an existing watcher
*
* @param int $watcherId
* @return void
*/
2014-02-23 22:26:28 +01:00
public function cancel($watcherId) {
if (!isset($this->watchers[$watcherId])) {
return;
2013-08-05 22:05:08 +02:00
}
$watcher = $this->watchers[$watcherId];
event_del($watcher->eventResource);
$this->garbage[] = $watcher;
$this->scheduleGarbageCollection();
unset($this->watchers[$watcherId]);
2013-08-05 22:05:08 +02:00
}
2014-08-06 22:01:24 +02:00
/**
* Temporarily disable (but don't cancel) an existing timer/stream watcher
*
* @param int $watcherId
* @return void
*/
2014-02-23 22:26:28 +01:00
public function disable($watcherId) {
2013-08-05 22:05:08 +02:00
if (!isset($this->watchers[$watcherId])) {
return;
}
$watcher = $this->watchers[$watcherId];
if ($watcher->isEnabled) {
event_del($watcher->eventResource);
$watcher->isEnabled = false;
2013-08-05 22:05:08 +02:00
}
}
2014-08-06 22:01:24 +02:00
/**
* Enable a disabled timer/stream watcher
*
* @param int $watcherId
* @return void
*/
2014-02-23 22:26:28 +01:00
public function enable($watcherId) {
2013-08-05 22:05:08 +02:00
if (!isset($this->watchers[$watcherId])) {
return;
}
$watcher = $this->watchers[$watcherId];
2013-08-05 22:05:08 +02:00
if (!$watcher->isEnabled) {
2014-06-11 18:21:46 +02:00
event_add($watcher->eventResource, $watcher->msDelay);
$watcher->isEnabled = true;
2013-08-05 22:05:08 +02:00
}
}
private function scheduleGarbageCollection() {
if (!$this->isGCScheduled) {
event_add($this->gcEvent, 0);
$this->isGCScheduled = true;
2013-08-05 22:05:08 +02:00
}
}
private function collectGarbage() {
$this->garbage = [];
$this->isGCScheduled = false;
2013-08-05 22:05:08 +02:00
event_del($this->gcEvent);
}
}