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

Added vendor directory to source control

This commit is contained in:
Daniel 2021-11-08 13:23:16 -03:00
parent 16534d4217
commit d2589e176f
3049 changed files with 249997 additions and 395005 deletions

View file

@ -1,13 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../vendor/autoload.php';
Loop::addTimer(0.8, function () {
echo 'world!' . PHP_EOL;
});
Loop::addTimer(0.3, function () {
echo 'hello ';
});

View file

@ -1,14 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../vendor/autoload.php';
$timer = Loop::addPeriodicTimer(0.1, function () {
echo 'Tick' . PHP_EOL;
});
Loop::addTimer(1.0, function () use ($timer) {
Loop::cancelTimer($timer);
echo 'Done' . PHP_EOL;
});

View file

@ -1,13 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../vendor/autoload.php';
Loop::futureTick(function () {
echo 'b';
});
Loop::futureTick(function () {
echo 'c';
});
echo 'a';

View file

@ -1,17 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../vendor/autoload.php';
if (!defined('SIGINT')) {
fwrite(STDERR, 'Not supported on your platform (ext-pcntl missing or Windows?)' . PHP_EOL);
exit(1);
}
Loop::addSignal(SIGINT, $func = function ($signal) use (&$func) {
echo 'Signal: ', (string)$signal, PHP_EOL;
Loop::removeSignal(SIGINT, $func);
});
echo 'Listening for SIGINT. Use "kill -SIGINT ' . getmypid() . '" or CTRL+C' . PHP_EOL;

View file

@ -1,26 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../vendor/autoload.php';
if (!defined('STDIN') || stream_set_blocking(STDIN, false) !== true) {
fwrite(STDERR, 'ERROR: Unable to set STDIN non-blocking (not CLI or Windows?)' . PHP_EOL);
exit(1);
}
// read everything from STDIN and report number of bytes
// for illustration purposes only, should use react/stream instead
Loop::addReadStream(STDIN, function ($stream) {
$chunk = fread($stream, 64 * 1024);
// reading nothing means we reached EOF
if ($chunk === '') {
Loop::removeReadStream($stream);
stream_set_blocking($stream, true);
fclose($stream);
return;
}
echo strlen($chunk) . ' bytes' . PHP_EOL;
});

View file

@ -1,39 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../vendor/autoload.php';
// data can be given as first argument or defaults to "y"
$data = (isset($argv[1]) ? $argv[1] : 'y') . "\n";
// repeat data X times in order to fill around 200 KB
$data = str_repeat($data, round(200000 / strlen($data)));
if (!defined('STDOUT') || stream_set_blocking(STDOUT, false) !== true) {
fwrite(STDERR, 'ERROR: Unable to set STDOUT non-blocking (not CLI or Windows?)' . PHP_EOL);
exit(1);
}
// write data to STDOUT whenever its write buffer accepts data
// for illustrations purpose only, should use react/stream instead
Loop::addWriteStream(STDOUT, function ($stdout) use (&$data) {
// try to write data
$r = fwrite($stdout, $data);
// nothing could be written despite being writable => closed
if ($r === 0) {
Loop::removeWriteStream($stdout);
fclose($stdout);
stream_set_blocking($stdout, true);
fwrite(STDERR, 'Stopped because STDOUT closed' . PHP_EOL);
return;
}
// implement a very simple ring buffer, unless everything has been written at once:
// everything written in this iteration will be appended for next iteration
if (isset($data[$r])) {
$data = substr($data, $r) . substr($data, 0, $r);
}
});

View file

@ -1,31 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../vendor/autoload.php';
// connect to www.google.com:80 (blocking call!)
// for illustration purposes only, should use react/socket instead
$stream = stream_socket_client('tcp://www.google.com:80');
if (!$stream) {
exit(1);
}
stream_set_blocking($stream, false);
// send HTTP request
fwrite($stream, "GET / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n");
// wait for HTTP response
Loop::addReadStream($stream, function ($stream) {
$chunk = fread($stream, 64 * 1024);
// reading nothing means we reached EOF
if ($chunk === '') {
echo '[END]' . PHP_EOL;
Loop::removeReadStream($stream);
fclose($stream);
return;
}
echo $chunk;
});

View file

@ -1,60 +0,0 @@
<?php
use React\EventLoop\Factory;
use React\EventLoop\Loop;
require __DIR__ . '/../vendor/autoload.php';
// resolve hostname before establishing TCP/IP connection (resolving DNS is still blocking here)
// for illustration purposes only, should use react/socket or react/dns instead!
$ip = gethostbyname('www.google.com');
if (ip2long($ip) === false) {
echo 'Unable to resolve hostname' . PHP_EOL;
exit(1);
}
// establish TCP/IP connection (non-blocking)
// for illustraction purposes only, should use react/socket instead!
$stream = stream_socket_client('tcp://' . $ip . ':80', $errno, $errstr, null, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
if (!$stream) {
exit(1);
}
stream_set_blocking($stream, false);
// print progress every 10ms
echo 'Connecting';
$timer = Loop::addPeriodicTimer(0.01, function () {
echo '.';
});
// wait for connection success/error
Loop::addWriteStream($stream, function ($stream) use ($timer) {
Loop::removeWriteStream($stream);
Loop::cancelTimer($timer);
// check for socket error (connection rejected)
if (stream_socket_get_name($stream, true) === false) {
echo '[unable to connect]' . PHP_EOL;
exit(1);
} else {
echo '[connected]' . PHP_EOL;
}
// send HTTP request
fwrite($stream, "GET / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n");
// wait for HTTP response
Loop::addReadStream($stream, function ($stream) {
$chunk = fread($stream, 64 * 1024);
// reading nothing means we reached EOF
if ($chunk === '') {
echo '[END]' . PHP_EOL;
Loop::removeReadStream($stream);
fclose($stream);
return;
}
echo $chunk;
});
});

View file

@ -1,34 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../vendor/autoload.php';
// start TCP/IP server on localhost:8080
// for illustration purposes only, should use react/socket instead
$server = stream_socket_server('tcp://127.0.0.1:8080');
if (!$server) {
exit(1);
}
stream_set_blocking($server, false);
// wait for incoming connections on server socket
Loop::addReadStream($server, function ($server) {
$conn = stream_socket_accept($server);
$data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n";
Loop::addWriteStream($conn, function ($conn) use (&$data) {
$written = fwrite($conn, $data);
if ($written === strlen($data)) {
fclose($conn);
Loop::removeWriteStream($conn);
} else {
$data = substr($data, $written);
}
});
});
Loop::addPeriodicTimer(5, function () {
$memory = memory_get_usage() / 1024;
$formatted = number_format($memory, 3).'K';
echo "Current memory usage: {$formatted}\n";
});

View file

@ -1,11 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../vendor/autoload.php';
$n = isset($argv[1]) ? (int)$argv[1] : 1000 * 100;
for ($i = 0; $i < $n; ++$i) {
Loop::futureTick(function () { });
}

View file

@ -1,11 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../vendor/autoload.php';
$n = isset($argv[1]) ? (int)$argv[1] : 1000 * 100;
for ($i = 0; $i < $n; ++$i) {
Loop::addTimer(0, function () { });
}

View file

@ -1,18 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../vendor/autoload.php';
$ticks = isset($argv[1]) ? (int)$argv[1] : 1000 * 100;
$tick = function () use (&$tick, &$ticks) {
if ($ticks > 0) {
--$ticks;
//$loop->addTimer(0, $tick);
Loop::futureTick($tick);
} else {
echo 'done';
}
};
$tick();

View file

@ -1,18 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../vendor/autoload.php';
$ticks = isset($argv[1]) ? (int)$argv[1] : 1000 * 100;
$tick = function () use (&$tick, &$ticks) {
if ($ticks > 0) {
--$ticks;
//$loop->futureTick($tick);
Loop::addTimer(0, $tick);
} else {
echo 'done';
}
};
$tick();

View file

@ -1,67 +0,0 @@
<?php
/**
* Run the script indefinitely seconds with the loop from the factory and report every 2 seconds:
* php 95-benchmark-memory.php
* Run the script for 30 seconds with the stream_select loop and report every 10 seconds:
* php 95-benchmark-memory.php -t 30 -l StreamSelect -r 10
*/
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use React\EventLoop\TimerInterface;
require __DIR__ . '/../vendor/autoload.php';
$args = getopt('t:l:r:');
$t = isset($args['t']) ? (int)$args['t'] : 0;
$loop = isset($args['l']) && class_exists('React\EventLoop\\' . $args['l'] . 'Loop') ? 'React\EventLoop\\' . $args['l'] . 'Loop' : Loop::get();
if (!($loop instanceof LoopInterface)) {
Loop::set(new $loop());
}
$r = isset($args['r']) ? (int)$args['r'] : 2;
$runs = 0;
if (5 < $t) {
Loop::addTimer($t, function () {
Loop::stop();
});
}
Loop::addPeriodicTimer(0.001, function () use (&$runs) {
$runs++;
Loop::addPeriodicTimer(1, function (TimerInterface $timer) {
Loop::cancelTimer($timer);
});
});
Loop::addPeriodicTimer($r, function () use (&$runs) {
$kmem = round(memory_get_usage() / 1024);
$kmemReal = round(memory_get_usage(true) / 1024);
echo "Runs:\t\t\t$runs\n";
echo "Memory (internal):\t$kmem KiB\n";
echo "Memory (real):\t\t$kmemReal KiB\n";
echo str_repeat('-', 50), "\n";
});
echo "PHP Version:\t\t", phpversion(), "\n";
echo "Loop\t\t\t", get_class(Loop::get()), "\n";
echo "Time\t\t\t", date('r'), "\n";
echo str_repeat('-', 50), "\n";
$beginTime = time();
Loop::run();
$endTime = time();
$timeTaken = $endTime - $beginTime;
echo "PHP Version:\t\t", phpversion(), "\n";
echo "Loop\t\t\t", get_class(Loop::get()), "\n";
echo "Time\t\t\t", date('r'), "\n";
echo "Time taken\t\t", $timeTaken, " seconds\n";
echo "Runs per second\t\t", round($runs / $timeTaken), "\n";

View file

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- PHPUnit configuration file with new format for PHPUnit 9.3+ -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
cacheResult="false">
<testsuites>
<testsuite name="React test suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<directory>./src/</directory>
</include>
</coverage>
</phpunit>

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- PHPUnit configuration file with old format for PHPUnit 9.2 or older -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/4.8/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true">
<testsuites>
<testsuite name="React test suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src/</directory>
</whitelist>
</filter>
</phpunit>

View file

@ -1,253 +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]);
}
}
}
<?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

@ -1,275 +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);
}
};
}
}
<?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

@ -1,201 +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;
}
}
<?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

@ -1,285 +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);
}
};
}
}
<?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

@ -1,342 +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);
}
}
<?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);
}
}

View file

@ -1,75 +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
}
}
<?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
}
}

View file

@ -1,225 +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();
}
}
<?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

@ -1,463 +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();
}
<?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

@ -1,63 +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;
}
}
<?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

@ -1,308 +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;
}
}
<?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

@ -1,60 +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();
}
}
<?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

@ -1,55 +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;
}
}
<?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

@ -1,107 +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]);
}
}
}
}
<?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

@ -1,27 +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();
}
<?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();
}

View file

@ -1,786 +0,0 @@
<?php
namespace React\Tests\EventLoop;
use React\EventLoop\StreamSelectLoop;
use React\EventLoop\ExtUvLoop;
abstract class AbstractLoopTest extends TestCase
{
/**
* @var \React\EventLoop\LoopInterface
*/
protected $loop;
private $tickTimeout;
const PHP_DEFAULT_CHUNK_SIZE = 8192;
/**
* @before
*/
public function setUpLoop()
{
// It's a timeout, don't set it too low. Travis and other CI systems are slow.
$this->tickTimeout = 0.02;
$this->loop = $this->createLoop();
}
abstract public function createLoop();
public function createSocketPair()
{
$domain = (DIRECTORY_SEPARATOR === '\\') ? STREAM_PF_INET : STREAM_PF_UNIX;
$sockets = stream_socket_pair($domain, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
foreach ($sockets as $socket) {
if (function_exists('stream_set_read_buffer')) {
stream_set_read_buffer($socket, 0);
}
}
return $sockets;
}
public function testAddReadStreamTriggersWhenSocketReceivesData()
{
list ($input, $output) = $this->createSocketPair();
$loop = $this->loop;
$timeout = $loop->addTimer(0.1, function () use ($input, $loop) {
$loop->removeReadStream($input);
});
$called = 0;
$this->loop->addReadStream($input, function () use (&$called, $loop, $input, $timeout) {
++$called;
$loop->removeReadStream($input);
$loop->cancelTimer($timeout);
});
fwrite($output, "foo\n");
$this->loop->run();
$this->assertEquals(1, $called);
}
public function testAddReadStreamTriggersWhenSocketCloses()
{
list ($input, $output) = $this->createSocketPair();
$loop = $this->loop;
$timeout = $loop->addTimer(0.1, function () use ($input, $loop) {
$loop->removeReadStream($input);
});
$called = 0;
$this->loop->addReadStream($input, function () use (&$called, $loop, $input, $timeout) {
++$called;
$loop->removeReadStream($input);
$loop->cancelTimer($timeout);
});
fclose($output);
$this->loop->run();
$this->assertEquals(1, $called);
}
public function testAddWriteStreamTriggersWhenSocketConnectionSucceeds()
{
$server = stream_socket_server('127.0.0.1:0');
$errno = $errstr = null;
$connecting = stream_socket_client(stream_socket_get_name($server, false), $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
$loop = $this->loop;
$timeout = $loop->addTimer(0.1, function () use ($connecting, $loop) {
$loop->removeWriteStream($connecting);
});
$called = 0;
$this->loop->addWriteStream($connecting, function () use (&$called, $loop, $connecting, $timeout) {
++$called;
$loop->removeWriteStream($connecting);
$loop->cancelTimer($timeout);
});
$this->loop->run();
$this->assertEquals(1, $called);
}
public function testAddWriteStreamTriggersWhenSocketConnectionRefused()
{
if (defined('HHVM_VERSION')) {
$this->markTestSkipped('Not supported on HHVM');
}
// first verify the operating system actually refuses the connection and no firewall is in place
// use higher timeout because Windows retires multiple times and has a noticeable delay
// @link https://stackoverflow.com/questions/19440364/why-do-failed-attempts-of-socket-connect-take-1-sec-on-windows
$errno = $errstr = null;
if (@stream_socket_client('127.0.0.1:1', $errno, $errstr, 10.0) !== false || (defined('SOCKET_ECONNREFUSED') && $errno !== SOCKET_ECONNREFUSED)) {
$this->markTestSkipped('Expected host to refuse connection, but got error ' . $errno . ': ' . $errstr);
}
$connecting = stream_socket_client('127.0.0.1:1', $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
$loop = $this->loop;
$timeout = $loop->addTimer(10.0, function () use ($connecting, $loop) {
$loop->removeWriteStream($connecting);
});
$called = 0;
$this->loop->addWriteStream($connecting, function () use (&$called, $loop, $connecting, $timeout) {
++$called;
$loop->removeWriteStream($connecting);
$loop->cancelTimer($timeout);
});
$this->loop->run();
$this->assertEquals(1, $called);
}
public function testAddReadStream()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}
list ($input, $output) = $this->createSocketPair();
$this->loop->addReadStream($input, $this->expectCallableExactly(2));
fwrite($output, "foo\n");
$this->tickLoop($this->loop);
fwrite($output, "bar\n");
$this->tickLoop($this->loop);
}
public function testAddReadStreamIgnoresSecondCallable()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}
list ($input, $output) = $this->createSocketPair();
$this->loop->addReadStream($input, $this->expectCallableExactly(2));
$this->loop->addReadStream($input, $this->expectCallableNever());
fwrite($output, "foo\n");
$this->tickLoop($this->loop);
fwrite($output, "bar\n");
$this->tickLoop($this->loop);
}
public function testAddReadStreamReceivesDataFromStreamReference()
{
$this->received = '';
$this->subAddReadStreamReceivesDataFromStreamReference();
$this->assertEquals('', $this->received);
$this->assertRunFasterThan($this->tickTimeout * 2);
$this->assertEquals('[hello]X', $this->received);
}
/**
* Helper for above test. This happens in another helper method to verify
* the loop keeps track of assigned stream resources (refcount).
*/
private function subAddReadStreamReceivesDataFromStreamReference()
{
list ($input, $output) = $this->createSocketPair();
fwrite($input, 'hello');
fclose($input);
$loop = $this->loop;
$received =& $this->received;
$loop->addReadStream($output, function ($output) use ($loop, &$received) {
$chunk = fread($output, 1024);
if ($chunk === '') {
$received .= 'X';
$loop->removeReadStream($output);
fclose($output);
} else {
$received .= '[' . $chunk . ']';
}
});
}
public function testAddWriteStream()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}
list ($input) = $this->createSocketPair();
$this->loop->addWriteStream($input, $this->expectCallableExactly(2));
$this->tickLoop($this->loop);
$this->tickLoop($this->loop);
}
public function testAddWriteStreamIgnoresSecondCallable()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}
list ($input) = $this->createSocketPair();
$this->loop->addWriteStream($input, $this->expectCallableExactly(2));
$this->loop->addWriteStream($input, $this->expectCallableNever());
$this->tickLoop($this->loop);
$this->tickLoop($this->loop);
}
public function testRemoveReadStreamInstantly()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}
list ($input, $output) = $this->createSocketPair();
$this->loop->addReadStream($input, $this->expectCallableNever());
$this->loop->removeReadStream($input);
fwrite($output, "bar\n");
$this->tickLoop($this->loop);
}
public function testRemoveReadStreamAfterReading()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}
list ($input, $output) = $this->createSocketPair();
$this->loop->addReadStream($input, $this->expectCallableOnce());
fwrite($output, "foo\n");
$this->tickLoop($this->loop);
$this->loop->removeReadStream($input);
fwrite($output, "bar\n");
$this->tickLoop($this->loop);
}
public function testRemoveWriteStreamInstantly()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}
list ($input) = $this->createSocketPair();
$this->loop->addWriteStream($input, $this->expectCallableNever());
$this->loop->removeWriteStream($input);
$this->tickLoop($this->loop);
}
public function testRemoveWriteStreamAfterWriting()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}
list ($input) = $this->createSocketPair();
$this->loop->addWriteStream($input, $this->expectCallableOnce());
$this->tickLoop($this->loop);
$this->loop->removeWriteStream($input);
$this->tickLoop($this->loop);
}
public function testRemoveStreamForReadOnly()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}
list ($input, $output) = $this->createSocketPair();
$this->loop->addReadStream($input, $this->expectCallableNever());
$this->loop->addWriteStream($output, $this->expectCallableOnce());
$this->loop->removeReadStream($input);
fwrite($output, "foo\n");
$this->tickLoop($this->loop);
}
public function testRemoveStreamForWriteOnly()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}
list ($input, $output) = $this->createSocketPair();
fwrite($output, "foo\n");
$this->loop->addReadStream($input, $this->expectCallableOnce());
$this->loop->addWriteStream($output, $this->expectCallableNever());
$this->loop->removeWriteStream($output);
$this->tickLoop($this->loop);
}
public function testRemoveReadAndWriteStreamFromLoopOnceResourceClosesEndsLoop()
{
list($stream, $other) = $this->createSocketPair();
stream_set_blocking($stream, false);
stream_set_blocking($other, false);
// dummy writable handler
$this->loop->addWriteStream($stream, function () { });
// remove stream when the stream is readable (closes)
$loop = $this->loop;
$loop->addReadStream($stream, function ($stream) use ($loop) {
$loop->removeReadStream($stream);
$loop->removeWriteStream($stream);
fclose($stream);
});
// close other side
fclose($other);
$this->assertRunFasterThan($this->tickTimeout);
}
public function testRemoveReadAndWriteStreamFromLoopOnceResourceClosesOnEndOfFileEndsLoop()
{
list($stream, $other) = $this->createSocketPair();
stream_set_blocking($stream, false);
stream_set_blocking($other, false);
// dummy writable handler
$this->loop->addWriteStream($stream, function () { });
// remove stream when the stream is readable (closes)
$loop = $this->loop;
$loop->addReadStream($stream, function ($stream) use ($loop) {
$data = fread($stream, 1024);
if ($data !== '') {
return;
}
$loop->removeReadStream($stream);
$loop->removeWriteStream($stream);
fclose($stream);
});
// send data and close stream
fwrite($other, str_repeat('.', static::PHP_DEFAULT_CHUNK_SIZE));
$this->loop->addTimer(0.01, function () use ($other) {
fclose($other);
});
$this->assertRunFasterThan(0.1);
}
public function testRemoveReadAndWriteStreamFromLoopWithClosingResourceEndsLoop()
{
// get only one part of the pair to ensure the other side will close immediately
list($stream) = $this->createSocketPair();
stream_set_blocking($stream, false);
// dummy writable handler
$this->loop->addWriteStream($stream, function () { });
// remove stream when the stream is readable (closes)
$loop = $this->loop;
$loop->addReadStream($stream, function ($stream) use ($loop) {
$loop->removeReadStream($stream);
$loop->removeWriteStream($stream);
fclose($stream);
});
$this->assertRunFasterThan($this->tickTimeout);
}
public function testRemoveInvalid()
{
list ($stream) = $this->createSocketPair();
// remove a valid stream from the event loop that was never added in the first place
$this->loop->removeReadStream($stream);
$this->loop->removeWriteStream($stream);
$this->assertTrue(true);
}
/** @test */
public function emptyRunShouldSimplyReturn()
{
$this->assertRunFasterThan($this->tickTimeout);
}
/** @test */
public function runShouldReturnWhenNoMoreFds()
{
list ($input, $output) = $this->createSocketPair();
$loop = $this->loop;
$this->loop->addReadStream($input, function ($stream) use ($loop) {
$loop->removeReadStream($stream);
});
fwrite($output, "foo\n");
$this->assertRunFasterThan($this->tickTimeout * 2);
}
/** @test */
public function stopShouldStopRunningLoop()
{
list ($input, $output) = $this->createSocketPair();
$loop = $this->loop;
$this->loop->addReadStream($input, function ($stream) use ($loop) {
$loop->stop();
});
fwrite($output, "foo\n");
$this->assertRunFasterThan($this->tickTimeout * 2);
}
public function testStopShouldPreventRunFromBlocking()
{
$that = $this;
$this->loop->addTimer(
1,
function () use ($that) {
$that->fail('Timer was executed.');
}
);
$loop = $this->loop;
$this->loop->futureTick(
function () use ($loop) {
$loop->stop();
}
);
$this->assertRunFasterThan($this->tickTimeout * 2);
}
public function testIgnoreRemovedCallback()
{
// two independent streams, both should be readable right away
list ($input1, $output1) = $this->createSocketPair();
list ($input2, $output2) = $this->createSocketPair();
$called = false;
$loop = $this->loop;
$loop->addReadStream($input1, function ($stream) use (& $called, $loop, $input2) {
// stream1 is readable, remove stream2 as well => this will invalidate its callback
$loop->removeReadStream($stream);
$loop->removeReadStream($input2);
$called = true;
});
// this callback would have to be called as well, but the first stream already removed us
$that = $this;
$loop->addReadStream($input2, function () use (& $called, $that) {
if ($called) {
$that->fail('Callback 2 must not be called after callback 1 was called');
}
});
fwrite($output1, "foo\n");
fwrite($output2, "foo\n");
$loop->run();
$this->assertTrue($called);
}
public function testFutureTickEventGeneratedByFutureTick()
{
$loop = $this->loop;
$this->loop->futureTick(
function () use ($loop) {
$loop->futureTick(
function () {
echo 'future-tick' . PHP_EOL;
}
);
}
);
$this->expectOutputString('future-tick' . PHP_EOL);
$this->loop->run();
}
public function testFutureTick()
{
$called = false;
$callback = function () use (&$called) {
$called = true;
};
$this->loop->futureTick($callback);
$this->assertFalse($called);
$this->tickLoop($this->loop);
$this->assertTrue($called);
}
public function testFutureTickFiresBeforeIO()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}
list ($stream) = $this->createSocketPair();
$this->loop->addWriteStream(
$stream,
function () {
echo 'stream' . PHP_EOL;
}
);
$this->loop->futureTick(
function () {
echo 'future-tick' . PHP_EOL;
}
);
$this->expectOutputString('future-tick' . PHP_EOL . 'stream' . PHP_EOL);
$this->tickLoop($this->loop);
}
/**
* @depends testFutureTickFiresBeforeIO
*/
public function testRecursiveFutureTick()
{
list ($stream) = $this->createSocketPair();
$loop = $this->loop;
$this->loop->addWriteStream(
$stream,
function () use ($stream, $loop) {
echo 'stream' . PHP_EOL;
$loop->removeWriteStream($stream);
}
);
$this->loop->futureTick(
function () use ($loop) {
echo 'future-tick-1' . PHP_EOL;
$loop->futureTick(
function () {
echo 'future-tick-2' . PHP_EOL;
}
);
}
);
$this->expectOutputString('future-tick-1' . PHP_EOL . 'stream' . PHP_EOL . 'future-tick-2' . PHP_EOL);
$this->loop->run();
}
public function testRunWaitsForFutureTickEvents()
{
list ($stream) = $this->createSocketPair();
$loop = $this->loop;
$this->loop->addWriteStream(
$stream,
function () use ($stream, $loop) {
$loop->removeWriteStream($stream);
$loop->futureTick(
function () {
echo 'future-tick' . PHP_EOL;
}
);
}
);
$this->expectOutputString('future-tick' . PHP_EOL);
$this->loop->run();
}
public function testFutureTickEventGeneratedByTimer()
{
$loop = $this->loop;
$this->loop->addTimer(
0.001,
function () use ($loop) {
$loop->futureTick(
function () {
echo 'future-tick' . PHP_EOL;
}
);
}
);
$this->expectOutputString('future-tick' . PHP_EOL);
$this->loop->run();
}
public function testRemoveSignalNotRegisteredIsNoOp()
{
$this->loop->removeSignal(2, function () { });
$this->assertTrue(true);
}
/**
* @requires extension pcntl
*/
public function testSignal()
{
if (!function_exists('posix_kill') || !function_exists('posix_getpid')) {
$this->markTestSkipped('Signal test skipped because functions "posix_kill" and "posix_getpid" are missing.');
}
$called = false;
$calledShouldNot = true;
$timer = $this->loop->addPeriodicTimer(1, function () {});
$this->loop->addSignal(SIGUSR2, $func2 = function () use (&$calledShouldNot) {
$calledShouldNot = false;
});
$loop = $this->loop;
$this->loop->addSignal(SIGUSR1, $func1 = function () use (&$func1, &$func2, &$called, $timer, $loop) {
$called = true;
$loop->removeSignal(SIGUSR1, $func1);
$loop->removeSignal(SIGUSR2, $func2);
$loop->cancelTimer($timer);
});
$this->loop->futureTick(function () {
posix_kill(posix_getpid(), SIGUSR1);
});
$this->loop->run();
$this->assertTrue($called);
$this->assertTrue($calledShouldNot);
}
/**
* @requires extension pcntl
*/
public function testSignalMultipleUsagesForTheSameListener()
{
$funcCallCount = 0;
$func = function () use (&$funcCallCount) {
$funcCallCount++;
};
$this->loop->addTimer(1, function () {});
$this->loop->addSignal(SIGUSR1, $func);
$this->loop->addSignal(SIGUSR1, $func);
$this->loop->addTimer(0.4, function () {
posix_kill(posix_getpid(), SIGUSR1);
});
$loop = $this->loop;
$this->loop->addTimer(0.9, function () use (&$func, $loop) {
$loop->removeSignal(SIGUSR1, $func);
});
$this->loop->run();
$this->assertSame(1, $funcCallCount);
}
/**
* @requires extension pcntl
*/
public function testSignalsKeepTheLoopRunning()
{
$loop = $this->loop;
$function = function () {};
$this->loop->addSignal(SIGUSR1, $function);
$this->loop->addTimer(1.5, function () use ($function, $loop) {
$loop->removeSignal(SIGUSR1, $function);
$loop->stop();
});
$this->assertRunSlowerThan(1.4);
}
/**
* @requires extension pcntl
*/
public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop()
{
$loop = $this->loop;
$function = function () {};
$this->loop->addSignal(SIGUSR1, $function);
$this->loop->addTimer(1.5, function () use ($function, $loop) {
$loop->removeSignal(SIGUSR1, $function);
});
$this->assertRunFasterThan(1.6);
}
public function testTimerIntervalCanBeFarInFuture()
{
// Maximum interval for ExtUvLoop implementation
$interval = ((int) (PHP_INT_MAX / 1000)) - 1;
$loop = $this->loop;
// start a timer very far in the future
$timer = $this->loop->addTimer($interval, function () { });
$this->loop->futureTick(function () use ($timer, $loop) {
$loop->cancelTimer($timer);
});
$this->assertRunFasterThan($this->tickTimeout);
}
private function assertRunSlowerThan($minInterval)
{
$start = microtime(true);
$this->loop->run();
$end = microtime(true);
$interval = $end - $start;
$this->assertLessThan($interval, $minInterval);
}
private function assertRunFasterThan($maxInterval)
{
$start = microtime(true);
$this->loop->run();
$end = microtime(true);
$interval = $end - $start;
$this->assertLessThan($maxInterval, $interval);
}
}

View file

@ -1,75 +0,0 @@
<?php
namespace React\Tests\EventLoop;
class BinTest extends TestCase
{
/**
* @before
*/
public function setUpBin()
{
if (!defined('PHP_BINARY') || defined('HHVM_VERSION')) {
$this->markTestSkipped('Tests not supported on legacy PHP 5.3 or HHVM');
}
chdir(__DIR__ . '/bin/');
}
public function testExecuteExampleWithoutLoopRunRunsLoopAndExecutesTicks()
{
$output = exec(escapeshellarg(PHP_BINARY) . ' 01-ticks-loop-class.php');
$this->assertEquals('abc', $output);
}
public function testExecuteExampleWithExplicitLoopRunRunsLoopAndExecutesTicks()
{
$output = exec(escapeshellarg(PHP_BINARY) . ' 02-ticks-loop-instance.php');
$this->assertEquals('abc', $output);
}
public function testExecuteExampleWithExplicitLoopRunAndStopRunsLoopAndExecutesTicksUntilStopped()
{
$output = exec(escapeshellarg(PHP_BINARY) . ' 03-ticks-loop-stop.php');
$this->assertEquals('abc', $output);
}
public function testExecuteExampleWithUncaughtExceptionShouldNotRunLoop()
{
$time = microtime(true);
exec(escapeshellarg(PHP_BINARY) . ' 11-uncaught.php 2>/dev/null');
$time = microtime(true) - $time;
$this->assertLessThan(1.0, $time);
}
public function testExecuteExampleWithUndefinedVariableShouldNotRunLoop()
{
$time = microtime(true);
exec(escapeshellarg(PHP_BINARY) . ' 12-undefined.php 2>/dev/null');
$time = microtime(true) - $time;
$this->assertLessThan(1.0, $time);
}
public function testExecuteExampleWithExplicitStopShouldNotRunLoop()
{
$time = microtime(true);
exec(escapeshellarg(PHP_BINARY) . ' 21-stop.php 2>/dev/null');
$time = microtime(true) - $time;
$this->assertLessThan(1.0, $time);
}
public function testExecuteExampleWithExplicitStopInExceptionHandlerShouldNotRunLoop()
{
$time = microtime(true);
exec(escapeshellarg(PHP_BINARY) . ' 22-uncaught-stop.php 2>/dev/null');
$time = microtime(true) - $time;
$this->assertLessThan(1.0, $time);
}
}

View file

@ -1,17 +0,0 @@
<?php
namespace React\Tests\EventLoop;
use React\EventLoop\ExtEvLoop;
class ExtEvLoopTest extends AbstractLoopTest
{
public function createLoop()
{
if (!class_exists('EvLoop')) {
$this->markTestSkipped('ExtEvLoop tests skipped because ext-ev extension is not installed.');
}
return new ExtEvLoop();
}
}

View file

@ -1,84 +0,0 @@
<?php
namespace React\Tests\EventLoop;
use React\EventLoop\ExtEventLoop;
class ExtEventLoopTest extends AbstractLoopTest
{
public function createLoop($readStreamCompatible = false)
{
if ('Linux' === PHP_OS && !extension_loaded('posix')) {
$this->markTestSkipped('libevent tests skipped on linux due to linux epoll issues.');
}
if (!extension_loaded('event')) {
$this->markTestSkipped('ext-event tests skipped because ext-event is not installed.');
}
return new ExtEventLoop();
}
public function createStream()
{
// Use a FIFO on linux to get around lack of support for disk-based file
// descriptors when using the EPOLL back-end.
if ('Linux' === PHP_OS) {
$this->fifoPath = tempnam(sys_get_temp_dir(), 'react-');
unlink($this->fifoPath);
posix_mkfifo($this->fifoPath, 0600);
$stream = fopen($this->fifoPath, 'r+');
// ext-event (as of 1.8.1) does not yet support in-memory temporary
// streams. Setting maxmemory:0 and performing a write forces PHP to
// back this temporary stream with a real file.
//
// This problem is mentioned at https://bugs.php.net/bug.php?id=64652&edit=3
// but remains unresolved (despite that issue being closed).
} else {
$stream = fopen('php://temp/maxmemory:0', 'r+');
fwrite($stream, 'x');
ftruncate($stream, 0);
}
return $stream;
}
public function writeToStream($stream, $content)
{
if ('Linux' !== PHP_OS) {
return parent::writeToStream($stream, $content);
}
fwrite($stream, $content);
}
/**
* @group epoll-readable-error
*/
public function testCanUseReadableStreamWithFeatureFds()
{
if (PHP_VERSION_ID > 70000) {
$this->markTestSkipped('Memory stream not supported');
}
$this->loop = $this->createLoop(true);
$input = fopen('php://temp/maxmemory:0', 'r+');
fwrite($input, 'x');
ftruncate($input, 0);
$this->loop->addReadStream($input, $this->expectCallableExactly(2));
fwrite($input, "foo\n");
$this->tickLoop($this->loop);
fwrite($input, "bar\n");
$this->tickLoop($this->loop);
}
}

View file

@ -1,22 +0,0 @@
<?php
namespace React\Tests\EventLoop;
use React\EventLoop\ExtLibevLoop;
class ExtLibevLoopTest extends AbstractLoopTest
{
public function createLoop()
{
if (!class_exists('libev\EventLoop')) {
$this->markTestSkipped('libev tests skipped because ext-libev is not installed.');
}
return new ExtLibevLoop();
}
public function testLibEvConstructor()
{
$loop = new ExtLibevLoop();
}
}

View file

@ -1,61 +0,0 @@
<?php
namespace React\Tests\EventLoop;
use React\EventLoop\ExtLibeventLoop;
class ExtLibeventLoopTest extends AbstractLoopTest
{
private $fifoPath;
public function createLoop()
{
if ('Linux' === PHP_OS && !extension_loaded('posix')) {
$this->markTestSkipped('libevent tests skipped on linux due to linux epoll issues.');
}
if (!function_exists('event_base_new')) {
$this->markTestSkipped('libevent tests skipped because ext-libevent is not installed.');
}
return new ExtLibeventLoop();
}
/**
* @after
*/
public function tearDownFile()
{
if (file_exists($this->fifoPath)) {
unlink($this->fifoPath);
}
}
public function createStream()
{
if ('Linux' !== PHP_OS) {
return parent::createStream();
}
$this->fifoPath = tempnam(sys_get_temp_dir(), 'react-');
unlink($this->fifoPath);
// Use a FIFO on linux to get around lack of support for disk-based file
// descriptors when using the EPOLL back-end.
posix_mkfifo($this->fifoPath, 0600);
$stream = fopen($this->fifoPath, 'r+');
return $stream;
}
public function writeToStream($stream, $content)
{
if ('Linux' !== PHP_OS) {
return parent::writeToStream($stream, $content);
}
fwrite($stream, $content);
}
}

View file

@ -1,95 +0,0 @@
<?php
namespace React\Tests\EventLoop;
use React\EventLoop\ExtUvLoop;
class ExtUvLoopTest extends AbstractLoopTest
{
public function createLoop()
{
if (!function_exists('uv_loop_new')) {
$this->markTestSkipped('uv tests skipped because ext-uv is not installed.');
}
return new ExtUvLoop();
}
/** @dataProvider intervalProvider */
public function testTimerInterval($interval, $expectedExceptionMessage)
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage($expectedExceptionMessage);
$this->loop
->addTimer(
$interval,
function () {
return 0;
}
);
}
public function intervalProvider()
{
$oversizeInterval = PHP_INT_MAX / 1000;
$maxValue = (int) (PHP_INT_MAX / 1000);
$oneMaxValue = $maxValue + 1;
$tenMaxValue = $maxValue + 10;
$tenMillionsMaxValue = $maxValue + 10000000;
$intMax = PHP_INT_MAX;
$oneIntMax = PHP_INT_MAX + 1;
$tenIntMax = PHP_INT_MAX + 10;
$oneHundredIntMax = PHP_INT_MAX + 100;
$oneThousandIntMax = PHP_INT_MAX + 1000;
$tenMillionsIntMax = PHP_INT_MAX + 10000000;
$tenThousandsTimesIntMax = PHP_INT_MAX * 1000;
return array(
array(
$oversizeInterval,
"Interval overflow, value must be lower than '{$maxValue}', but '{$oversizeInterval}' passed."
),
array(
$oneMaxValue,
"Interval overflow, value must be lower than '{$maxValue}', but '{$oneMaxValue}' passed.",
),
array(
$tenMaxValue,
"Interval overflow, value must be lower than '{$maxValue}', but '{$tenMaxValue}' passed.",
),
array(
$tenMillionsMaxValue,
"Interval overflow, value must be lower than '{$maxValue}', but '{$tenMillionsMaxValue}' passed.",
),
array(
$intMax,
"Interval overflow, value must be lower than '{$maxValue}', but '{$intMax}' passed.",
),
array(
$oneIntMax,
"Interval overflow, value must be lower than '{$maxValue}', but '{$oneIntMax}' passed.",
),
array(
$tenIntMax,
"Interval overflow, value must be lower than '{$maxValue}', but '{$tenIntMax}' passed.",
),
array(
$oneHundredIntMax,
"Interval overflow, value must be lower than '{$maxValue}', but '{$oneHundredIntMax}' passed.",
),
array(
$oneThousandIntMax,
"Interval overflow, value must be lower than '{$maxValue}', but '{$oneThousandIntMax}' passed.",
),
array(
$tenMillionsIntMax,
"Interval overflow, value must be lower than '{$maxValue}', but '{$tenMillionsIntMax}' passed.",
),
array(
$tenThousandsTimesIntMax,
"Interval overflow, value must be lower than '{$maxValue}', but '{$tenThousandsTimesIntMax}' passed.",
),
);
}
}

View file

@ -1,216 +0,0 @@
<?php
namespace React\Tests\EventLoop;
use React\EventLoop\Factory;
use React\EventLoop\Loop;
use ReflectionClass;
final class LoopTest extends TestCase
{
/**
* @dataProvider numberOfTests
*/
public function testFactoryCreateSetsEventLoopOnLoopAccessor()
{
$factoryLoop = Factory::create();
$accessorLoop = Loop::get();
self::assertSame($factoryLoop, $accessorLoop);
}
/**
* @dataProvider numberOfTests
*/
public function testCallingFactoryAfterCallingLoopGetYieldsADifferentInstanceOfTheEventLoop()
{
// Note that this behavior isn't wise and highly advised against. Always used Loop::get.
$accessorLoop = Loop::get();
$factoryLoop = Factory::create();
self::assertNotSame($factoryLoop, $accessorLoop);
}
/**
* @dataProvider numberOfTests
*/
public function testCallingLoopGetShouldAlwaysReturnTheSameEventLoop()
{
self::assertSame(Loop::get(), Loop::get());
}
/**
* Run several tests several times to ensure we reset the loop between tests and code is still behavior as expected.
*
* @return array<array>
*/
public function numberOfTests()
{
return array(array(), array(), array());
}
public function testStaticAddReadStreamCallsAddReadStreamOnLoopInstance()
{
$stream = tmpfile();
$listener = function () { };
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->once())->method('addReadStream')->with($stream, $listener);
Loop::set($loop);
Loop::addReadStream($stream, $listener);
}
public function testStaticAddWriteStreamCallsAddWriteStreamOnLoopInstance()
{
$stream = tmpfile();
$listener = function () { };
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->once())->method('addWriteStream')->with($stream, $listener);
Loop::set($loop);
Loop::addWriteStream($stream, $listener);
}
public function testStaticRemoveReadStreamCallsRemoveReadStreamOnLoopInstance()
{
$stream = tmpfile();
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->once())->method('removeReadStream')->with($stream);
Loop::set($loop);
Loop::removeReadStream($stream);
}
public function testStaticRemoveWriteStreamCallsRemoveWriteStreamOnLoopInstance()
{
$stream = tmpfile();
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->once())->method('removeWriteStream')->with($stream);
Loop::set($loop);
Loop::removeWriteStream($stream);
}
public function testStaticAddTimerCallsAddTimerOnLoopInstanceAndReturnsTimerInstance()
{
$interval = 1.0;
$callback = function () { };
$timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock();
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->once())->method('addTimer')->with($interval, $callback)->willReturn($timer);
Loop::set($loop);
$ret = Loop::addTimer($interval, $callback);
$this->assertSame($timer, $ret);
}
public function testStaticAddPeriodicTimerCallsAddPeriodicTimerOnLoopInstanceAndReturnsTimerInstance()
{
$interval = 1.0;
$callback = function () { };
$timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock();
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->once())->method('addPeriodicTimer')->with($interval, $callback)->willReturn($timer);
Loop::set($loop);
$ret = Loop::addPeriodicTimer($interval, $callback);
$this->assertSame($timer, $ret);
}
public function testStaticCancelTimerCallsCancelTimerOnLoopInstance()
{
$timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock();
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->once())->method('cancelTimer')->with($timer);
Loop::set($loop);
Loop::cancelTimer($timer);
}
public function testStaticFutureTickCallsFutureTickOnLoopInstance()
{
$listener = function () { };
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->once())->method('futureTick')->with($listener);
Loop::set($loop);
Loop::futureTick($listener);
}
public function testStaticAddSignalCallsAddSignalOnLoopInstance()
{
$signal = 1;
$listener = function () { };
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->once())->method('addSignal')->with($signal, $listener);
Loop::set($loop);
Loop::addSignal($signal, $listener);
}
public function testStaticRemoveSignalCallsRemoveSignalOnLoopInstance()
{
$signal = 1;
$listener = function () { };
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->once())->method('removeSignal')->with($signal, $listener);
Loop::set($loop);
Loop::removeSignal($signal, $listener);
}
public function testStaticRunCallsRunOnLoopInstance()
{
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->once())->method('run')->with();
Loop::set($loop);
Loop::run();
}
public function testStaticStopCallsStopOnLoopInstance()
{
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$loop->expects($this->once())->method('stop')->with();
Loop::set($loop);
Loop::stop();
}
/**
* @after
* @before
*/
public function unsetLoopFromLoopAccessor()
{
$ref = new ReflectionClass('\React\EventLoop\Loop');
$prop = $ref->getProperty('instance');
$prop->setAccessible(true);
$prop->setValue(null);
$prop->setAccessible(false);
}
}

View file

@ -1,58 +0,0 @@
<?php
namespace React\Tests\EventLoop;
use React\EventLoop\SignalsHandler;
final class SignalsHandlerTest extends TestCase
{
/**
* @requires extension pcntl
*/
public function testEmittedEventsAndCallHandling()
{
$callCount = 0;
$func = function () use (&$callCount) {
$callCount++;
};
$signals = new SignalsHandler();
$this->assertSame(0, $callCount);
$signals->add(SIGUSR1, $func);
$this->assertSame(0, $callCount);
$signals->add(SIGUSR1, $func);
$this->assertSame(0, $callCount);
$signals->add(SIGUSR1, $func);
$this->assertSame(0, $callCount);
$signals->call(SIGUSR1);
$this->assertSame(1, $callCount);
$signals->add(SIGUSR2, $func);
$this->assertSame(1, $callCount);
$signals->add(SIGUSR2, $func);
$this->assertSame(1, $callCount);
$signals->call(SIGUSR2);
$this->assertSame(2, $callCount);
$signals->remove(SIGUSR2, $func);
$this->assertSame(2, $callCount);
$signals->remove(SIGUSR2, $func);
$this->assertSame(2, $callCount);
$signals->call(SIGUSR2);
$this->assertSame(2, $callCount);
$signals->remove(SIGUSR1, $func);
$this->assertSame(2, $callCount);
$signals->call(SIGUSR1);
$this->assertSame(2, $callCount);
}
}

View file

@ -1,145 +0,0 @@
<?php
namespace React\Tests\EventLoop;
use React\EventLoop\LoopInterface;
use React\EventLoop\StreamSelectLoop;
class StreamSelectLoopTest extends AbstractLoopTest
{
/**
* @after
*/
protected function tearDownSignalHandlers()
{
parent::tearDown();
if (strncmp($this->getName(false), 'testSignal', 10) === 0 && extension_loaded('pcntl')) {
$this->resetSignalHandlers();
}
}
public function createLoop()
{
return new StreamSelectLoop();
}
public function testStreamSelectTimeoutEmulation()
{
$this->loop->addTimer(
0.05,
$this->expectCallableOnce()
);
$start = microtime(true);
$this->loop->run();
$end = microtime(true);
$interval = $end - $start;
$this->assertGreaterThan(0.04, $interval);
}
public function signalProvider()
{
return array(
array('SIGUSR1'),
array('SIGHUP'),
array('SIGTERM'),
);
}
/**
* Test signal interrupt when no stream is attached to the loop
* @dataProvider signalProvider
* @requires extension pcntl
*/
public function testSignalInterruptNoStream($signal)
{
// dispatch signal handler every 10ms for 0.1s
$check = $this->loop->addPeriodicTimer(0.01, function() {
pcntl_signal_dispatch();
});
$loop = $this->loop;
$loop->addTimer(0.1, function () use ($check, $loop) {
$loop->cancelTimer($check);
});
$handled = false;
$this->assertTrue(pcntl_signal(constant($signal), function () use (&$handled) {
$handled = true;
}));
// spawn external process to send signal to current process id
$this->forkSendSignal($signal);
$this->loop->run();
$this->assertTrue($handled);
}
/**
* Test signal interrupt when a stream is attached to the loop
* @dataProvider signalProvider
* @requires extension pcntl
*/
public function testSignalInterruptWithStream($signal)
{
// dispatch signal handler every 10ms
$this->loop->addPeriodicTimer(0.01, function() {
pcntl_signal_dispatch();
});
// add stream to the loop
$loop = $this->loop;
list($writeStream, $readStream) = $this->createSocketPair();
$loop->addReadStream($readStream, function ($stream) use ($loop) {
/** @var $loop LoopInterface */
$read = fgets($stream);
if ($read === "end loop\n") {
$loop->stop();
}
});
$this->loop->addTimer(0.1, function() use ($writeStream) {
fwrite($writeStream, "end loop\n");
});
$handled = false;
$this->assertTrue(pcntl_signal(constant($signal), function () use (&$handled) {
$handled = true;
}));
// spawn external process to send signal to current process id
$this->forkSendSignal($signal);
$this->loop->run();
$this->assertTrue($handled);
}
/**
* reset all signal handlers to default
*/
protected function resetSignalHandlers()
{
foreach($this->signalProvider() as $signal) {
pcntl_signal(constant($signal[0]), SIG_DFL);
}
}
/**
* fork child process to send signal to current process id
*/
protected function forkSendSignal($signal)
{
$currentPid = posix_getpid();
$childPid = pcntl_fork();
if ($childPid == -1) {
$this->fail("Failed to fork child process!");
} else if ($childPid === 0) {
// this is executed in the child process
usleep(20000);
posix_kill($currentPid, constant($signal));
die();
}
}
}

View file

@ -1,59 +0,0 @@
<?php
namespace React\Tests\EventLoop;
use PHPUnit\Framework\TestCase as BaseTestCase;
use React\EventLoop\LoopInterface;
class TestCase extends BaseTestCase
{
protected function expectCallableExactly($amount)
{
$mock = $this->createCallableMock();
$mock
->expects($this->exactly($amount))
->method('__invoke');
return $mock;
}
protected function expectCallableOnce()
{
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke');
return $mock;
}
protected function expectCallableNever()
{
$mock = $this->createCallableMock();
$mock
->expects($this->never())
->method('__invoke');
return $mock;
}
protected function createCallableMock()
{
if (method_exists('PHPUnit\Framework\MockObject\MockBuilder', 'addMethods')) {
// PHPUnit 9+
return $this->getMockBuilder('stdClass')->addMethods(array('__invoke'))->getMock();
} else {
// legacy PHPUnit 4 - PHPUnit 9
return $this->getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock();
}
}
protected function tickLoop(LoopInterface $loop)
{
$loop->futureTick(function () use ($loop) {
$loop->stop();
});
$loop->run();
}
}

View file

@ -1,141 +0,0 @@
<?php
namespace React\Tests\EventLoop\Timer;
use React\EventLoop\LoopInterface;
use React\Tests\EventLoop\TestCase;
abstract class AbstractTimerTest extends TestCase
{
/**
* @return LoopInterface
*/
abstract public function createLoop();
public function testAddTimerReturnsNonPeriodicTimerInstance()
{
$loop = $this->createLoop();
$timer = $loop->addTimer(0.001, $this->expectCallableNever());
$this->assertInstanceOf('React\EventLoop\TimerInterface', $timer);
$this->assertFalse($timer->isPeriodic());
}
public function testAddTimerWillBeInvokedOnceAndBlocksLoopWhenRunning()
{
$loop = $this->createLoop();
$loop->addTimer(0.005, $this->expectCallableOnce());
$start = microtime(true);
$loop->run();
$end = microtime(true);
// 1 invocation should take 5ms (± a few milliseconds due to timer inaccuracies)
// make no strict assumptions about time interval, must at least take 1ms
// and should not take longer than 0.1s for slower loops.
$this->assertGreaterThanOrEqual(0.001, $end - $start);
$this->assertLessThan(0.1, $end - $start);
}
public function testAddPeriodicTimerReturnsPeriodicTimerInstance()
{
$loop = $this->createLoop();
$periodic = $loop->addPeriodicTimer(0.1, $this->expectCallableNever());
$this->assertInstanceOf('React\EventLoop\TimerInterface', $periodic);
$this->assertTrue($periodic->isPeriodic());
}
public function testAddPeriodicTimerWillBeInvokedUntilItIsCancelled()
{
$loop = $this->createLoop();
$periodic = $loop->addPeriodicTimer(0.1, $this->expectCallableExactly(3));
// make no strict assumptions about actual time interval.
// leave some room to ensure this ticks exactly 3 times.
$loop->addTimer(0.350, function () use ($loop, $periodic) {
$loop->cancelTimer($periodic);
});
$loop->run();
}
public function testAddPeriodicTimerWillBeInvokedWithMaximumAccuracyUntilItIsCancelled()
{
$loop = $this->createLoop();
$i = 0;
$periodic = $loop->addPeriodicTimer(0.001, function () use (&$i) {
++$i;
});
$loop->addTimer(0.1, function () use ($loop, $periodic) {
$loop->cancelTimer($periodic);
});
$loop->run();
// make no strict assumptions about number of invocations.
// we know it must be no more than 100 times and should at least be
// invoked 4 times for really slow loops
$this->assertLessThanOrEqual(100, $i);
$this->assertGreaterThanOrEqual(4, $i);
}
public function testAddPeriodicTimerCancelsItself()
{
$loop = $this->createLoop();
$i = 0;
$loop->addPeriodicTimer(0.001, function ($timer) use (&$i, $loop) {
$i++;
if ($i === 5) {
$loop->cancelTimer($timer);
}
});
$start = microtime(true);
$loop->run();
$end = microtime(true);
$this->assertEquals(5, $i);
// 5 invocations should take 5ms (± 1ms due to timer inaccuracies)
// make no strict assumptions about time interval, must at least take 4ms
// and should not take longer than 0.2s for slower loops.
$this->assertGreaterThanOrEqual(0.004, $end - $start);
$this->assertLessThan(0.2, $end - $start);
}
public function testMinimumIntervalOneMicrosecond()
{
$loop = $this->createLoop();
$timer = $loop->addTimer(0, function () {});
$this->assertEquals(0.000001, $timer->getInterval());
}
public function testTimerIntervalBelowZeroRunsImmediately()
{
$loop = $this->createLoop();
$start = 0;
$loop->addTimer(
-1,
function () use (&$start) {
$start = \microtime(true);
}
);
$loop->run();
$end = \microtime(true);
// 1ms should be enough even on slow machines (± 1ms due to timer inaccuracies)
$this->assertLessThan(0.002, $end - $start);
}
}

View file

@ -1,17 +0,0 @@
<?php
namespace React\Tests\EventLoop\Timer;
use React\EventLoop\ExtEvLoop;
class ExtEvTimerTest extends AbstractTimerTest
{
public function createLoop()
{
if (!class_exists('EvLoop')) {
$this->markTestSkipped('ExtEvLoop tests skipped because ext-ev extension is not installed.');
}
return new ExtEvLoop();
}
}

View file

@ -1,17 +0,0 @@
<?php
namespace React\Tests\EventLoop\Timer;
use React\EventLoop\ExtEventLoop;
class ExtEventTimerTest extends AbstractTimerTest
{
public function createLoop()
{
if (!extension_loaded('event')) {
$this->markTestSkipped('ext-event tests skipped because ext-event is not installed.');
}
return new ExtEventLoop();
}
}

View file

@ -1,17 +0,0 @@
<?php
namespace React\Tests\EventLoop\Timer;
use React\EventLoop\ExtLibevLoop;
class ExtLibevTimerTest extends AbstractTimerTest
{
public function createLoop()
{
if (!class_exists('libev\EventLoop')) {
$this->markTestSkipped('libev tests skipped because ext-libev is not installed.');
}
return new ExtLibevLoop();
}
}

View file

@ -1,17 +0,0 @@
<?php
namespace React\Tests\EventLoop\Timer;
use React\EventLoop\ExtLibeventLoop;
class ExtLibeventTimerTest extends AbstractTimerTest
{
public function createLoop()
{
if (!function_exists('event_base_new')) {
$this->markTestSkipped('libevent tests skipped because ext-libevent is not installed.');
}
return new ExtLibeventLoop();
}
}

View file

@ -1,17 +0,0 @@
<?php
namespace React\Tests\EventLoop\Timer;
use React\EventLoop\ExtUvLoop;
class ExtUvTimerTest extends AbstractTimerTest
{
public function createLoop()
{
if (!function_exists('uv_loop_new')) {
$this->markTestSkipped('uv tests skipped because ext-uv is not installed.');
}
return new ExtUvLoop();
}
}

View file

@ -1,13 +0,0 @@
<?php
namespace React\Tests\EventLoop\Timer;
use React\EventLoop\StreamSelectLoop;
class StreamSelectTimerTest extends AbstractTimerTest
{
public function createLoop()
{
return new StreamSelectLoop();
}
}

View file

@ -1,40 +0,0 @@
<?php
namespace React\Tests\EventLoop\Timer;
use React\Tests\EventLoop\TestCase;
use React\EventLoop\Timer\Timer;
use React\EventLoop\Timer\Timers;
class TimersTest extends TestCase
{
public function testBlockedTimer()
{
$timers = new Timers();
$timers->tick();
// simulate a bunch of processing on stream events,
// part of which schedules a future timer...
sleep(1);
$timers->add(new Timer(0.5, function () {
$this->fail("Timer shouldn't be called");
}));
$timers->tick();
$this->assertTrue(true);
}
public function testContains()
{
$timers = new Timers();
$timer1 = new Timer(0.1, function () {});
$timer2 = new Timer(0.1, function () {});
$timers->add($timer1);
self::assertTrue($timers->contains($timer1));
self::assertFalse($timers->contains($timer2));
}
}

View file

@ -1,13 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../../vendor/autoload.php';
Loop::futureTick(function () {
echo 'b';
});
Loop::futureTick(function () {
echo 'c';
});
echo 'a';

View file

@ -1,19 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../../vendor/autoload.php';
$loop = Loop::get();
$loop->futureTick(function () {
echo 'b';
});
$loop->futureTick(function () {
echo 'c';
});
echo 'a';
$loop->run();

View file

@ -1,23 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../../vendor/autoload.php';
$loop = Loop::get();
$loop->futureTick(function () use ($loop) {
echo 'b';
$loop->stop();
$loop->futureTick(function () {
echo 'never';
});
});
echo 'a';
$loop->run();
echo 'c';

View file

@ -1,11 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../../vendor/autoload.php';
Loop::addTimer(10.0, function () {
echo 'never';
});
throw new RuntimeException();

View file

@ -1,11 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../../vendor/autoload.php';
Loop::get()->addTimer(10.0, function () {
echo 'never';
});
$undefined->foo('bar');

View file

@ -1,11 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../../vendor/autoload.php';
Loop::addTimer(10.0, function () {
echo 'never';
});
Loop::stop();

View file

@ -1,16 +0,0 @@
<?php
use React\EventLoop\Loop;
require __DIR__ . '/../../vendor/autoload.php';
Loop::addTimer(10.0, function () {
echo 'never';
});
set_exception_handler(function (Exception $e) {
echo 'Uncaught error occured' . PHP_EOL;
Loop::stop();
});
throw new RuntimeException();