1
0
Fork 0
mirror of https://github.com/DanielnetoDotCom/YouPHPTube synced 2025-10-05 02:39:46 +02:00

Added vendor directory to source control

This commit is contained in:
Daniel 2021-10-05 13:04:32 -03:00
parent 1c3c7b5c26
commit aac245d32f
25330 changed files with 3486213 additions and 69 deletions

View file

@ -0,0 +1,253 @@
<?php
namespace React\EventLoop;
use Ev;
use EvIo;
use EvLoop;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Timer\Timer;
use SplObjectStorage;
/**
* An `ext-ev` based event loop.
*
* This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev),
* that provides an interface to `libev` library.
* `libev` itself supports a number of system-specific backends (epoll, kqueue).
*
* This loop is known to work with PHP 5.4 through PHP 7+.
*
* @see http://php.net/manual/en/book.ev.php
* @see https://bitbucket.org/osmanov/pecl-ev/overview
*/
class ExtEvLoop implements LoopInterface
{
/**
* @var EvLoop
*/
private $loop;
/**
* @var FutureTickQueue
*/
private $futureTickQueue;
/**
* @var SplObjectStorage
*/
private $timers;
/**
* @var EvIo[]
*/
private $readStreams = array();
/**
* @var EvIo[]
*/
private $writeStreams = array();
/**
* @var bool
*/
private $running;
/**
* @var SignalsHandler
*/
private $signals;
/**
* @var \EvSignal[]
*/
private $signalEvents = array();
public function __construct()
{
$this->loop = new EvLoop();
$this->futureTickQueue = new FutureTickQueue();
$this->timers = new SplObjectStorage();
$this->signals = new SignalsHandler();
}
public function addReadStream($stream, $listener)
{
$key = (int)$stream;
if (isset($this->readStreams[$key])) {
return;
}
$callback = $this->getStreamListenerClosure($stream, $listener);
$event = $this->loop->io($stream, Ev::READ, $callback);
$this->readStreams[$key] = $event;
}
/**
* @param resource $stream
* @param callable $listener
*
* @return \Closure
*/
private function getStreamListenerClosure($stream, $listener)
{
return function () use ($stream, $listener) {
\call_user_func($listener, $stream);
};
}
public function addWriteStream($stream, $listener)
{
$key = (int)$stream;
if (isset($this->writeStreams[$key])) {
return;
}
$callback = $this->getStreamListenerClosure($stream, $listener);
$event = $this->loop->io($stream, Ev::WRITE, $callback);
$this->writeStreams[$key] = $event;
}
public function removeReadStream($stream)
{
$key = (int)$stream;
if (!isset($this->readStreams[$key])) {
return;
}
$this->readStreams[$key]->stop();
unset($this->readStreams[$key]);
}
public function removeWriteStream($stream)
{
$key = (int)$stream;
if (!isset($this->writeStreams[$key])) {
return;
}
$this->writeStreams[$key]->stop();
unset($this->writeStreams[$key]);
}
public function addTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, false);
$that = $this;
$timers = $this->timers;
$callback = function () use ($timer, $timers, $that) {
\call_user_func($timer->getCallback(), $timer);
if ($timers->contains($timer)) {
$that->cancelTimer($timer);
}
};
$event = $this->loop->timer($timer->getInterval(), 0.0, $callback);
$this->timers->attach($timer, $event);
return $timer;
}
public function addPeriodicTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, true);
$callback = function () use ($timer) {
\call_user_func($timer->getCallback(), $timer);
};
$event = $this->loop->timer($interval, $interval, $callback);
$this->timers->attach($timer, $event);
return $timer;
}
public function cancelTimer(TimerInterface $timer)
{
if (!isset($this->timers[$timer])) {
return;
}
$event = $this->timers[$timer];
$event->stop();
$this->timers->detach($timer);
}
public function futureTick($listener)
{
$this->futureTickQueue->add($listener);
}
public function run()
{
$this->running = true;
while ($this->running) {
$this->futureTickQueue->tick();
$hasPendingCallbacks = !$this->futureTickQueue->isEmpty();
$wasJustStopped = !$this->running;
$nothingLeftToDo = !$this->readStreams
&& !$this->writeStreams
&& !$this->timers->count()
&& $this->signals->isEmpty();
$flags = Ev::RUN_ONCE;
if ($wasJustStopped || $hasPendingCallbacks) {
$flags |= Ev::RUN_NOWAIT;
} elseif ($nothingLeftToDo) {
break;
}
$this->loop->run($flags);
}
}
public function stop()
{
$this->running = false;
}
public function __destruct()
{
/** @var TimerInterface $timer */
foreach ($this->timers as $timer) {
$this->cancelTimer($timer);
}
foreach ($this->readStreams as $key => $stream) {
$this->removeReadStream($key);
}
foreach ($this->writeStreams as $key => $stream) {
$this->removeWriteStream($key);
}
}
public function addSignal($signal, $listener)
{
$this->signals->add($signal, $listener);
if (!isset($this->signalEvents[$signal])) {
$this->signalEvents[$signal] = $this->loop->signal($signal, function() use ($signal) {
$this->signals->call($signal);
});
}
}
public function removeSignal($signal, $listener)
{
$this->signals->remove($signal, $listener);
if (isset($this->signalEvents[$signal])) {
$this->signalEvents[$signal]->stop();
unset($this->signalEvents[$signal]);
}
}
}

View file

@ -0,0 +1,275 @@
<?php
namespace React\EventLoop;
use BadMethodCallException;
use Event;
use EventBase;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Timer\Timer;
use SplObjectStorage;
/**
* An `ext-event` based event loop.
*
* This uses the [`event` PECL extension](https://pecl.php.net/package/event),
* that provides an interface to `libevent` library.
* `libevent` itself supports a number of system-specific backends (epoll, kqueue).
*
* This loop is known to work with PHP 5.4 through PHP 7+.
*
* @link https://pecl.php.net/package/event
*/
final class ExtEventLoop implements LoopInterface
{
private $eventBase;
private $futureTickQueue;
private $timerCallback;
private $timerEvents;
private $streamCallback;
private $readEvents = array();
private $writeEvents = array();
private $readListeners = array();
private $writeListeners = array();
private $readRefs = array();
private $writeRefs = array();
private $running;
private $signals;
private $signalEvents = array();
public function __construct()
{
if (!\class_exists('EventBase', false)) {
throw new BadMethodCallException('Cannot create ExtEventLoop, ext-event extension missing');
}
// support arbitrary file descriptors and not just sockets
// Windows only has limited file descriptor support, so do not require this (will fail otherwise)
// @link http://www.wangafu.net/~nickm/libevent-book/Ref2_eventbase.html#_setting_up_a_complicated_event_base
$config = new \EventConfig();
if (\DIRECTORY_SEPARATOR !== '\\') {
$config->requireFeatures(\EventConfig::FEATURE_FDS);
}
$this->eventBase = new EventBase($config);
$this->futureTickQueue = new FutureTickQueue();
$this->timerEvents = new SplObjectStorage();
$this->signals = new SignalsHandler();
$this->createTimerCallback();
$this->createStreamCallback();
}
public function __destruct()
{
// explicitly clear all references to Event objects to prevent SEGFAULTs on Windows
foreach ($this->timerEvents as $timer) {
$this->timerEvents->detach($timer);
}
$this->readEvents = array();
$this->writeEvents = array();
}
public function addReadStream($stream, $listener)
{
$key = (int) $stream;
if (isset($this->readListeners[$key])) {
return;
}
$event = new Event($this->eventBase, $stream, Event::PERSIST | Event::READ, $this->streamCallback);
$event->add();
$this->readEvents[$key] = $event;
$this->readListeners[$key] = $listener;
// ext-event does not increase refcount on stream resources for PHP 7+
// manually keep track of stream resource to prevent premature garbage collection
if (\PHP_VERSION_ID >= 70000) {
$this->readRefs[$key] = $stream;
}
}
public function addWriteStream($stream, $listener)
{
$key = (int) $stream;
if (isset($this->writeListeners[$key])) {
return;
}
$event = new Event($this->eventBase, $stream, Event::PERSIST | Event::WRITE, $this->streamCallback);
$event->add();
$this->writeEvents[$key] = $event;
$this->writeListeners[$key] = $listener;
// ext-event does not increase refcount on stream resources for PHP 7+
// manually keep track of stream resource to prevent premature garbage collection
if (\PHP_VERSION_ID >= 70000) {
$this->writeRefs[$key] = $stream;
}
}
public function removeReadStream($stream)
{
$key = (int) $stream;
if (isset($this->readEvents[$key])) {
$this->readEvents[$key]->free();
unset(
$this->readEvents[$key],
$this->readListeners[$key],
$this->readRefs[$key]
);
}
}
public function removeWriteStream($stream)
{
$key = (int) $stream;
if (isset($this->writeEvents[$key])) {
$this->writeEvents[$key]->free();
unset(
$this->writeEvents[$key],
$this->writeListeners[$key],
$this->writeRefs[$key]
);
}
}
public function addTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, false);
$this->scheduleTimer($timer);
return $timer;
}
public function addPeriodicTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, true);
$this->scheduleTimer($timer);
return $timer;
}
public function cancelTimer(TimerInterface $timer)
{
if ($this->timerEvents->contains($timer)) {
$this->timerEvents[$timer]->free();
$this->timerEvents->detach($timer);
}
}
public function futureTick($listener)
{
$this->futureTickQueue->add($listener);
}
public function addSignal($signal, $listener)
{
$this->signals->add($signal, $listener);
if (!isset($this->signalEvents[$signal])) {
$this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, array($this->signals, 'call'));
$this->signalEvents[$signal]->add();
}
}
public function removeSignal($signal, $listener)
{
$this->signals->remove($signal, $listener);
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
$this->signalEvents[$signal]->free();
unset($this->signalEvents[$signal]);
}
}
public function run()
{
$this->running = true;
while ($this->running) {
$this->futureTickQueue->tick();
$flags = EventBase::LOOP_ONCE;
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
$flags |= EventBase::LOOP_NONBLOCK;
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
break;
}
$this->eventBase->loop($flags);
}
}
public function stop()
{
$this->running = false;
}
/**
* Schedule a timer for execution.
*
* @param TimerInterface $timer
*/
private function scheduleTimer(TimerInterface $timer)
{
$flags = Event::TIMEOUT;
if ($timer->isPeriodic()) {
$flags |= Event::PERSIST;
}
$event = new Event($this->eventBase, -1, $flags, $this->timerCallback, $timer);
$this->timerEvents[$timer] = $event;
$event->add($timer->getInterval());
}
/**
* Create a callback used as the target of timer events.
*
* A reference is kept to the callback for the lifetime of the loop
* to prevent "Cannot destroy active lambda function" fatal error from
* the event extension.
*/
private function createTimerCallback()
{
$timers = $this->timerEvents;
$this->timerCallback = function ($_, $__, $timer) use ($timers) {
\call_user_func($timer->getCallback(), $timer);
if (!$timer->isPeriodic() && $timers->contains($timer)) {
$this->cancelTimer($timer);
}
};
}
/**
* Create a callback used as the target of stream events.
*
* A reference is kept to the callback for the lifetime of the loop
* to prevent "Cannot destroy active lambda function" fatal error from
* the event extension.
*/
private function createStreamCallback()
{
$read =& $this->readListeners;
$write =& $this->writeListeners;
$this->streamCallback = function ($stream, $flags) use (&$read, &$write) {
$key = (int) $stream;
if (Event::READ === (Event::READ & $flags) && isset($read[$key])) {
\call_user_func($read[$key], $stream);
}
if (Event::WRITE === (Event::WRITE & $flags) && isset($write[$key])) {
\call_user_func($write[$key], $stream);
}
};
}
}

View file

@ -0,0 +1,201 @@
<?php
namespace React\EventLoop;
use BadMethodCallException;
use libev\EventLoop;
use libev\IOEvent;
use libev\SignalEvent;
use libev\TimerEvent;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Timer\Timer;
use SplObjectStorage;
/**
* [Deprecated] An `ext-libev` based event loop.
*
* This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev),
* that provides an interface to `libev` library.
* `libev` itself supports a number of system-specific backends (epoll, kqueue).
*
* This loop does only work with PHP 5.
* An update for PHP 7 is [unlikely](https://github.com/m4rw3r/php-libev/issues/8)
* to happen any time soon.
*
* @see https://github.com/m4rw3r/php-libev
* @see https://gist.github.com/1688204
* @deprecated 1.2.0, use [`ExtEvLoop`](#extevloop) instead.
*/
final class ExtLibevLoop implements LoopInterface
{
private $loop;
private $futureTickQueue;
private $timerEvents;
private $readEvents = array();
private $writeEvents = array();
private $running;
private $signals;
private $signalEvents = array();
public function __construct()
{
if (!\class_exists('libev\EventLoop', false)) {
throw new BadMethodCallException('Cannot create ExtLibevLoop, ext-libev extension missing');
}
$this->loop = new EventLoop();
$this->futureTickQueue = new FutureTickQueue();
$this->timerEvents = new SplObjectStorage();
$this->signals = new SignalsHandler();
}
public function addReadStream($stream, $listener)
{
if (isset($this->readEvents[(int) $stream])) {
return;
}
$callback = function () use ($stream, $listener) {
\call_user_func($listener, $stream);
};
$event = new IOEvent($callback, $stream, IOEvent::READ);
$this->loop->add($event);
$this->readEvents[(int) $stream] = $event;
}
public function addWriteStream($stream, $listener)
{
if (isset($this->writeEvents[(int) $stream])) {
return;
}
$callback = function () use ($stream, $listener) {
\call_user_func($listener, $stream);
};
$event = new IOEvent($callback, $stream, IOEvent::WRITE);
$this->loop->add($event);
$this->writeEvents[(int) $stream] = $event;
}
public function removeReadStream($stream)
{
$key = (int) $stream;
if (isset($this->readEvents[$key])) {
$this->readEvents[$key]->stop();
$this->loop->remove($this->readEvents[$key]);
unset($this->readEvents[$key]);
}
}
public function removeWriteStream($stream)
{
$key = (int) $stream;
if (isset($this->writeEvents[$key])) {
$this->writeEvents[$key]->stop();
$this->loop->remove($this->writeEvents[$key]);
unset($this->writeEvents[$key]);
}
}
public function addTimer($interval, $callback)
{
$timer = new Timer( $interval, $callback, false);
$that = $this;
$timers = $this->timerEvents;
$callback = function () use ($timer, $timers, $that) {
\call_user_func($timer->getCallback(), $timer);
if ($timers->contains($timer)) {
$that->cancelTimer($timer);
}
};
$event = new TimerEvent($callback, $timer->getInterval());
$this->timerEvents->attach($timer, $event);
$this->loop->add($event);
return $timer;
}
public function addPeriodicTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, true);
$callback = function () use ($timer) {
\call_user_func($timer->getCallback(), $timer);
};
$event = new TimerEvent($callback, $interval, $interval);
$this->timerEvents->attach($timer, $event);
$this->loop->add($event);
return $timer;
}
public function cancelTimer(TimerInterface $timer)
{
if (isset($this->timerEvents[$timer])) {
$this->loop->remove($this->timerEvents[$timer]);
$this->timerEvents->detach($timer);
}
}
public function futureTick($listener)
{
$this->futureTickQueue->add($listener);
}
public function addSignal($signal, $listener)
{
$this->signals->add($signal, $listener);
if (!isset($this->signalEvents[$signal])) {
$signals = $this->signals;
$this->signalEvents[$signal] = new SignalEvent(function () use ($signals, $signal) {
$signals->call($signal);
}, $signal);
$this->loop->add($this->signalEvents[$signal]);
}
}
public function removeSignal($signal, $listener)
{
$this->signals->remove($signal, $listener);
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
$this->signalEvents[$signal]->stop();
$this->loop->remove($this->signalEvents[$signal]);
unset($this->signalEvents[$signal]);
}
}
public function run()
{
$this->running = true;
while ($this->running) {
$this->futureTickQueue->tick();
$flags = EventLoop::RUN_ONCE;
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
$flags |= EventLoop::RUN_NOWAIT;
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
break;
}
$this->loop->run($flags);
}
}
public function stop()
{
$this->running = false;
}
}

View file

@ -0,0 +1,285 @@
<?php
namespace React\EventLoop;
use BadMethodCallException;
use Event;
use EventBase;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Timer\Timer;
use SplObjectStorage;
/**
* [Deprecated] An `ext-libevent` based event loop.
*
* This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent),
* that provides an interface to `libevent` library.
* `libevent` itself supports a number of system-specific backends (epoll, kqueue).
*
* This event loop does only work with PHP 5.
* An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for
* PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s.
* To reiterate: Using this event loop on PHP 7 is not recommended.
* Accordingly, the [`Factory`](#factory) will not try to use this event loop on
* PHP 7.
*
* This event loop is known to trigger a readable listener only if
* the stream *becomes* readable (edge-triggered) and may not trigger if the
* stream has already been readable from the beginning.
* This also implies that a stream may not be recognized as readable when data
* is still left in PHP's internal stream buffers.
* As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
* to disable PHP's internal read buffer in this case.
* See also [`addReadStream()`](#addreadstream) for more details.
*
* @link https://pecl.php.net/package/libevent
* @deprecated 1.2.0, use [`ExtEventLoop`](#exteventloop) instead.
*/
final class ExtLibeventLoop implements LoopInterface
{
/** @internal */
const MICROSECONDS_PER_SECOND = 1000000;
private $eventBase;
private $futureTickQueue;
private $timerCallback;
private $timerEvents;
private $streamCallback;
private $readEvents = array();
private $writeEvents = array();
private $readListeners = array();
private $writeListeners = array();
private $running;
private $signals;
private $signalEvents = array();
public function __construct()
{
if (!\function_exists('event_base_new')) {
throw new BadMethodCallException('Cannot create ExtLibeventLoop, ext-libevent extension missing');
}
$this->eventBase = \event_base_new();
$this->futureTickQueue = new FutureTickQueue();
$this->timerEvents = new SplObjectStorage();
$this->signals = new SignalsHandler();
$this->createTimerCallback();
$this->createStreamCallback();
}
public function addReadStream($stream, $listener)
{
$key = (int) $stream;
if (isset($this->readListeners[$key])) {
return;
}
$event = \event_new();
\event_set($event, $stream, \EV_PERSIST | \EV_READ, $this->streamCallback);
\event_base_set($event, $this->eventBase);
\event_add($event);
$this->readEvents[$key] = $event;
$this->readListeners[$key] = $listener;
}
public function addWriteStream($stream, $listener)
{
$key = (int) $stream;
if (isset($this->writeListeners[$key])) {
return;
}
$event = \event_new();
\event_set($event, $stream, \EV_PERSIST | \EV_WRITE, $this->streamCallback);
\event_base_set($event, $this->eventBase);
\event_add($event);
$this->writeEvents[$key] = $event;
$this->writeListeners[$key] = $listener;
}
public function removeReadStream($stream)
{
$key = (int) $stream;
if (isset($this->readListeners[$key])) {
$event = $this->readEvents[$key];
\event_del($event);
\event_free($event);
unset(
$this->readEvents[$key],
$this->readListeners[$key]
);
}
}
public function removeWriteStream($stream)
{
$key = (int) $stream;
if (isset($this->writeListeners[$key])) {
$event = $this->writeEvents[$key];
\event_del($event);
\event_free($event);
unset(
$this->writeEvents[$key],
$this->writeListeners[$key]
);
}
}
public function addTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, false);
$this->scheduleTimer($timer);
return $timer;
}
public function addPeriodicTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, true);
$this->scheduleTimer($timer);
return $timer;
}
public function cancelTimer(TimerInterface $timer)
{
if ($this->timerEvents->contains($timer)) {
$event = $this->timerEvents[$timer];
\event_del($event);
\event_free($event);
$this->timerEvents->detach($timer);
}
}
public function futureTick($listener)
{
$this->futureTickQueue->add($listener);
}
public function addSignal($signal, $listener)
{
$this->signals->add($signal, $listener);
if (!isset($this->signalEvents[$signal])) {
$this->signalEvents[$signal] = \event_new();
\event_set($this->signalEvents[$signal], $signal, \EV_PERSIST | \EV_SIGNAL, array($this->signals, 'call'));
\event_base_set($this->signalEvents[$signal], $this->eventBase);
\event_add($this->signalEvents[$signal]);
}
}
public function removeSignal($signal, $listener)
{
$this->signals->remove($signal, $listener);
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
\event_del($this->signalEvents[$signal]);
\event_free($this->signalEvents[$signal]);
unset($this->signalEvents[$signal]);
}
}
public function run()
{
$this->running = true;
while ($this->running) {
$this->futureTickQueue->tick();
$flags = \EVLOOP_ONCE;
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
$flags |= \EVLOOP_NONBLOCK;
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
break;
}
\event_base_loop($this->eventBase, $flags);
}
}
public function stop()
{
$this->running = false;
}
/**
* Schedule a timer for execution.
*
* @param TimerInterface $timer
*/
private function scheduleTimer(TimerInterface $timer)
{
$this->timerEvents[$timer] = $event = \event_timer_new();
\event_timer_set($event, $this->timerCallback, $timer);
\event_base_set($event, $this->eventBase);
\event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND);
}
/**
* Create a callback used as the target of timer events.
*
* A reference is kept to the callback for the lifetime of the loop
* to prevent "Cannot destroy active lambda function" fatal error from
* the event extension.
*/
private function createTimerCallback()
{
$that = $this;
$timers = $this->timerEvents;
$this->timerCallback = function ($_, $__, $timer) use ($timers, $that) {
\call_user_func($timer->getCallback(), $timer);
// Timer already cancelled ...
if (!$timers->contains($timer)) {
return;
}
// Reschedule periodic timers ...
if ($timer->isPeriodic()) {
\event_add(
$timers[$timer],
$timer->getInterval() * ExtLibeventLoop::MICROSECONDS_PER_SECOND
);
// Clean-up one shot timers ...
} else {
$that->cancelTimer($timer);
}
};
}
/**
* Create a callback used as the target of stream events.
*
* A reference is kept to the callback for the lifetime of the loop
* to prevent "Cannot destroy active lambda function" fatal error from
* the event extension.
*/
private function createStreamCallback()
{
$read =& $this->readListeners;
$write =& $this->writeListeners;
$this->streamCallback = function ($stream, $flags) use (&$read, &$write) {
$key = (int) $stream;
if (\EV_READ === (\EV_READ & $flags) && isset($read[$key])) {
\call_user_func($read[$key], $stream);
}
if (\EV_WRITE === (\EV_WRITE & $flags) && isset($write[$key])) {
\call_user_func($write[$key], $stream);
}
};
}
}

View file

@ -0,0 +1,342 @@
<?php
namespace React\EventLoop;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Timer\Timer;
use SplObjectStorage;
/**
* An `ext-uv` based event loop.
*
* This loop uses the [`uv` PECL extension](https://pecl.php.net/package/uv),
* that provides an interface to `libuv` library.
* `libuv` itself supports a number of system-specific backends (epoll, kqueue).
*
* This loop is known to work with PHP 7+.
*
* @see https://github.com/bwoebi/php-uv
*/
final class ExtUvLoop implements LoopInterface
{
private $uv;
private $futureTickQueue;
private $timers;
private $streamEvents = array();
private $readStreams = array();
private $writeStreams = array();
private $running;
private $signals;
private $signalEvents = array();
private $streamListener;
public function __construct()
{
if (!\function_exists('uv_loop_new')) {
throw new \BadMethodCallException('Cannot create LibUvLoop, ext-uv extension missing');
}
$this->uv = \uv_loop_new();
$this->futureTickQueue = new FutureTickQueue();
$this->timers = new SplObjectStorage();
$this->streamListener = $this->createStreamListener();
$this->signals = new SignalsHandler();
}
/**
* Returns the underlying ext-uv event loop. (Internal ReactPHP use only.)
*
* @internal
*
* @return resource
*/
public function getUvLoop()
{
return $this->uv;
}
/**
* {@inheritdoc}
*/
public function addReadStream($stream, $listener)
{
if (isset($this->readStreams[(int) $stream])) {
return;
}
$this->readStreams[(int) $stream] = $listener;
$this->addStream($stream);
}
/**
* {@inheritdoc}
*/
public function addWriteStream($stream, $listener)
{
if (isset($this->writeStreams[(int) $stream])) {
return;
}
$this->writeStreams[(int) $stream] = $listener;
$this->addStream($stream);
}
/**
* {@inheritdoc}
*/
public function removeReadStream($stream)
{
if (!isset($this->streamEvents[(int) $stream])) {
return;
}
unset($this->readStreams[(int) $stream]);
$this->removeStream($stream);
}
/**
* {@inheritdoc}
*/
public function removeWriteStream($stream)
{
if (!isset($this->streamEvents[(int) $stream])) {
return;
}
unset($this->writeStreams[(int) $stream]);
$this->removeStream($stream);
}
/**
* {@inheritdoc}
*/
public function addTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, false);
$that = $this;
$timers = $this->timers;
$callback = function () use ($timer, $timers, $that) {
\call_user_func($timer->getCallback(), $timer);
if ($timers->contains($timer)) {
$that->cancelTimer($timer);
}
};
$event = \uv_timer_init($this->uv);
$this->timers->attach($timer, $event);
\uv_timer_start(
$event,
$this->convertFloatSecondsToMilliseconds($interval),
0,
$callback
);
return $timer;
}
/**
* {@inheritdoc}
*/
public function addPeriodicTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, true);
$callback = function () use ($timer) {
\call_user_func($timer->getCallback(), $timer);
};
$interval = $this->convertFloatSecondsToMilliseconds($interval);
$event = \uv_timer_init($this->uv);
$this->timers->attach($timer, $event);
\uv_timer_start(
$event,
$interval,
(int) $interval === 0 ? 1 : $interval,
$callback
);
return $timer;
}
/**
* {@inheritdoc}
*/
public function cancelTimer(TimerInterface $timer)
{
if (isset($this->timers[$timer])) {
@\uv_timer_stop($this->timers[$timer]);
$this->timers->detach($timer);
}
}
/**
* {@inheritdoc}
*/
public function futureTick($listener)
{
$this->futureTickQueue->add($listener);
}
public function addSignal($signal, $listener)
{
$this->signals->add($signal, $listener);
if (!isset($this->signalEvents[$signal])) {
$signals = $this->signals;
$this->signalEvents[$signal] = \uv_signal_init($this->uv);
\uv_signal_start($this->signalEvents[$signal], function () use ($signals, $signal) {
$signals->call($signal);
}, $signal);
}
}
public function removeSignal($signal, $listener)
{
$this->signals->remove($signal, $listener);
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
\uv_signal_stop($this->signalEvents[$signal]);
unset($this->signalEvents[$signal]);
}
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->running = true;
while ($this->running) {
$this->futureTickQueue->tick();
$hasPendingCallbacks = !$this->futureTickQueue->isEmpty();
$wasJustStopped = !$this->running;
$nothingLeftToDo = !$this->readStreams
&& !$this->writeStreams
&& !$this->timers->count()
&& $this->signals->isEmpty();
// Use UV::RUN_ONCE when there are only I/O events active in the loop and block until one of those triggers,
// otherwise use UV::RUN_NOWAIT.
// @link http://docs.libuv.org/en/v1.x/loop.html#c.uv_run
$flags = \UV::RUN_ONCE;
if ($wasJustStopped || $hasPendingCallbacks) {
$flags = \UV::RUN_NOWAIT;
} elseif ($nothingLeftToDo) {
break;
}
\uv_run($this->uv, $flags);
}
}
/**
* {@inheritdoc}
*/
public function stop()
{
$this->running = false;
}
private function addStream($stream)
{
if (!isset($this->streamEvents[(int) $stream])) {
$this->streamEvents[(int)$stream] = \uv_poll_init_socket($this->uv, $stream);
}
if ($this->streamEvents[(int) $stream] !== false) {
$this->pollStream($stream);
}
}
private function removeStream($stream)
{
if (!isset($this->streamEvents[(int) $stream])) {
return;
}
if (!isset($this->readStreams[(int) $stream])
&& !isset($this->writeStreams[(int) $stream])) {
\uv_poll_stop($this->streamEvents[(int) $stream]);
\uv_close($this->streamEvents[(int) $stream]);
unset($this->streamEvents[(int) $stream]);
return;
}
$this->pollStream($stream);
}
private function pollStream($stream)
{
if (!isset($this->streamEvents[(int) $stream])) {
return;
}
$flags = 0;
if (isset($this->readStreams[(int) $stream])) {
$flags |= \UV::READABLE;
}
if (isset($this->writeStreams[(int) $stream])) {
$flags |= \UV::WRITABLE;
}
\uv_poll_start($this->streamEvents[(int) $stream], $flags, $this->streamListener);
}
/**
* Create a stream listener
*
* @return callable Returns a callback
*/
private function createStreamListener()
{
$callback = function ($event, $status, $events, $stream) {
// libuv automatically stops polling on error, re-enable polling to match other loop implementations
if ($status !== 0) {
$this->pollStream($stream);
// libuv may report no events on error, but this should still invoke stream listeners to report closed connections
// re-enable both readable and writable, correct listeners will be checked below anyway
if ($events === 0) {
$events = \UV::READABLE | \UV::WRITABLE;
}
}
if (isset($this->readStreams[(int) $stream]) && ($events & \UV::READABLE)) {
\call_user_func($this->readStreams[(int) $stream], $stream);
}
if (isset($this->writeStreams[(int) $stream]) && ($events & \UV::WRITABLE)) {
\call_user_func($this->writeStreams[(int) $stream], $stream);
}
};
return $callback;
}
/**
* @param float $interval
* @return int
*/
private function convertFloatSecondsToMilliseconds($interval)
{
if ($interval < 0) {
return 0;
}
$maxValue = (int) (\PHP_INT_MAX / 1000);
$intInterval = (int) $interval;
if (($intInterval <= 0 && $interval > 1) || $intInterval >= $maxValue) {
throw new \InvalidArgumentException(
"Interval overflow, value must be lower than '{$maxValue}', but '{$interval}' passed."
);
}
return (int) \floor($interval * 1000);
}
}

75
vendor/react/event-loop/src/Factory.php vendored Normal file
View file

@ -0,0 +1,75 @@
<?php
namespace React\EventLoop;
/**
* [Deprecated] The `Factory` class exists as a convenient way to pick the best available event loop implementation.
*
* @deprecated 1.2.0 See Loop instead.
* @see Loop
*/
final class Factory
{
/**
* [Deprecated] Creates a new event loop instance
*
* ```php
* // deprecated
* $loop = React\EventLoop\Factory::create();
*
* // new
* $loop = React\EventLoop\Loop::get();
* ```
*
* This method always returns an instance implementing `LoopInterface`,
* the actual event loop implementation is an implementation detail.
*
* This method should usually only be called once at the beginning of the program.
*
* @deprecated 1.2.0 See Loop::get() instead.
* @see Loop::get()
*
* @return LoopInterface
*/
public static function create()
{
$loop = self::construct();
Loop::set($loop);
return $loop;
}
/**
* @internal
* @return LoopInterface
*/
private static function construct()
{
// @codeCoverageIgnoreStart
if (\function_exists('uv_loop_new')) {
// only use ext-uv on PHP 7
return new ExtUvLoop();
}
if (\class_exists('libev\EventLoop', false)) {
return new ExtLibevLoop();
}
if (\class_exists('EvLoop', false)) {
return new ExtEvLoop();
}
if (\class_exists('EventBase', false)) {
return new ExtEventLoop();
}
if (\function_exists('event_base_new') && \PHP_MAJOR_VERSION === 5) {
// only use ext-libevent on PHP 5 for now
return new ExtLibeventLoop();
}
return new StreamSelectLoop();
// @codeCoverageIgnoreEnd
}
}

225
vendor/react/event-loop/src/Loop.php vendored Normal file
View file

@ -0,0 +1,225 @@
<?php
namespace React\EventLoop;
/**
* The `Loop` class exists as a convenient way to get the currently relevant loop
*/
final class Loop
{
/**
* @var LoopInterface
*/
private static $instance;
/** @var bool */
private static $stopped = false;
/**
* Returns the event loop.
* When no loop is set, it will call the factory to create one.
*
* This method always returns an instance implementing `LoopInterface`,
* the actual event loop implementation is an implementation detail.
*
* This method is the preferred way to get the event loop and using
* Factory::create has been deprecated.
*
* @return LoopInterface
*/
public static function get()
{
if (self::$instance instanceof LoopInterface) {
return self::$instance;
}
self::$instance = $loop = Factory::create();
// Automatically run loop at end of program, unless already started or stopped explicitly.
// This is tested using child processes, so coverage is actually 100%, see BinTest.
// @codeCoverageIgnoreStart
$hasRun = false;
$loop->futureTick(function () use (&$hasRun) {
$hasRun = true;
});
$stopped =& self::$stopped;
register_shutdown_function(function () use ($loop, &$hasRun, &$stopped) {
// Don't run if we're coming from a fatal error (uncaught exception).
$error = error_get_last();
if ((isset($error['type']) ? $error['type'] : 0) & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR)) {
return;
}
if (!$hasRun && !$stopped) {
$loop->run();
}
});
// @codeCoverageIgnoreEnd
return self::$instance;
}
/**
* Internal undocumented method, behavior might change or throw in the
* future. Use with caution and at your own risk.
*
* @internal
* @return void
*/
public static function set(LoopInterface $loop)
{
self::$instance = $loop;
}
/**
* [Advanced] Register a listener to be notified when a stream is ready to read.
*
* @param resource $stream
* @param callable $listener
* @return void
* @throws \Exception
* @see LoopInterface::addReadStream()
*/
public static function addReadStream($stream, $listener)
{
self::get()->addReadStream($stream, $listener);
}
/**
* [Advanced] Register a listener to be notified when a stream is ready to write.
*
* @param resource $stream
* @param callable $listener
* @return void
* @throws \Exception
* @see LoopInterface::addWriteStream()
*/
public static function addWriteStream($stream, $listener)
{
self::get()->addWriteStream($stream, $listener);
}
/**
* Remove the read event listener for the given stream.
*
* @param resource $stream
* @return void
* @see LoopInterface::removeReadStream()
*/
public static function removeReadStream($stream)
{
self::get()->removeReadStream($stream);
}
/**
* Remove the write event listener for the given stream.
*
* @param resource $stream
* @return void
* @see LoopInterface::removeWriteStream()
*/
public static function removeWriteStream($stream)
{
self::get()->removeWriteStream($stream);
}
/**
* Enqueue a callback to be invoked once after the given interval.
*
* @param float $interval
* @param callable $callback
* @return TimerInterface
* @see LoopInterface::addTimer()
*/
public static function addTimer($interval, $callback)
{
return self::get()->addTimer($interval, $callback);
}
/**
* Enqueue a callback to be invoked repeatedly after the given interval.
*
* @param float $interval
* @param callable $callback
* @return TimerInterface
* @see LoopInterface::addPeriodicTimer()
*/
public static function addPeriodicTimer($interval, $callback)
{
return self::get()->addPeriodicTimer($interval, $callback);
}
/**
* Cancel a pending timer.
*
* @param TimerInterface $timer
* @return void
* @see LoopInterface::cancelTimer()
*/
public static function cancelTimer(TimerInterface $timer)
{
return self::get()->cancelTimer($timer);
}
/**
* Schedule a callback to be invoked on a future tick of the event loop.
*
* @param callable $listener
* @return void
* @see LoopInterface::futureTick()
*/
public static function futureTick($listener)
{
self::get()->futureTick($listener);
}
/**
* Register a listener to be notified when a signal has been caught by this process.
*
* @param int $signal
* @param callable $listener
* @return void
* @see LoopInterface::addSignal()
*/
public static function addSignal($signal, $listener)
{
self::get()->addSignal($signal, $listener);
}
/**
* Removes a previously added signal listener.
*
* @param int $signal
* @param callable $listener
* @return void
* @see LoopInterface::removeSignal()
*/
public static function removeSignal($signal, $listener)
{
self::get()->removeSignal($signal, $listener);
}
/**
* Run the event loop until there are no more tasks to perform.
*
* @return void
* @see LoopInterface::run()
*/
public static function run()
{
self::get()->run();
}
/**
* Instruct a running event loop to stop.
*
* @return void
* @see LoopInterface::stop()
*/
public static function stop()
{
self::$stopped = true;
self::get()->stop();
}
}

View file

@ -0,0 +1,463 @@
<?php
namespace React\EventLoop;
interface LoopInterface
{
/**
* [Advanced] Register a listener to be notified when a stream is ready to read.
*
* Note that this low-level API is considered advanced usage.
* Most use cases should probably use the higher-level
* [readable Stream API](https://github.com/reactphp/stream#readablestreaminterface)
* instead.
*
* The first parameter MUST be a valid stream resource that supports
* checking whether it is ready to read by this loop implementation.
* A single stream resource MUST NOT be added more than once.
* Instead, either call [`removeReadStream()`](#removereadstream) first or
* react to this event with a single listener and then dispatch from this
* listener. This method MAY throw an `Exception` if the given resource type
* is not supported by this loop implementation.
*
* The listener callback function MUST be able to accept a single parameter,
* the stream resource added by this method or you MAY use a function which
* has no parameters at all.
*
* The listener callback function MUST NOT throw an `Exception`.
* The return value of the listener callback function will be ignored and has
* no effect, so for performance reasons you're recommended to not return
* any excessive data structures.
*
* If you want to access any variables within your callback function, you
* can bind arbitrary data to a callback closure like this:
*
* ```php
* $loop->addReadStream($stream, function ($stream) use ($name) {
* echo $name . ' said: ' . fread($stream);
* });
* ```
*
* See also [example #11](examples).
*
* You can invoke [`removeReadStream()`](#removereadstream) to remove the
* read event listener for this stream.
*
* The execution order of listeners when multiple streams become ready at
* the same time is not guaranteed.
*
* @param resource $stream The PHP stream resource to check.
* @param callable $listener Invoked when the stream is ready.
* @throws \Exception if the given resource type is not supported by this loop implementation
* @see self::removeReadStream()
*/
public function addReadStream($stream, $listener);
/**
* [Advanced] Register a listener to be notified when a stream is ready to write.
*
* Note that this low-level API is considered advanced usage.
* Most use cases should probably use the higher-level
* [writable Stream API](https://github.com/reactphp/stream#writablestreaminterface)
* instead.
*
* The first parameter MUST be a valid stream resource that supports
* checking whether it is ready to write by this loop implementation.
* A single stream resource MUST NOT be added more than once.
* Instead, either call [`removeWriteStream()`](#removewritestream) first or
* react to this event with a single listener and then dispatch from this
* listener. This method MAY throw an `Exception` if the given resource type
* is not supported by this loop implementation.
*
* The listener callback function MUST be able to accept a single parameter,
* the stream resource added by this method or you MAY use a function which
* has no parameters at all.
*
* The listener callback function MUST NOT throw an `Exception`.
* The return value of the listener callback function will be ignored and has
* no effect, so for performance reasons you're recommended to not return
* any excessive data structures.
*
* If you want to access any variables within your callback function, you
* can bind arbitrary data to a callback closure like this:
*
* ```php
* $loop->addWriteStream($stream, function ($stream) use ($name) {
* fwrite($stream, 'Hello ' . $name);
* });
* ```
*
* See also [example #12](examples).
*
* You can invoke [`removeWriteStream()`](#removewritestream) to remove the
* write event listener for this stream.
*
* The execution order of listeners when multiple streams become ready at
* the same time is not guaranteed.
*
* Some event loop implementations are known to only trigger the listener if
* the stream *becomes* readable (edge-triggered) and may not trigger if the
* stream has already been readable from the beginning.
* This also implies that a stream may not be recognized as readable when data
* is still left in PHP's internal stream buffers.
* As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
* to disable PHP's internal read buffer in this case.
*
* @param resource $stream The PHP stream resource to check.
* @param callable $listener Invoked when the stream is ready.
* @throws \Exception if the given resource type is not supported by this loop implementation
* @see self::removeWriteStream()
*/
public function addWriteStream($stream, $listener);
/**
* Remove the read event listener for the given stream.
*
* Removing a stream from the loop that has already been removed or trying
* to remove a stream that was never added or is invalid has no effect.
*
* @param resource $stream The PHP stream resource.
*/
public function removeReadStream($stream);
/**
* Remove the write event listener for the given stream.
*
* Removing a stream from the loop that has already been removed or trying
* to remove a stream that was never added or is invalid has no effect.
*
* @param resource $stream The PHP stream resource.
*/
public function removeWriteStream($stream);
/**
* Enqueue a callback to be invoked once after the given interval.
*
* The timer callback function MUST be able to accept a single parameter,
* the timer instance as also returned by this method or you MAY use a
* function which has no parameters at all.
*
* The timer callback function MUST NOT throw an `Exception`.
* The return value of the timer callback function will be ignored and has
* no effect, so for performance reasons you're recommended to not return
* any excessive data structures.
*
* Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure
* the callback will be invoked only once after the given interval.
* You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer.
*
* ```php
* $loop->addTimer(0.8, function () {
* echo 'world!' . PHP_EOL;
* });
*
* $loop->addTimer(0.3, function () {
* echo 'hello ';
* });
* ```
*
* See also [example #1](examples).
*
* If you want to access any variables within your callback function, you
* can bind arbitrary data to a callback closure like this:
*
* ```php
* function hello($name, LoopInterface $loop)
* {
* $loop->addTimer(1.0, function () use ($name) {
* echo "hello $name\n";
* });
* }
*
* hello('Tester', $loop);
* ```
*
* This interface does not enforce any particular timer resolution, so
* special care may have to be taken if you rely on very high precision with
* millisecond accuracy or below. Event loop implementations SHOULD work on
* a best effort basis and SHOULD provide at least millisecond accuracy
* unless otherwise noted. Many existing event loop implementations are
* known to provide microsecond accuracy, but it's generally not recommended
* to rely on this high precision.
*
* Similarly, the execution order of timers scheduled to execute at the
* same time (within its possible accuracy) is not guaranteed.
*
* This interface suggests that event loop implementations SHOULD use a
* monotonic time source if available. Given that a monotonic time source is
* only available as of PHP 7.3 by default, event loop implementations MAY
* fall back to using wall-clock time.
* While this does not affect many common use cases, this is an important
* distinction for programs that rely on a high time precision or on systems
* that are subject to discontinuous time adjustments (time jumps).
* This means that if you schedule a timer to trigger in 30s and then adjust
* your system time forward by 20s, the timer SHOULD still trigger in 30s.
* See also [event loop implementations](#loop-implementations) for more details.
*
* @param int|float $interval The number of seconds to wait before execution.
* @param callable $callback The callback to invoke.
*
* @return TimerInterface
*/
public function addTimer($interval, $callback);
/**
* Enqueue a callback to be invoked repeatedly after the given interval.
*
* The timer callback function MUST be able to accept a single parameter,
* the timer instance as also returned by this method or you MAY use a
* function which has no parameters at all.
*
* The timer callback function MUST NOT throw an `Exception`.
* The return value of the timer callback function will be ignored and has
* no effect, so for performance reasons you're recommended to not return
* any excessive data structures.
*
* Unlike [`addTimer()`](#addtimer), this method will ensure the the
* callback will be invoked infinitely after the given interval or until you
* invoke [`cancelTimer`](#canceltimer).
*
* ```php
* $timer = $loop->addPeriodicTimer(0.1, function () {
* echo 'tick!' . PHP_EOL;
* });
*
* $loop->addTimer(1.0, function () use ($loop, $timer) {
* $loop->cancelTimer($timer);
* echo 'Done' . PHP_EOL;
* });
* ```
*
* See also [example #2](examples).
*
* If you want to limit the number of executions, you can bind
* arbitrary data to a callback closure like this:
*
* ```php
* function hello($name, LoopInterface $loop)
* {
* $n = 3;
* $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) {
* if ($n > 0) {
* --$n;
* echo "hello $name\n";
* } else {
* $loop->cancelTimer($timer);
* }
* });
* }
*
* hello('Tester', $loop);
* ```
*
* This interface does not enforce any particular timer resolution, so
* special care may have to be taken if you rely on very high precision with
* millisecond accuracy or below. Event loop implementations SHOULD work on
* a best effort basis and SHOULD provide at least millisecond accuracy
* unless otherwise noted. Many existing event loop implementations are
* known to provide microsecond accuracy, but it's generally not recommended
* to rely on this high precision.
*
* Similarly, the execution order of timers scheduled to execute at the
* same time (within its possible accuracy) is not guaranteed.
*
* This interface suggests that event loop implementations SHOULD use a
* monotonic time source if available. Given that a monotonic time source is
* only available as of PHP 7.3 by default, event loop implementations MAY
* fall back to using wall-clock time.
* While this does not affect many common use cases, this is an important
* distinction for programs that rely on a high time precision or on systems
* that are subject to discontinuous time adjustments (time jumps).
* This means that if you schedule a timer to trigger in 30s and then adjust
* your system time forward by 20s, the timer SHOULD still trigger in 30s.
* See also [event loop implementations](#loop-implementations) for more details.
*
* Additionally, periodic timers may be subject to timer drift due to
* re-scheduling after each invocation. As such, it's generally not
* recommended to rely on this for high precision intervals with millisecond
* accuracy or below.
*
* @param int|float $interval The number of seconds to wait before execution.
* @param callable $callback The callback to invoke.
*
* @return TimerInterface
*/
public function addPeriodicTimer($interval, $callback);
/**
* Cancel a pending timer.
*
* See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples).
*
* Calling this method on a timer instance that has not been added to this
* loop instance or on a timer that has already been cancelled has no effect.
*
* @param TimerInterface $timer The timer to cancel.
*
* @return void
*/
public function cancelTimer(TimerInterface $timer);
/**
* Schedule a callback to be invoked on a future tick of the event loop.
*
* This works very much similar to timers with an interval of zero seconds,
* but does not require the overhead of scheduling a timer queue.
*
* The tick callback function MUST be able to accept zero parameters.
*
* The tick callback function MUST NOT throw an `Exception`.
* The return value of the tick callback function will be ignored and has
* no effect, so for performance reasons you're recommended to not return
* any excessive data structures.
*
* If you want to access any variables within your callback function, you
* can bind arbitrary data to a callback closure like this:
*
* ```php
* function hello($name, LoopInterface $loop)
* {
* $loop->futureTick(function () use ($name) {
* echo "hello $name\n";
* });
* }
*
* hello('Tester', $loop);
* ```
*
* Unlike timers, tick callbacks are guaranteed to be executed in the order
* they are enqueued.
* Also, once a callback is enqueued, there's no way to cancel this operation.
*
* This is often used to break down bigger tasks into smaller steps (a form
* of cooperative multitasking).
*
* ```php
* $loop->futureTick(function () {
* echo 'b';
* });
* $loop->futureTick(function () {
* echo 'c';
* });
* echo 'a';
* ```
*
* See also [example #3](examples).
*
* @param callable $listener The callback to invoke.
*
* @return void
*/
public function futureTick($listener);
/**
* Register a listener to be notified when a signal has been caught by this process.
*
* This is useful to catch user interrupt signals or shutdown signals from
* tools like `supervisor` or `systemd`.
*
* The listener callback function MUST be able to accept a single parameter,
* the signal added by this method or you MAY use a function which
* has no parameters at all.
*
* The listener callback function MUST NOT throw an `Exception`.
* The return value of the listener callback function will be ignored and has
* no effect, so for performance reasons you're recommended to not return
* any excessive data structures.
*
* ```php
* $loop->addSignal(SIGINT, function (int $signal) {
* echo 'Caught user interrupt signal' . PHP_EOL;
* });
* ```
*
* See also [example #4](examples).
*
* Signaling is only available on Unix-like platform, Windows isn't
* supported due to operating system limitations.
* This method may throw a `BadMethodCallException` if signals aren't
* supported on this platform, for example when required extensions are
* missing.
*
* **Note: A listener can only be added once to the same signal, any
* attempts to add it more then once will be ignored.**
*
* @param int $signal
* @param callable $listener
*
* @throws \BadMethodCallException when signals aren't supported on this
* platform, for example when required extensions are missing.
*
* @return void
*/
public function addSignal($signal, $listener);
/**
* Removes a previously added signal listener.
*
* ```php
* $loop->removeSignal(SIGINT, $listener);
* ```
*
* Any attempts to remove listeners that aren't registered will be ignored.
*
* @param int $signal
* @param callable $listener
*
* @return void
*/
public function removeSignal($signal, $listener);
/**
* Run the event loop until there are no more tasks to perform.
*
* For many applications, this method is the only directly visible
* invocation on the event loop.
* As a rule of thumb, it is usally recommended to attach everything to the
* same loop instance and then run the loop once at the bottom end of the
* application.
*
* ```php
* $loop->run();
* ```
*
* This method will keep the loop running until there are no more tasks
* to perform. In other words: This method will block until the last
* timer, stream and/or signal has been removed.
*
* Likewise, it is imperative to ensure the application actually invokes
* this method once. Adding listeners to the loop and missing to actually
* run it will result in the application exiting without actually waiting
* for any of the attached listeners.
*
* This method MUST NOT be called while the loop is already running.
* This method MAY be called more than once after it has explicity been
* [`stop()`ped](#stop) or after it automatically stopped because it
* previously did no longer have anything to do.
*
* @return void
*/
public function run();
/**
* Instruct a running event loop to stop.
*
* This method is considered advanced usage and should be used with care.
* As a rule of thumb, it is usually recommended to let the loop stop
* only automatically when it no longer has anything to do.
*
* This method can be used to explicitly instruct the event loop to stop:
*
* ```php
* $loop->addTimer(3.0, function () use ($loop) {
* $loop->stop();
* });
* ```
*
* Calling this method on a loop instance that is not currently running or
* on a loop instance that has already been stopped has no effect.
*
* @return void
*/
public function stop();
}

View file

@ -0,0 +1,63 @@
<?php
namespace React\EventLoop;
/**
* @internal
*/
final class SignalsHandler
{
private $signals = array();
public function add($signal, $listener)
{
if (!isset($this->signals[$signal])) {
$this->signals[$signal] = array();
}
if (\in_array($listener, $this->signals[$signal])) {
return;
}
$this->signals[$signal][] = $listener;
}
public function remove($signal, $listener)
{
if (!isset($this->signals[$signal])) {
return;
}
$index = \array_search($listener, $this->signals[$signal], true);
unset($this->signals[$signal][$index]);
if (isset($this->signals[$signal]) && \count($this->signals[$signal]) === 0) {
unset($this->signals[$signal]);
}
}
public function call($signal)
{
if (!isset($this->signals[$signal])) {
return;
}
foreach ($this->signals[$signal] as $listener) {
\call_user_func($listener, $signal);
}
}
public function count($signal)
{
if (!isset($this->signals[$signal])) {
return 0;
}
return \count($this->signals[$signal]);
}
public function isEmpty()
{
return !$this->signals;
}
}

View file

@ -0,0 +1,308 @@
<?php
namespace React\EventLoop;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Timer\Timer;
use React\EventLoop\Timer\Timers;
/**
* A `stream_select()` based event loop.
*
* This uses the [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php)
* function and is the only implementation which works out of the box with PHP.
*
* This event loop works out of the box on PHP 5.4 through PHP 7+ and HHVM.
* This means that no installation is required and this library works on all
* platforms and supported PHP versions.
* Accordingly, the [`Factory`](#factory) will use this event loop by default if
* you do not install any of the event loop extensions listed below.
*
* Under the hood, it does a simple `select` system call.
* This system call is limited to the maximum file descriptor number of
* `FD_SETSIZE` (platform dependent, commonly 1024) and scales with `O(m)`
* (`m` being the maximum file descriptor number passed).
* This means that you may run into issues when handling thousands of streams
* concurrently and you may want to look into using one of the alternative
* event loop implementations listed below in this case.
* If your use case is among the many common use cases that involve handling only
* dozens or a few hundred streams at once, then this event loop implementation
* performs really well.
*
* If you want to use signal handling (see also [`addSignal()`](#addsignal) below),
* this event loop implementation requires `ext-pcntl`.
* This extension is only available for Unix-like platforms and does not support
* Windows.
* It is commonly installed as part of many PHP distributions.
* If this extension is missing (or you're running on Windows), signal handling is
* not supported and throws a `BadMethodCallException` instead.
*
* This event loop is known to rely on wall-clock time to schedule future timers
* when using any version before PHP 7.3, because a monotonic time source is
* only available as of PHP 7.3 (`hrtime()`).
* While this does not affect many common use cases, this is an important
* distinction for programs that rely on a high time precision or on systems
* that are subject to discontinuous time adjustments (time jumps).
* This means that if you schedule a timer to trigger in 30s on PHP < 7.3 and
* then adjust your system time forward by 20s, the timer may trigger in 10s.
* See also [`addTimer()`](#addtimer) for more details.
*
* @link https://www.php.net/manual/en/function.stream-select.php
*/
final class StreamSelectLoop implements LoopInterface
{
/** @internal */
const MICROSECONDS_PER_SECOND = 1000000;
private $futureTickQueue;
private $timers;
private $readStreams = array();
private $readListeners = array();
private $writeStreams = array();
private $writeListeners = array();
private $running;
private $pcntl = false;
private $pcntlPoll = false;
private $signals;
public function __construct()
{
$this->futureTickQueue = new FutureTickQueue();
$this->timers = new Timers();
$this->pcntl = \function_exists('pcntl_signal') && \function_exists('pcntl_signal_dispatch');
$this->pcntlPoll = $this->pcntl && !\function_exists('pcntl_async_signals');
$this->signals = new SignalsHandler();
// prefer async signals if available (PHP 7.1+) or fall back to dispatching on each tick
if ($this->pcntl && !$this->pcntlPoll) {
\pcntl_async_signals(true);
}
}
public function addReadStream($stream, $listener)
{
$key = (int) $stream;
if (!isset($this->readStreams[$key])) {
$this->readStreams[$key] = $stream;
$this->readListeners[$key] = $listener;
}
}
public function addWriteStream($stream, $listener)
{
$key = (int) $stream;
if (!isset($this->writeStreams[$key])) {
$this->writeStreams[$key] = $stream;
$this->writeListeners[$key] = $listener;
}
}
public function removeReadStream($stream)
{
$key = (int) $stream;
unset(
$this->readStreams[$key],
$this->readListeners[$key]
);
}
public function removeWriteStream($stream)
{
$key = (int) $stream;
unset(
$this->writeStreams[$key],
$this->writeListeners[$key]
);
}
public function addTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, false);
$this->timers->add($timer);
return $timer;
}
public function addPeriodicTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, true);
$this->timers->add($timer);
return $timer;
}
public function cancelTimer(TimerInterface $timer)
{
$this->timers->cancel($timer);
}
public function futureTick($listener)
{
$this->futureTickQueue->add($listener);
}
public function addSignal($signal, $listener)
{
if ($this->pcntl === false) {
throw new \BadMethodCallException('Event loop feature "signals" isn\'t supported by the "StreamSelectLoop"');
}
$first = $this->signals->count($signal) === 0;
$this->signals->add($signal, $listener);
if ($first) {
\pcntl_signal($signal, array($this->signals, 'call'));
}
}
public function removeSignal($signal, $listener)
{
if (!$this->signals->count($signal)) {
return;
}
$this->signals->remove($signal, $listener);
if ($this->signals->count($signal) === 0) {
\pcntl_signal($signal, \SIG_DFL);
}
}
public function run()
{
$this->running = true;
while ($this->running) {
$this->futureTickQueue->tick();
$this->timers->tick();
// Future-tick queue has pending callbacks ...
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
$timeout = 0;
// There is a pending timer, only block until it is due ...
} elseif ($scheduledAt = $this->timers->getFirst()) {
$timeout = $scheduledAt - $this->timers->getTime();
if ($timeout < 0) {
$timeout = 0;
} else {
// Convert float seconds to int microseconds.
// Ensure we do not exceed maximum integer size, which may
// cause the loop to tick once every ~35min on 32bit systems.
$timeout *= self::MICROSECONDS_PER_SECOND;
$timeout = $timeout > \PHP_INT_MAX ? \PHP_INT_MAX : (int)$timeout;
}
// The only possible event is stream or signal activity, so wait forever ...
} elseif ($this->readStreams || $this->writeStreams || !$this->signals->isEmpty()) {
$timeout = null;
// There's nothing left to do ...
} else {
break;
}
$this->waitForStreamActivity($timeout);
}
}
public function stop()
{
$this->running = false;
}
/**
* Wait/check for stream activity, or until the next timer is due.
*
* @param integer|null $timeout Activity timeout in microseconds, or null to wait forever.
*/
private function waitForStreamActivity($timeout)
{
$read = $this->readStreams;
$write = $this->writeStreams;
$available = $this->streamSelect($read, $write, $timeout);
if ($this->pcntlPoll) {
\pcntl_signal_dispatch();
}
if (false === $available) {
// if a system call has been interrupted,
// we cannot rely on it's outcome
return;
}
foreach ($read as $stream) {
$key = (int) $stream;
if (isset($this->readListeners[$key])) {
\call_user_func($this->readListeners[$key], $stream);
}
}
foreach ($write as $stream) {
$key = (int) $stream;
if (isset($this->writeListeners[$key])) {
\call_user_func($this->writeListeners[$key], $stream);
}
}
}
/**
* Emulate a stream_select() implementation that does not break when passed
* empty stream arrays.
*
* @param array $read An array of read streams to select upon.
* @param array $write An array of write streams to select upon.
* @param int|null $timeout Activity timeout in microseconds, or null to wait forever.
*
* @return int|false The total number of streams that are ready for read/write.
* Can return false if stream_select() is interrupted by a signal.
*/
private function streamSelect(array &$read, array &$write, $timeout)
{
if ($read || $write) {
// We do not usually use or expose the `exceptfds` parameter passed to the underlying `select`.
// However, Windows does not report failed connection attempts in `writefds` passed to `select` like most other platforms.
// Instead, it uses `writefds` only for successful connection attempts and `exceptfds` for failed connection attempts.
// We work around this by adding all sockets that look like a pending connection attempt to `exceptfds` automatically on Windows and merge it back later.
// This ensures the public API matches other loop implementations across all platforms (see also test suite or rather test matrix).
// Lacking better APIs, every write-only socket that has not yet read any data is assumed to be in a pending connection attempt state.
// @link https://docs.microsoft.com/de-de/windows/win32/api/winsock2/nf-winsock2-select
$except = null;
if (\DIRECTORY_SEPARATOR === '\\') {
$except = array();
foreach ($write as $key => $socket) {
if (!isset($read[$key]) && @\ftell($socket) === 0) {
$except[$key] = $socket;
}
}
}
// suppress warnings that occur, when stream_select is interrupted by a signal
$ret = @\stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);
if ($except) {
$write = \array_merge($write, $except);
}
return $ret;
}
if ($timeout > 0) {
\usleep($timeout);
} elseif ($timeout === null) {
// wait forever (we only reach this if we're only awaiting signals)
// this may be interrupted and return earlier when a signal is received
\sleep(PHP_INT_MAX);
}
return 0;
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace React\EventLoop\Tick;
use SplQueue;
/**
* A tick queue implementation that can hold multiple callback functions
*
* This class should only be used internally, see LoopInterface instead.
*
* @see LoopInterface
* @internal
*/
final class FutureTickQueue
{
private $queue;
public function __construct()
{
$this->queue = new SplQueue();
}
/**
* Add a callback to be invoked on a future tick of the event loop.
*
* Callbacks are guaranteed to be executed in the order they are enqueued.
*
* @param callable $listener The callback to invoke.
*/
public function add($listener)
{
$this->queue->enqueue($listener);
}
/**
* Flush the callback queue.
*/
public function tick()
{
// Only invoke as many callbacks as were on the queue when tick() was called.
$count = $this->queue->count();
while ($count--) {
\call_user_func(
$this->queue->dequeue()
);
}
}
/**
* Check if the next tick queue is empty.
*
* @return boolean
*/
public function isEmpty()
{
return $this->queue->isEmpty();
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace React\EventLoop\Timer;
use React\EventLoop\TimerInterface;
/**
* The actual connection implementation for TimerInterface
*
* This class should only be used internally, see TimerInterface instead.
*
* @see TimerInterface
* @internal
*/
final class Timer implements TimerInterface
{
const MIN_INTERVAL = 0.000001;
private $interval;
private $callback;
private $periodic;
/**
* Constructor initializes the fields of the Timer
*
* @param float $interval The interval after which this timer will execute, in seconds
* @param callable $callback The callback that will be executed when this timer elapses
* @param bool $periodic Whether the time is periodic
*/
public function __construct($interval, $callback, $periodic = false)
{
if ($interval < self::MIN_INTERVAL) {
$interval = self::MIN_INTERVAL;
}
$this->interval = (float) $interval;
$this->callback = $callback;
$this->periodic = (bool) $periodic;
}
public function getInterval()
{
return $this->interval;
}
public function getCallback()
{
return $this->callback;
}
public function isPeriodic()
{
return $this->periodic;
}
}

View file

@ -0,0 +1,107 @@
<?php
namespace React\EventLoop\Timer;
use React\EventLoop\TimerInterface;
/**
* A scheduler implementation that can hold multiple timer instances
*
* This class should only be used internally, see TimerInterface instead.
*
* @see TimerInterface
* @internal
*/
final class Timers
{
private $time;
private $timers = array();
private $schedule = array();
private $sorted = true;
private $useHighResolution;
public function __construct()
{
// prefer high-resolution timer, available as of PHP 7.3+
$this->useHighResolution = \function_exists('hrtime');
}
public function updateTime()
{
return $this->time = $this->useHighResolution ? \hrtime(true) * 1e-9 : \microtime(true);
}
public function getTime()
{
return $this->time ?: $this->updateTime();
}
public function add(TimerInterface $timer)
{
$id = \spl_object_hash($timer);
$this->timers[$id] = $timer;
$this->schedule[$id] = $timer->getInterval() + $this->updateTime();
$this->sorted = false;
}
public function contains(TimerInterface $timer)
{
return isset($this->timers[\spl_object_hash($timer)]);
}
public function cancel(TimerInterface $timer)
{
$id = \spl_object_hash($timer);
unset($this->timers[$id], $this->schedule[$id]);
}
public function getFirst()
{
// ensure timers are sorted to simply accessing next (first) one
if (!$this->sorted) {
$this->sorted = true;
\asort($this->schedule);
}
return \reset($this->schedule);
}
public function isEmpty()
{
return \count($this->timers) === 0;
}
public function tick()
{
// ensure timers are sorted so we can execute in order
if (!$this->sorted) {
$this->sorted = true;
\asort($this->schedule);
}
$time = $this->updateTime();
foreach ($this->schedule as $id => $scheduled) {
// schedule is ordered, so loop until first timer that is not scheduled for execution now
if ($scheduled >= $time) {
break;
}
// skip any timers that are removed while we process the current schedule
if (!isset($this->schedule[$id]) || $this->schedule[$id] !== $scheduled) {
continue;
}
$timer = $this->timers[$id];
\call_user_func($timer->getCallback(), $timer);
// re-schedule if this is a periodic timer and it has not been cancelled explicitly already
if ($timer->isPeriodic() && isset($this->timers[$id])) {
$this->schedule[$id] = $timer->getInterval() + $time;
$this->sorted = false;
} else {
unset($this->timers[$id], $this->schedule[$id]);
}
}
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace React\EventLoop;
interface TimerInterface
{
/**
* Get the interval after which this timer will execute, in seconds
*
* @return float
*/
public function getInterval();
/**
* Get the callback that will be executed when this timer elapses
*
* @return callable
*/
public function getCallback();
/**
* Determine whether the time is periodic
*
* @return bool
*/
public function isPeriodic();
}