This commit is contained in:
2022-10-23 01:39:27 +02:00
parent 8c17aab483
commit 1929b84685
4130 changed files with 479334 additions and 0 deletions

3
vendor/react/event-loop/.gitignore vendored Executable file
View File

@@ -0,0 +1,3 @@
composer.lock
phpunit.xml
vendor

27
vendor/react/event-loop/.travis.yml vendored Executable file
View File

@@ -0,0 +1,27 @@
language: php
php:
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- hhvm
sudo: false
addons:
apt:
packages:
- libevent-dev # Used by 'event' and 'libevent' PHP extensions
cache:
directories:
- $HOME/.composer/cache/files
install:
- ./travis-init.sh
- composer install
script:
- ./vendor/bin/phpunit --coverage-text

79
vendor/react/event-loop/CHANGELOG.md vendored Executable file
View File

@@ -0,0 +1,79 @@
# Changelog
## 0.4.3 (2017-04-27)
* Bug fix: Bugfix in the usage sample code #57 (@dandelionred)
* Improvement: Remove branch-alias definition #53 (@WyriHaximus)
* Improvement: StreamSelectLoop: Use fresh time so Timers added during stream events are accurate #51 (@andrewminerd)
* Improvement: Avoid deprecation warnings in test suite due to deprecation of getMock() in PHPUnit #68 (@martinschroeder)
* Improvement: Add PHPUnit 4.8 to require-dev #69 (@shaunbramley)
* Improvement: Increase test timeouts for HHVM and unify timeout handling #70 (@clue)
* Improvement: Travis improvements (backported from #74) #75 (@clue)
* Improvement: Test suite now uses socket pairs instead of memory streams #66 (@martinschroeder)
* Improvement: StreamSelectLoop: Test suite uses signal constant names in data provider #67 (@martinschroeder)
* Improvement: ExtEventLoop: No longer suppress all errors #65 (@mamciek)
* Improvement: Readme cleanup #89 (@jsor)
* Improvement: Restructure and improve README #90 (@jsor)
* Bug fix: StreamSelectLoop: Fix erroneous zero-time sleep (backport to 0.4) #94 (@jsor)
## 0.4.2 (2016-03-07)
* Bug fix: No longer error when signals sent to StreamSelectLoop
* Support HHVM and PHP7 (@ondrejmirtes, @cebe)
* Feature: Added support for EventConfig for ExtEventLoop (@steverhoades)
* Bug fix: Fixed an issue loading loop extension libs via autoloader (@czarpino)
## 0.4.1 (2014-04-13)
* Bug fix: null timeout in StreamSelectLoop causing 100% CPU usage (@clue)
* Bug fix: v0.3.4 changes merged for v0.4.1
## 0.3.4 (2014-03-30)
* Changed StreamSelectLoop to use non-blocking behavior on tick() (@astephens25)
## 0.4.0 (2014-02-02)
* Feature: Added `EventLoopInterface::nextTick()`, implemented in all event loops (@jmalloc)
* Feature: Added `EventLoopInterface::futureTick()`, implemented in all event loops (@jmalloc)
* Feature: Added `ExtEventLoop` implementation using pecl/event (@jmalloc)
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
* BC break: New method: `EventLoopInterface::nextTick()`
* BC break: New method: `EventLoopInterface::futureTick()`
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
## 0.3.3 (2013-07-08)
* Bug fix: No error on removing non-existent streams (@clue)
* Bug fix: Do not silently remove feof listeners in `LibEvLoop`
## 0.3.0 (2013-04-14)
* BC break: New timers API (@nrk)
* BC break: Remove check on return value from stream callbacks (@nrk)
## 0.2.7 (2013-01-05)
* Bug fix: Fix libevent timers with PHP 5.3
* Bug fix: Fix libevent timer cancellation (@nrk)
## 0.2.6 (2012-12-26)
* Bug fix: Plug memory issue in libevent timers (@cameronjacobson)
* Bug fix: Correctly pause LibEvLoop on stop()
## 0.2.3 (2012-11-14)
* Feature: LibEvLoop, integration of `php-libev`
## 0.2.0 (2012-09-10)
* Version bump
## 0.1.1 (2012-07-12)
* Version bump
## 0.1.0 (2012-07-11)
* First tagged release

19
vendor/react/event-loop/LICENSE vendored Executable file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2012 Igor Wiedler, Chris Boden
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

143
vendor/react/event-loop/README.md vendored Executable file
View File

@@ -0,0 +1,143 @@
# EventLoop Component
[![Build Status](https://travis-ci.org/reactphp/event-loop.svg?branch=master)](https://travis-ci.org/reactphp/event-loop)
[![Code Climate](https://codeclimate.com/github/reactphp/event-loop/badges/gpa.svg)](https://codeclimate.com/github/reactphp/event-loop)
Event loop abstraction layer that libraries can use for evented I/O.
In order for async based libraries to be interoperable, they need to use the
same event loop. This component provides a common `LoopInterface` that any
library can target. This allows them to be used in the same loop, with one
single `run()` call that is controlled by the user.
**Table of Contents**
* [Quickstart example](#quickstart-example)
* [Usage](#usage)
* [Loop implementations](#loop-implementations)
* [Install](#install)
* [Tests](#tests)
* [License](#license)
## Quickstart example
Here is an async HTTP server built with just the event loop.
```php
$loop = React\EventLoop\Factory::create();
$server = stream_socket_server('tcp://127.0.0.1:8080');
stream_set_blocking($server, 0);
$loop->addReadStream($server, function ($server) use ($loop) {
$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, $loop) {
$written = fwrite($conn, $data);
if ($written === strlen($data)) {
fclose($conn);
$loop->removeStream($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";
});
$loop->run();
```
## Usage
Typical applications use a single event loop which is created at the beginning
and run at the end of the program.
```php
// [1]
$loop = React\EventLoop\Factory::create();
// [2]
$loop->addPeriodicTimer(1, function () {
echo "Tick\n";
});
$stream = new React\Stream\ReadableResourceStream(
fopen('file.txt', 'r'),
$loop
);
// [3]
$loop->run();
```
1. The loop instance is created at the beginning of the program. A convenience
factory `React\EventLoop\Factory::create()` is provided by this library which
picks the best available [loop implementation](#loop-implementations).
2. The loop instance is used directly or passed to library and application code.
In this example, a periodic timer is registered with the event loop which
simply outputs `Tick` every second and a
[readable stream](https://github.com/reactphp/stream#readableresourcestream)
is created by using ReactPHP's
[stream component](https://github.com/reactphp/stream) for demonstration
purposes.
3. The loop is run with a single `$loop->run()` call at the end of the program.
## Loop implementations
In addition to the interface there are the following implementations provided:
* `StreamSelectLoop`: This is the only implementation which works out of the
box with PHP. It does a simple `select` system call. It's not the most
performant of loops, but still does the job quite well.
* `LibEventLoop`: This uses the `libevent` pecl extension. `libevent` itself
supports a number of system-specific backends (epoll, kqueue).
* `LibEvLoop`: This uses the `libev` pecl extension
([github](https://github.com/m4rw3r/php-libev)). It supports the same
backends as libevent.
* `ExtEventLoop`: This uses the `event` pecl extension. It supports the same
backends as libevent.
All of the loops support these features:
* File descriptor polling
* One-off timers
* Periodic timers
* Deferred execution of callbacks
## Install
The recommended way to install this library is [through Composer](http://getcomposer.org).
[New to Composer?](http://getcomposer.org/doc/00-intro.md)
This will install the latest supported version:
```bash
$ composer require react/event-loop
```
## Tests
To run the test suite, you first need to clone this repo and then install all
dependencies [through Composer](http://getcomposer.org):
```bash
$ composer install
```
To run the test suite, go to the project root and run:
```bash
$ php vendor/bin/phpunit
```
## License
MIT, see [LICENSE file](LICENSE).

22
vendor/react/event-loop/composer.json vendored Executable file
View File

@@ -0,0 +1,22 @@
{
"name": "react/event-loop",
"description": "Event loop abstraction layer that libraries can use for evented I/O.",
"keywords": ["event-loop", "asynchronous"],
"license": "MIT",
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "~4.8"
},
"suggest": {
"ext-libevent": ">=0.1.0",
"ext-event": "~1.0",
"ext-libev": "*"
},
"autoload": {
"psr-4": {
"React\\EventLoop\\": "src"
}
}
}

25
vendor/react/event-loop/phpunit.xml.dist vendored Executable file
View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="tests/bootstrap.php"
>
<testsuites>
<testsuite name="React Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src/</directory>
</whitelist>
</filter>
</phpunit>

326
vendor/react/event-loop/src/ExtEventLoop.php vendored Executable file
View File

@@ -0,0 +1,326 @@
<?php
namespace React\EventLoop;
use Event;
use EventBase;
use EventConfig as EventBaseConfig;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Tick\NextTickQueue;
use React\EventLoop\Timer\Timer;
use React\EventLoop\Timer\TimerInterface;
use SplObjectStorage;
/**
* An ext-event based event-loop.
*/
class ExtEventLoop implements LoopInterface
{
private $eventBase;
private $nextTickQueue;
private $futureTickQueue;
private $timerCallback;
private $timerEvents;
private $streamCallback;
private $streamEvents = [];
private $streamFlags = [];
private $readListeners = [];
private $writeListeners = [];
private $running;
public function __construct(EventBaseConfig $config = null)
{
$this->eventBase = new EventBase($config);
$this->nextTickQueue = new NextTickQueue($this);
$this->futureTickQueue = new FutureTickQueue($this);
$this->timerEvents = new SplObjectStorage();
$this->createTimerCallback();
$this->createStreamCallback();
}
/**
* {@inheritdoc}
*/
public function addReadStream($stream, callable $listener)
{
$key = (int) $stream;
if (!isset($this->readListeners[$key])) {
$this->readListeners[$key] = $listener;
$this->subscribeStreamEvent($stream, Event::READ);
}
}
/**
* {@inheritdoc}
*/
public function addWriteStream($stream, callable $listener)
{
$key = (int) $stream;
if (!isset($this->writeListeners[$key])) {
$this->writeListeners[$key] = $listener;
$this->subscribeStreamEvent($stream, Event::WRITE);
}
}
/**
* {@inheritdoc}
*/
public function removeReadStream($stream)
{
$key = (int) $stream;
if (isset($this->readListeners[$key])) {
unset($this->readListeners[$key]);
$this->unsubscribeStreamEvent($stream, Event::READ);
}
}
/**
* {@inheritdoc}
*/
public function removeWriteStream($stream)
{
$key = (int) $stream;
if (isset($this->writeListeners[$key])) {
unset($this->writeListeners[$key]);
$this->unsubscribeStreamEvent($stream, Event::WRITE);
}
}
/**
* {@inheritdoc}
*/
public function removeStream($stream)
{
$key = (int) $stream;
if (isset($this->streamEvents[$key])) {
$this->streamEvents[$key]->free();
unset(
$this->streamFlags[$key],
$this->streamEvents[$key],
$this->readListeners[$key],
$this->writeListeners[$key]
);
}
}
/**
* {@inheritdoc}
*/
public function addTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, false);
$this->scheduleTimer($timer);
return $timer;
}
/**
* {@inheritdoc}
*/
public function addPeriodicTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, true);
$this->scheduleTimer($timer);
return $timer;
}
/**
* {@inheritdoc}
*/
public function cancelTimer(TimerInterface $timer)
{
if ($this->isTimerActive($timer)) {
$this->timerEvents[$timer]->free();
$this->timerEvents->detach($timer);
}
}
/**
* {@inheritdoc}
*/
public function isTimerActive(TimerInterface $timer)
{
return $this->timerEvents->contains($timer);
}
/**
* {@inheritdoc}
*/
public function nextTick(callable $listener)
{
$this->nextTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function futureTick(callable $listener)
{
$this->futureTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function tick()
{
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
// @-suppression: https://github.com/reactphp/react/pull/234#discussion-diff-7759616R226
@$this->eventBase->loop(EventBase::LOOP_ONCE | EventBase::LOOP_NONBLOCK);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->running = true;
while ($this->running) {
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
$flags = EventBase::LOOP_ONCE;
if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
$flags |= EventBase::LOOP_NONBLOCK;
} elseif (!$this->streamEvents && !$this->timerEvents->count()) {
break;
}
$this->eventBase->loop($flags);
}
}
/**
* {@inheritdoc}
*/
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 new ext-event Event object, or update the existing one.
*
* @param resource $stream
* @param integer $flag Event::READ or Event::WRITE
*/
private function subscribeStreamEvent($stream, $flag)
{
$key = (int) $stream;
if (isset($this->streamEvents[$key])) {
$event = $this->streamEvents[$key];
$flags = ($this->streamFlags[$key] |= $flag);
$event->del();
$event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback);
} else {
$event = new Event($this->eventBase, $stream, Event::PERSIST | $flag, $this->streamCallback);
$this->streamEvents[$key] = $event;
$this->streamFlags[$key] = $flag;
}
$event->add();
}
/**
* Update the ext-event Event object for this stream to stop listening to
* the given event type, or remove it entirely if it's no longer needed.
*
* @param resource $stream
* @param integer $flag Event::READ or Event::WRITE
*/
private function unsubscribeStreamEvent($stream, $flag)
{
$key = (int) $stream;
$flags = $this->streamFlags[$key] &= ~$flag;
if (0 === $flags) {
$this->removeStream($stream);
return;
}
$event = $this->streamEvents[$key];
$event->del();
$event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback);
$event->add();
}
/**
* 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()
{
$this->timerCallback = function ($_, $__, $timer) {
call_user_func($timer->getCallback(), $timer);
if (!$timer->isPeriodic() && $this->isTimerActive($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()
{
$this->streamCallback = function ($stream, $flags) {
$key = (int) $stream;
if (Event::READ === (Event::READ & $flags) && isset($this->readListeners[$key])) {
call_user_func($this->readListeners[$key], $stream, $this);
}
if (Event::WRITE === (Event::WRITE & $flags) && isset($this->writeListeners[$key])) {
call_user_func($this->writeListeners[$key], $stream, $this);
}
};
}
}

21
vendor/react/event-loop/src/Factory.php vendored Executable file
View File

@@ -0,0 +1,21 @@
<?php
namespace React\EventLoop;
class Factory
{
public static function create()
{
// @codeCoverageIgnoreStart
if (function_exists('event_base_new')) {
return new LibEventLoop();
} elseif (class_exists('libev\EventLoop', false)) {
return new LibEvLoop;
} elseif (class_exists('EventBase', false)) {
return new ExtEventLoop;
}
return new StreamSelectLoop();
// @codeCoverageIgnoreEnd
}
}

226
vendor/react/event-loop/src/LibEvLoop.php vendored Executable file
View File

@@ -0,0 +1,226 @@
<?php
namespace React\EventLoop;
use libev\EventLoop;
use libev\IOEvent;
use libev\TimerEvent;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Tick\NextTickQueue;
use React\EventLoop\Timer\Timer;
use React\EventLoop\Timer\TimerInterface;
use SplObjectStorage;
/**
* @see https://github.com/m4rw3r/php-libev
* @see https://gist.github.com/1688204
*/
class LibEvLoop implements LoopInterface
{
private $loop;
private $nextTickQueue;
private $futureTickQueue;
private $timerEvents;
private $readEvents = [];
private $writeEvents = [];
private $running;
public function __construct()
{
$this->loop = new EventLoop();
$this->nextTickQueue = new NextTickQueue($this);
$this->futureTickQueue = new FutureTickQueue($this);
$this->timerEvents = new SplObjectStorage();
}
/**
* {@inheritdoc}
*/
public function addReadStream($stream, callable $listener)
{
if (isset($this->readEvents[(int) $stream])) {
return;
}
$callback = function () use ($stream, $listener) {
call_user_func($listener, $stream, $this);
};
$event = new IOEvent($callback, $stream, IOEvent::READ);
$this->loop->add($event);
$this->readEvents[(int) $stream] = $event;
}
/**
* {@inheritdoc}
*/
public function addWriteStream($stream, callable $listener)
{
if (isset($this->writeEvents[(int) $stream])) {
return;
}
$callback = function () use ($stream, $listener) {
call_user_func($listener, $stream, $this);
};
$event = new IOEvent($callback, $stream, IOEvent::WRITE);
$this->loop->add($event);
$this->writeEvents[(int) $stream] = $event;
}
/**
* {@inheritdoc}
*/
public function removeReadStream($stream)
{
$key = (int) $stream;
if (isset($this->readEvents[$key])) {
$this->readEvents[$key]->stop();
unset($this->readEvents[$key]);
}
}
/**
* {@inheritdoc}
*/
public function removeWriteStream($stream)
{
$key = (int) $stream;
if (isset($this->writeEvents[$key])) {
$this->writeEvents[$key]->stop();
unset($this->writeEvents[$key]);
}
}
/**
* {@inheritdoc}
*/
public function removeStream($stream)
{
$this->removeReadStream($stream);
$this->removeWriteStream($stream);
}
/**
* {@inheritdoc}
*/
public function addTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, false);
$callback = function () use ($timer) {
call_user_func($timer->getCallback(), $timer);
if ($this->isTimerActive($timer)) {
$this->cancelTimer($timer);
}
};
$event = new TimerEvent($callback, $timer->getInterval());
$this->timerEvents->attach($timer, $event);
$this->loop->add($event);
return $timer;
}
/**
* {@inheritdoc}
*/
public function addPeriodicTimer($interval, callable $callback)
{
$timer = new Timer($this, $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;
}
/**
* {@inheritdoc}
*/
public function cancelTimer(TimerInterface $timer)
{
if (isset($this->timerEvents[$timer])) {
$this->loop->remove($this->timerEvents[$timer]);
$this->timerEvents->detach($timer);
}
}
/**
* {@inheritdoc}
*/
public function isTimerActive(TimerInterface $timer)
{
return $this->timerEvents->contains($timer);
}
/**
* {@inheritdoc}
*/
public function nextTick(callable $listener)
{
$this->nextTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function futureTick(callable $listener)
{
$this->futureTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function tick()
{
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
$this->loop->run(EventLoop::RUN_ONCE | EventLoop::RUN_NOWAIT);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->running = true;
while ($this->running) {
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
$flags = EventLoop::RUN_ONCE;
if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
$flags |= EventLoop::RUN_NOWAIT;
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count()) {
break;
}
$this->loop->run($flags);
}
}
/**
* {@inheritdoc}
*/
public function stop()
{
$this->running = false;
}
}

343
vendor/react/event-loop/src/LibEventLoop.php vendored Executable file
View File

@@ -0,0 +1,343 @@
<?php
namespace React\EventLoop;
use Event;
use EventBase;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Tick\NextTickQueue;
use React\EventLoop\Timer\Timer;
use React\EventLoop\Timer\TimerInterface;
use SplObjectStorage;
/**
* An ext-libevent based event-loop.
*/
class LibEventLoop implements LoopInterface
{
const MICROSECONDS_PER_SECOND = 1000000;
private $eventBase;
private $nextTickQueue;
private $futureTickQueue;
private $timerCallback;
private $timerEvents;
private $streamCallback;
private $streamEvents = [];
private $streamFlags = [];
private $readListeners = [];
private $writeListeners = [];
private $running;
public function __construct()
{
$this->eventBase = event_base_new();
$this->nextTickQueue = new NextTickQueue($this);
$this->futureTickQueue = new FutureTickQueue($this);
$this->timerEvents = new SplObjectStorage();
$this->createTimerCallback();
$this->createStreamCallback();
}
/**
* {@inheritdoc}
*/
public function addReadStream($stream, callable $listener)
{
$key = (int) $stream;
if (!isset($this->readListeners[$key])) {
$this->readListeners[$key] = $listener;
$this->subscribeStreamEvent($stream, EV_READ);
}
}
/**
* {@inheritdoc}
*/
public function addWriteStream($stream, callable $listener)
{
$key = (int) $stream;
if (!isset($this->writeListeners[$key])) {
$this->writeListeners[$key] = $listener;
$this->subscribeStreamEvent($stream, EV_WRITE);
}
}
/**
* {@inheritdoc}
*/
public function removeReadStream($stream)
{
$key = (int) $stream;
if (isset($this->readListeners[$key])) {
unset($this->readListeners[$key]);
$this->unsubscribeStreamEvent($stream, EV_READ);
}
}
/**
* {@inheritdoc}
*/
public function removeWriteStream($stream)
{
$key = (int) $stream;
if (isset($this->writeListeners[$key])) {
unset($this->writeListeners[$key]);
$this->unsubscribeStreamEvent($stream, EV_WRITE);
}
}
/**
* {@inheritdoc}
*/
public function removeStream($stream)
{
$key = (int) $stream;
if (isset($this->streamEvents[$key])) {
$event = $this->streamEvents[$key];
event_del($event);
event_free($event);
unset(
$this->streamFlags[$key],
$this->streamEvents[$key],
$this->readListeners[$key],
$this->writeListeners[$key]
);
}
}
/**
* {@inheritdoc}
*/
public function addTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, false);
$this->scheduleTimer($timer);
return $timer;
}
/**
* {@inheritdoc}
*/
public function addPeriodicTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, true);
$this->scheduleTimer($timer);
return $timer;
}
/**
* {@inheritdoc}
*/
public function cancelTimer(TimerInterface $timer)
{
if ($this->isTimerActive($timer)) {
$event = $this->timerEvents[$timer];
event_del($event);
event_free($event);
$this->timerEvents->detach($timer);
}
}
/**
* {@inheritdoc}
*/
public function isTimerActive(TimerInterface $timer)
{
return $this->timerEvents->contains($timer);
}
/**
* {@inheritdoc}
*/
public function nextTick(callable $listener)
{
$this->nextTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function futureTick(callable $listener)
{
$this->futureTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function tick()
{
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
event_base_loop($this->eventBase, EVLOOP_ONCE | EVLOOP_NONBLOCK);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->running = true;
while ($this->running) {
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
$flags = EVLOOP_ONCE;
if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
$flags |= EVLOOP_NONBLOCK;
} elseif (!$this->streamEvents && !$this->timerEvents->count()) {
break;
}
event_base_loop($this->eventBase, $flags);
}
}
/**
* {@inheritdoc}
*/
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 new ext-libevent event resource, or update the existing one.
*
* @param resource $stream
* @param integer $flag EV_READ or EV_WRITE
*/
private function subscribeStreamEvent($stream, $flag)
{
$key = (int) $stream;
if (isset($this->streamEvents[$key])) {
$event = $this->streamEvents[$key];
$flags = $this->streamFlags[$key] |= $flag;
event_del($event);
event_set($event, $stream, EV_PERSIST | $flags, $this->streamCallback);
} else {
$event = event_new();
event_set($event, $stream, EV_PERSIST | $flag, $this->streamCallback);
event_base_set($event, $this->eventBase);
$this->streamEvents[$key] = $event;
$this->streamFlags[$key] = $flag;
}
event_add($event);
}
/**
* Update the ext-libevent event resource for this stream to stop listening to
* the given event type, or remove it entirely if it's no longer needed.
*
* @param resource $stream
* @param integer $flag EV_READ or EV_WRITE
*/
private function unsubscribeStreamEvent($stream, $flag)
{
$key = (int) $stream;
$flags = $this->streamFlags[$key] &= ~$flag;
if (0 === $flags) {
$this->removeStream($stream);
return;
}
$event = $this->streamEvents[$key];
event_del($event);
event_set($event, $stream, EV_PERSIST | $flags, $this->streamCallback);
event_add($event);
}
/**
* 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()
{
$this->timerCallback = function ($_, $__, $timer) {
call_user_func($timer->getCallback(), $timer);
// Timer already cancelled ...
if (!$this->isTimerActive($timer)) {
return;
// Reschedule periodic timers ...
} elseif ($timer->isPeriodic()) {
event_add(
$this->timerEvents[$timer],
$timer->getInterval() * self::MICROSECONDS_PER_SECOND
);
// Clean-up one shot timers ...
} else {
$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()
{
$this->streamCallback = function ($stream, $flags) {
$key = (int) $stream;
if (EV_READ === (EV_READ & $flags) && isset($this->readListeners[$key])) {
call_user_func($this->readListeners[$key], $stream, $this);
}
if (EV_WRITE === (EV_WRITE & $flags) && isset($this->writeListeners[$key])) {
call_user_func($this->writeListeners[$key], $stream, $this);
}
};
}
}

121
vendor/react/event-loop/src/LoopInterface.php vendored Executable file
View File

@@ -0,0 +1,121 @@
<?php
namespace React\EventLoop;
use React\EventLoop\Timer\TimerInterface;
interface LoopInterface
{
/**
* Register a listener to be notified when a stream is ready to read.
*
* @param resource $stream The PHP stream resource to check.
* @param callable $listener Invoked when the stream is ready.
*/
public function addReadStream($stream, callable $listener);
/**
* Register a listener to be notified when a stream is ready to write.
*
* @param resource $stream The PHP stream resource to check.
* @param callable $listener Invoked when the stream is ready.
*/
public function addWriteStream($stream, callable $listener);
/**
* Remove the read event listener for the given stream.
*
* @param resource $stream The PHP stream resource.
*/
public function removeReadStream($stream);
/**
* Remove the write event listener for the given stream.
*
* @param resource $stream The PHP stream resource.
*/
public function removeWriteStream($stream);
/**
* Remove all listeners for the given stream.
*
* @param resource $stream The PHP stream resource.
*/
public function removeStream($stream);
/**
* Enqueue a callback to be invoked once after the given interval.
*
* The execution order of timers scheduled to execute at the same time is
* not guaranteed.
*
* @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, callable $callback);
/**
* Enqueue a callback to be invoked repeatedly after the given interval.
*
* The execution order of timers scheduled to execute at the same time is
* not guaranteed.
*
* @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, callable $callback);
/**
* Cancel a pending timer.
*
* @param TimerInterface $timer The timer to cancel.
*/
public function cancelTimer(TimerInterface $timer);
/**
* Check if a given timer is active.
*
* @param TimerInterface $timer The timer to check.
*
* @return boolean True if the timer is still enqueued for execution.
*/
public function isTimerActive(TimerInterface $timer);
/**
* Schedule a callback to be invoked on the next tick of the event loop.
*
* Callbacks are guaranteed to be executed in the order they are enqueued,
* before any timer or stream events.
*
* @param callable $listener The callback to invoke.
*/
public function nextTick(callable $listener);
/**
* Schedule 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 futureTick(callable $listener);
/**
* Perform a single iteration of the event loop.
*/
public function tick();
/**
* Run the event loop until there are no more tasks to perform.
*/
public function run();
/**
* Instruct a running event loop to stop.
*/
public function stop();
}

View File

@@ -0,0 +1,273 @@
<?php
namespace React\EventLoop;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Tick\NextTickQueue;
use React\EventLoop\Timer\Timer;
use React\EventLoop\Timer\TimerInterface;
use React\EventLoop\Timer\Timers;
/**
* A stream_select() based event-loop.
*/
class StreamSelectLoop implements LoopInterface
{
const MICROSECONDS_PER_SECOND = 1000000;
private $nextTickQueue;
private $futureTickQueue;
private $timers;
private $readStreams = [];
private $readListeners = [];
private $writeStreams = [];
private $writeListeners = [];
private $running;
public function __construct()
{
$this->nextTickQueue = new NextTickQueue($this);
$this->futureTickQueue = new FutureTickQueue($this);
$this->timers = new Timers();
}
/**
* {@inheritdoc}
*/
public function addReadStream($stream, callable $listener)
{
$key = (int) $stream;
if (!isset($this->readStreams[$key])) {
$this->readStreams[$key] = $stream;
$this->readListeners[$key] = $listener;
}
}
/**
* {@inheritdoc}
*/
public function addWriteStream($stream, callable $listener)
{
$key = (int) $stream;
if (!isset($this->writeStreams[$key])) {
$this->writeStreams[$key] = $stream;
$this->writeListeners[$key] = $listener;
}
}
/**
* {@inheritdoc}
*/
public function removeReadStream($stream)
{
$key = (int) $stream;
unset(
$this->readStreams[$key],
$this->readListeners[$key]
);
}
/**
* {@inheritdoc}
*/
public function removeWriteStream($stream)
{
$key = (int) $stream;
unset(
$this->writeStreams[$key],
$this->writeListeners[$key]
);
}
/**
* {@inheritdoc}
*/
public function removeStream($stream)
{
$this->removeReadStream($stream);
$this->removeWriteStream($stream);
}
/**
* {@inheritdoc}
*/
public function addTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, false);
$this->timers->add($timer);
return $timer;
}
/**
* {@inheritdoc}
*/
public function addPeriodicTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, true);
$this->timers->add($timer);
return $timer;
}
/**
* {@inheritdoc}
*/
public function cancelTimer(TimerInterface $timer)
{
$this->timers->cancel($timer);
}
/**
* {@inheritdoc}
*/
public function isTimerActive(TimerInterface $timer)
{
return $this->timers->contains($timer);
}
/**
* {@inheritdoc}
*/
public function nextTick(callable $listener)
{
$this->nextTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function futureTick(callable $listener)
{
$this->futureTickQueue->add($listener);
}
/**
* {@inheritdoc}
*/
public function tick()
{
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
$this->timers->tick();
$this->waitForStreamActivity(0);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->running = true;
while ($this->running) {
$this->nextTickQueue->tick();
$this->futureTickQueue->tick();
$this->timers->tick();
// Next-tick or future-tick queues have pending callbacks ...
if (!$this->running || !$this->nextTickQueue->isEmpty() || !$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 {
/*
* round() needed to correct float error:
* https://github.com/reactphp/event-loop/issues/48
*/
$timeout = round($timeout * self::MICROSECONDS_PER_SECOND);
}
// The only possible event is stream activity, so wait forever ...
} elseif ($this->readStreams || $this->writeStreams) {
$timeout = null;
// There's nothing left to do ...
} else {
break;
}
$this->waitForStreamActivity($timeout);
}
}
/**
* {@inheritdoc}
*/
public function stop()
{
$this->running = false;
}
/**
* Wait/check for stream activity, or until the next timer is due.
*/
private function waitForStreamActivity($timeout)
{
$read = $this->readStreams;
$write = $this->writeStreams;
$available = $this->streamSelect($read, $write, $timeout);
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, $this);
}
}
foreach ($write as $stream) {
$key = (int) $stream;
if (isset($this->writeListeners[$key])) {
call_user_func($this->writeListeners[$key], $stream, $this);
}
}
}
/**
* 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 integer|null $timeout Activity timeout in microseconds, or null to wait forever.
*
* @return integer|false The total number of streams that are ready for read/write.
* Can return false if stream_select() is interrupted by a signal.
*/
protected function streamSelect(array &$read, array &$write, $timeout)
{
if ($read || $write) {
$except = null;
// suppress warnings that occur, when stream_select is interrupted by a signal
return @stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);
}
$timeout && usleep($timeout);
return 0;
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace React\EventLoop\Tick;
use React\EventLoop\LoopInterface;
use SplQueue;
class FutureTickQueue
{
private $eventLoop;
private $queue;
/**
* @param LoopInterface $eventLoop The event loop passed as the first parameter to callbacks.
*/
public function __construct(LoopInterface $eventLoop)
{
$this->eventLoop = $eventLoop;
$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(callable $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(),
$this->eventLoop
);
}
}
/**
* Check if the next tick queue is empty.
*
* @return boolean
*/
public function isEmpty()
{
return $this->queue->isEmpty();
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace React\EventLoop\Tick;
use React\EventLoop\LoopInterface;
use SplQueue;
class NextTickQueue
{
private $eventLoop;
private $queue;
/**
* @param LoopInterface $eventLoop The event loop passed as the first parameter to callbacks.
*/
public function __construct(LoopInterface $eventLoop)
{
$this->eventLoop = $eventLoop;
$this->queue = new SplQueue();
}
/**
* Add a callback to be invoked on the next tick of the event loop.
*
* Callbacks are guaranteed to be executed in the order they are enqueued,
* before any timer or stream events.
*
* @param callable $listener The callback to invoke.
*/
public function add(callable $listener)
{
$this->queue->enqueue($listener);
}
/**
* Flush the callback queue.
*/
public function tick()
{
while (!$this->queue->isEmpty()) {
call_user_func(
$this->queue->dequeue(),
$this->eventLoop
);
}
}
/**
* Check if the next tick queue is empty.
*
* @return boolean
*/
public function isEmpty()
{
return $this->queue->isEmpty();
}
}

102
vendor/react/event-loop/src/Timer/Timer.php vendored Executable file
View File

@@ -0,0 +1,102 @@
<?php
namespace React\EventLoop\Timer;
use React\EventLoop\LoopInterface;
class Timer implements TimerInterface
{
const MIN_INTERVAL = 0.000001;
protected $loop;
protected $interval;
protected $callback;
protected $periodic;
protected $data;
/**
* Constructor initializes the fields of the Timer
*
* @param LoopInterface $loop The loop with which this timer is associated
* @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
* @param mixed $data Arbitrary data associated with timer
*/
public function __construct(LoopInterface $loop, $interval, callable $callback, $periodic = false, $data = null)
{
if ($interval < self::MIN_INTERVAL) {
$interval = self::MIN_INTERVAL;
}
$this->loop = $loop;
$this->interval = (float) $interval;
$this->callback = $callback;
$this->periodic = (bool) $periodic;
$this->data = null;
}
/**
* {@inheritdoc}
*/
public function getLoop()
{
return $this->loop;
}
/**
* {@inheritdoc}
*/
public function getInterval()
{
return $this->interval;
}
/**
* {@inheritdoc}
*/
public function getCallback()
{
return $this->callback;
}
/**
* {@inheritdoc}
*/
public function setData($data)
{
$this->data = $data;
}
/**
* {@inheritdoc}
*/
public function getData()
{
return $this->data;
}
/**
* {@inheritdoc}
*/
public function isPeriodic()
{
return $this->periodic;
}
/**
* {@inheritdoc}
*/
public function isActive()
{
return $this->loop->isTimerActive($this);
}
/**
* {@inheritdoc}
*/
public function cancel()
{
$this->loop->cancelTimer($this);
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace React\EventLoop\Timer;
use React\EventLoop\LoopInterface;
interface TimerInterface
{
/**
* Get the loop with which this timer is associated
*
* @return LoopInterface
*/
public function getLoop();
/**
* 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();
/**
* Set arbitrary data associated with timer
*
* @param mixed $data
*/
public function setData($data);
/**
* Get arbitrary data associated with timer
*
* @return mixed
*/
public function getData();
/**
* Determine whether the time is periodic
*
* @return bool
*/
public function isPeriodic();
/**
* Determine whether the time is active
*
* @return bool
*/
public function isActive();
/**
* Cancel this timer
*/
public function cancel();
}

100
vendor/react/event-loop/src/Timer/Timers.php vendored Executable file
View File

@@ -0,0 +1,100 @@
<?php
namespace React\EventLoop\Timer;
use SplObjectStorage;
use SplPriorityQueue;
class Timers
{
private $time;
private $timers;
private $scheduler;
public function __construct()
{
$this->timers = new SplObjectStorage();
$this->scheduler = new SplPriorityQueue();
}
public function updateTime()
{
return $this->time = microtime(true);
}
public function getTime()
{
return $this->time ?: $this->updateTime();
}
public function add(TimerInterface $timer)
{
$interval = $timer->getInterval();
$scheduledAt = $interval + microtime(true);
$this->timers->attach($timer, $scheduledAt);
$this->scheduler->insert($timer, -$scheduledAt);
}
public function contains(TimerInterface $timer)
{
return $this->timers->contains($timer);
}
public function cancel(TimerInterface $timer)
{
$this->timers->detach($timer);
}
public function getFirst()
{
while ($this->scheduler->count()) {
$timer = $this->scheduler->top();
if ($this->timers->contains($timer)) {
return $this->timers[$timer];
}
$this->scheduler->extract();
}
return null;
}
public function isEmpty()
{
return count($this->timers) === 0;
}
public function tick()
{
$time = $this->updateTime();
$timers = $this->timers;
$scheduler = $this->scheduler;
while (!$scheduler->isEmpty()) {
$timer = $scheduler->top();
if (!isset($timers[$timer])) {
$scheduler->extract();
$timers->detach($timer);
continue;
}
if ($timers[$timer] >= $time) {
break;
}
$scheduler->extract();
call_user_func($timer->getCallback(), $timer);
if ($timer->isPeriodic() && isset($timers[$timer])) {
$timers[$timer] = $scheduledAt = $timer->getInterval() + $time;
$scheduler->insert($timer, -$scheduledAt);
} else {
$timers->detach($timer);
}
}
}
}

View File

@@ -0,0 +1,539 @@
<?php
namespace React\Tests\EventLoop;
abstract class AbstractLoopTest extends TestCase
{
/**
* @var \React\EventLoop\LoopInterface
*/
protected $loop;
private $tickTimeout;
public function setUp()
{
// HHVM is a bit slow, so give it more time
$this->tickTimeout = defined('HHVM_VERSION') ? 0.02 : 0.005;
$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 testAddReadStream()
{
list ($input, $output) = $this->createSocketPair();
$this->loop->addReadStream($input, $this->expectCallableExactly(2));
fwrite($output, "foo\n");
$this->loop->tick();
fwrite($output, "bar\n");
$this->loop->tick();
}
public function testAddReadStreamIgnoresSecondCallable()
{
list ($input, $output) = $this->createSocketPair();
$this->loop->addReadStream($input, $this->expectCallableExactly(2));
$this->loop->addReadStream($input, $this->expectCallableNever());
fwrite($output, "foo\n");
$this->loop->tick();
fwrite($output, "bar\n");
$this->loop->tick();
}
public function testAddWriteStream()
{
list ($input) = $this->createSocketPair();
$this->loop->addWriteStream($input, $this->expectCallableExactly(2));
$this->loop->tick();
$this->loop->tick();
}
public function testAddWriteStreamIgnoresSecondCallable()
{
list ($input) = $this->createSocketPair();
$this->loop->addWriteStream($input, $this->expectCallableExactly(2));
$this->loop->addWriteStream($input, $this->expectCallableNever());
$this->loop->tick();
$this->loop->tick();
}
public function testRemoveReadStreamInstantly()
{
list ($input, $output) = $this->createSocketPair();
$this->loop->addReadStream($input, $this->expectCallableNever());
$this->loop->removeReadStream($input);
fwrite($output, "bar\n");
$this->loop->tick();
}
public function testRemoveReadStreamAfterReading()
{
list ($input, $output) = $this->createSocketPair();
$this->loop->addReadStream($input, $this->expectCallableOnce());
fwrite($output, "foo\n");
$this->loop->tick();
$this->loop->removeReadStream($input);
fwrite($output, "bar\n");
$this->loop->tick();
}
public function testRemoveWriteStreamInstantly()
{
list ($input) = $this->createSocketPair();
$this->loop->addWriteStream($input, $this->expectCallableNever());
$this->loop->removeWriteStream($input);
$this->loop->tick();
}
public function testRemoveWriteStreamAfterWriting()
{
list ($input) = $this->createSocketPair();
$this->loop->addWriteStream($input, $this->expectCallableOnce());
$this->loop->tick();
$this->loop->removeWriteStream($input);
$this->loop->tick();
}
public function testRemoveStreamInstantly()
{
list ($input, $output) = $this->createSocketPair();
$this->loop->addReadStream($input, $this->expectCallableNever());
$this->loop->addWriteStream($input, $this->expectCallableNever());
$this->loop->removeStream($input);
fwrite($output, "bar\n");
$this->loop->tick();
}
public function testRemoveStreamForReadOnly()
{
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->loop->tick();
}
public function testRemoveStreamForWriteOnly()
{
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->loop->tick();
}
public function testRemoveStream()
{
list ($input, $output) = $this->createSocketPair();
$this->loop->addReadStream($input, $this->expectCallableOnce());
$this->loop->addWriteStream($input, $this->expectCallableOnce());
fwrite($output, "bar\n");
$this->loop->tick();
$this->loop->removeStream($input);
fwrite($output, "bar\n");
$this->loop->tick();
}
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->loop->removeStream($stream);
}
/** @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->removeStream($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()
{
$this->loop->addTimer(
1,
function () {
$this->fail('Timer was executed.');
}
);
$this->loop->nextTick(
function () {
$this->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
$loop->addReadStream($input2, function () use (& $called) {
if ($called) {
$this->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 testNextTick()
{
$called = false;
$callback = function ($loop) use (&$called) {
$this->assertSame($this->loop, $loop);
$called = true;
};
$this->loop->nextTick($callback);
$this->assertFalse($called);
$this->loop->tick();
$this->assertTrue($called);
}
public function testNextTickFiresBeforeIO()
{
list ($stream) = $this->createSocketPair();
$this->loop->addWriteStream(
$stream,
function () {
echo 'stream' . PHP_EOL;
}
);
$this->loop->nextTick(
function () {
echo 'next-tick' . PHP_EOL;
}
);
$this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL);
$this->loop->tick();
}
public function testRecursiveNextTick()
{
list ($stream) = $this->createSocketPair();
$this->loop->addWriteStream(
$stream,
function () {
echo 'stream' . PHP_EOL;
}
);
$this->loop->nextTick(
function () {
$this->loop->nextTick(
function () {
echo 'next-tick' . PHP_EOL;
}
);
}
);
$this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL);
$this->loop->tick();
}
public function testRunWaitsForNextTickEvents()
{
list ($stream) = $this->createSocketPair();
$this->loop->addWriteStream(
$stream,
function () use ($stream) {
$this->loop->removeStream($stream);
$this->loop->nextTick(
function () {
echo 'next-tick' . PHP_EOL;
}
);
}
);
$this->expectOutputString('next-tick' . PHP_EOL);
$this->loop->run();
}
public function testNextTickEventGeneratedByFutureTick()
{
list ($stream) = $this->createSocketPair();
$this->loop->futureTick(
function () {
$this->loop->nextTick(
function () {
echo 'next-tick' . PHP_EOL;
}
);
}
);
$this->expectOutputString('next-tick' . PHP_EOL);
$this->loop->run();
}
public function testNextTickEventGeneratedByTimer()
{
$this->loop->addTimer(
0.001,
function () {
$this->loop->nextTick(
function () {
echo 'next-tick' . PHP_EOL;
}
);
}
);
$this->expectOutputString('next-tick' . PHP_EOL);
$this->loop->run();
}
public function testFutureTick()
{
$called = false;
$callback = function ($loop) use (&$called) {
$this->assertSame($this->loop, $loop);
$called = true;
};
$this->loop->futureTick($callback);
$this->assertFalse($called);
$this->loop->tick();
$this->assertTrue($called);
}
public function testFutureTickFiresBeforeIO()
{
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->loop->tick();
}
public function testRecursiveFutureTick()
{
list ($stream) = $this->createSocketPair();
$this->loop->addWriteStream(
$stream,
function () use ($stream) {
echo 'stream' . PHP_EOL;
$this->loop->removeWriteStream($stream);
}
);
$this->loop->futureTick(
function () {
echo 'future-tick-1' . PHP_EOL;
$this->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();
$this->loop->addWriteStream(
$stream,
function () use ($stream) {
$this->loop->removeStream($stream);
$this->loop->futureTick(
function () {
echo 'future-tick' . PHP_EOL;
}
);
}
);
$this->expectOutputString('future-tick' . PHP_EOL);
$this->loop->run();
}
public function testFutureTickEventGeneratedByNextTick()
{
list ($stream) = $this->createSocketPair();
$this->loop->nextTick(
function () {
$this->loop->futureTick(
function () {
echo 'future-tick' . PHP_EOL;
}
);
}
);
$this->expectOutputString('future-tick' . PHP_EOL);
$this->loop->run();
}
public function testFutureTickEventGeneratedByTimer()
{
$this->loop->addTimer(
0.001,
function () {
$this->loop->futureTick(
function () {
echo 'future-tick' . PHP_EOL;
}
);
}
);
$this->expectOutputString('future-tick' . PHP_EOL);
$this->loop->run();
}
private function assertRunFasterThan($maxInterval)
{
$start = microtime(true);
$this->loop->run();
$end = microtime(true);
$interval = $end - $start;
$this->assertLessThan($maxInterval, $interval);
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace React\Tests\EventLoop;
class CallableStub
{
public function __invoke()
{
}
}

View File

@@ -0,0 +1,90 @@
<?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.');
}
$cfg = null;
if ($readStreamCompatible) {
$cfg = new \EventConfig();
$cfg->requireFeatures(\EventConfig::FEATURE_FDS);
}
return new ExtEventLoop($cfg);
}
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->loop->tick();
fwrite($input, "bar\n");
$this->loop->tick();
}
}

View File

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

View File

@@ -0,0 +1,58 @@
<?php
namespace React\Tests\EventLoop;
use React\EventLoop\LibEventLoop;
class LibEventLoopTest 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 LibEventLoop();
}
public function tearDown()
{
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

@@ -0,0 +1,179 @@
<?php
namespace React\Tests\EventLoop;
use React\EventLoop\LoopInterface;
use React\EventLoop\StreamSelectLoop;
use React\EventLoop\Timer\Timer;
class StreamSelectLoopTest extends AbstractLoopTest
{
protected function tearDown()
{
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 [
['SIGUSR1'],
['SIGHUP'],
['SIGTERM'],
];
}
private $_signalHandled = false;
/**
* Test signal interrupt when no stream is attached to the loop
* @dataProvider signalProvider
*/
public function testSignalInterruptNoStream($signal)
{
if (!extension_loaded('pcntl')) {
$this->markTestSkipped('"pcntl" extension is required to run this test.');
}
// dispatch signal handler once before signal is sent and once after
$this->loop->addTimer(0.01, function() { pcntl_signal_dispatch(); });
$this->loop->addTimer(0.03, function() { pcntl_signal_dispatch(); });
if (defined('HHVM_VERSION')) {
// hhvm startup is slow so we need to add another handler much later
$this->loop->addTimer(0.5, function() { pcntl_signal_dispatch(); });
}
$this->setUpSignalHandler($signal);
// spawn external process to send signal to current process id
$this->forkSendSignal($signal);
$this->loop->run();
$this->assertTrue($this->_signalHandled);
}
/**
* Test signal interrupt when a stream is attached to the loop
* @dataProvider signalProvider
*/
public function testSignalInterruptWithStream($signal)
{
if (!extension_loaded('pcntl')) {
$this->markTestSkipped('"pcntl" extension is required to run this test.');
}
// dispatch signal handler every 10ms
$this->loop->addPeriodicTimer(0.01, function() { pcntl_signal_dispatch(); });
// add stream to the loop
list($writeStream, $readStream) = $this->createSocketPair();
$this->loop->addReadStream($readStream, function($stream, $loop) {
/** @var $loop LoopInterface */
$read = fgets($stream);
if ($read === "end loop\n") {
$loop->stop();
}
});
$this->loop->addTimer(0.05, function() use ($writeStream) {
fwrite($writeStream, "end loop\n");
});
$this->setUpSignalHandler($signal);
// spawn external process to send signal to current process id
$this->forkSendSignal($signal);
$this->loop->run();
$this->assertTrue($this->_signalHandled);
}
/**
* add signal handler for signal
*/
protected function setUpSignalHandler($signal)
{
$this->_signalHandled = false;
$this->assertTrue(pcntl_signal(constant($signal), function() { $this->_signalHandled = true; }));
}
/**
* 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();
}
}
/**
* https://github.com/reactphp/event-loop/issues/48
*
* Tests that timer with very small interval uses at least 1 microsecond
* timeout.
*/
public function testSmallTimerInterval()
{
/** @var StreamSelectLoop|\PHPUnit_Framework_MockObject_MockObject $loop */
$loop = $this->getMock('React\EventLoop\StreamSelectLoop', ['streamSelect']);
$loop
->expects($this->at(0))
->method('streamSelect')
->with([], [], 1);
$loop
->expects($this->at(1))
->method('streamSelect')
->with([], [], 0);
$callsCount = 0;
$loop->addPeriodicTimer(Timer::MIN_INTERVAL, function() use (&$loop, &$callsCount) {
$callsCount++;
if ($callsCount == 2) {
$loop->stop();
}
});
$loop->run();
}
}

41
vendor/react/event-loop/tests/TestCase.php vendored Executable file
View File

@@ -0,0 +1,41 @@
<?php
namespace React\Tests\EventLoop;
class TestCase extends \PHPUnit_Framework_TestCase
{
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()
{
return $this->getMockBuilder('React\Tests\EventLoop\CallableStub')->getMock();
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace React\Tests\EventLoop\Timer;
use React\Tests\EventLoop\TestCase;
abstract class AbstractTimerTest extends TestCase
{
abstract public function createLoop();
public function testAddTimer()
{
// usleep is intentionally high
$loop = $this->createLoop();
$loop->addTimer(0.001, $this->expectCallableOnce());
usleep(1000);
$loop->tick();
}
public function testAddPeriodicTimer()
{
$loop = $this->createLoop();
$loop->addPeriodicTimer(0.001, $this->expectCallableExactly(3));
usleep(1000);
$loop->tick();
usleep(1000);
$loop->tick();
usleep(1000);
$loop->tick();
}
public function testAddPeriodicTimerWithCancel()
{
$loop = $this->createLoop();
$timer = $loop->addPeriodicTimer(0.001, $this->expectCallableExactly(2));
usleep(1000);
$loop->tick();
usleep(1000);
$loop->tick();
$timer->cancel();
usleep(1000);
$loop->tick();
}
public function testAddPeriodicTimerCancelsItself()
{
$i = 0;
$loop = $this->createLoop();
$loop->addPeriodicTimer(0.001, function ($timer) use (&$i) {
$i++;
if ($i == 2) {
$timer->cancel();
}
});
usleep(1000);
$loop->tick();
usleep(1000);
$loop->tick();
usleep(1000);
$loop->tick();
$this->assertSame(2, $i);
}
public function testIsTimerActive()
{
$loop = $this->createLoop();
$timer = $loop->addPeriodicTimer(0.001, function () {});
$this->assertTrue($loop->isTimerActive($timer));
$timer->cancel();
$this->assertFalse($loop->isTimerActive($timer));
}
public function testMinimumIntervalOneMicrosecond()
{
$loop = $this->createLoop();
$timer = $loop->addTimer(0, function () {});
$this->assertEquals(0.000001, $timer->getInterval());
}
}

View File

@@ -0,0 +1,17 @@
<?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

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

View File

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

View File

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

View File

@@ -0,0 +1,29 @@
<?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()
{
$loop = $this
->getMockBuilder('React\EventLoop\LoopInterface')
->getMock();
$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($loop, 0.5, function () {
$this->fail("Timer shouldn't be called");
}));
$timers->tick();
}
}

7
vendor/react/event-loop/tests/bootstrap.php vendored Executable file
View File

@@ -0,0 +1,7 @@
<?php
$loader = @include __DIR__ . '/../vendor/autoload.php';
if (!$loader) {
$loader = require __DIR__ . '/../../../../vendor/autoload.php';
}
$loader->addPsr4('React\\Tests\\EventLoop\\', __DIR__);

37
vendor/react/event-loop/travis-init.sh vendored Executable file
View File

@@ -0,0 +1,37 @@
#!/bin/bash
set -e
set -o pipefail
if [[ "$TRAVIS_PHP_VERSION" != "hhvm" &&
"$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]]; then
# install 'event' PHP extension
echo "yes" | pecl install event
# install 'libevent' PHP extension (does not support php 7)
if [[ "$TRAVIS_PHP_VERSION" != "7.0" &&
"$TRAVIS_PHP_VERSION" != "7.1" ]]; then
curl http://pecl.php.net/get/libevent-0.1.0.tgz | tar -xz
pushd libevent-0.1.0
phpize
./configure
make
make install
popd
echo "extension=libevent.so" >> "$(php -r 'echo php_ini_loaded_file();')"
fi
# install 'libev' PHP extension (does not support php 7)
if [[ "$TRAVIS_PHP_VERSION" != "7.0" &&
"$TRAVIS_PHP_VERSION" != "7.1" ]]; then
git clone --recursive https://github.com/m4rw3r/php-libev
pushd php-libev
phpize
./configure --with-libev
make
make install
popd
echo "extension=libev.so" >> "$(php -r 'echo php_ini_loaded_file();')"
fi
fi