From 79ca43cc00272160eed6c21cc1c0c89fc99ae5d2 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Fri, 26 Oct 2018 17:23:32 +0200 Subject: [PATCH 01/85] Test against PHP 7.3 on travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 7af713a4..0af783a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ php: - 7.0 - 7.1 - 7.2 + - 7.3 - hhvm # ignore errors, see below # lock distro so new future defaults will not break the build From 7c919c44b1242073ee5606b4b0145ffbaf0babbc Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sun, 11 Nov 2018 22:35:29 +0100 Subject: [PATCH 02/85] Don't install ext-event and ext-ev loops on PHP 7.3 --- travis-init.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/travis-init.sh b/travis-init.sh index 29ce884a..9ed7afaa 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -6,7 +6,8 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]]; then # install 'event' and 'ev' PHP extension - if [[ "$TRAVIS_PHP_VERSION" != "5.3" ]]; then + if [[ "$TRAVIS_PHP_VERSION" != "5.3" && + "$TRAVIS_PHP_VERSION" != "7.3" ]]; then echo "yes" | pecl install event echo "yes" | pecl install ev fi @@ -14,7 +15,8 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && # install 'libevent' PHP extension (does not support php 7) if [[ "$TRAVIS_PHP_VERSION" != "7.0" && "$TRAVIS_PHP_VERSION" != "7.1" && - "$TRAVIS_PHP_VERSION" != "7.2" ]]; then + "$TRAVIS_PHP_VERSION" != "7.2" && + "$TRAVIS_PHP_VERSION" != "7.3" ]]; then curl http://pecl.php.net/get/libevent-0.1.0.tgz | tar -xz pushd libevent-0.1.0 phpize @@ -28,7 +30,8 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && # install 'libev' PHP extension (does not support php 7) if [[ "$TRAVIS_PHP_VERSION" != "7.0" && "$TRAVIS_PHP_VERSION" != "7.1" && - "$TRAVIS_PHP_VERSION" != "7.2" ]]; then + "$TRAVIS_PHP_VERSION" != "7.2" && + "$TRAVIS_PHP_VERSION" != "7.3" ]]; then git clone --recursive https://github.com/m4rw3r/php-libev pushd php-libev phpize From d589f9f0395cbdcc5f72cc8e6feeee7084110844 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Fri, 28 Jul 2017 22:02:30 +0200 Subject: [PATCH 03/85] UV Event Loop --- .travis.yml | 5 + README.md | 9 + composer.json | 3 +- src/ExtUvLoop.php | 316 +++++++++++++++++++++++++++++++++ src/Factory.php | 9 +- tests/ExtUvLoopTest.php | 17 ++ tests/Timer/ExtUvTimerTest.php | 17 ++ travis-init.sh | 7 + 8 files changed, 379 insertions(+), 4 deletions(-) create mode 100644 src/ExtUvLoop.php create mode 100644 tests/ExtUvLoopTest.php create mode 100644 tests/Timer/ExtUvTimerTest.php diff --git a/.travis.yml b/.travis.yml index 0af783a9..4e946173 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,11 @@ cache: directories: - $HOME/.composer/cache/files +before_install: + - sudo add-apt-repository ppa:ondrej/php -y + - sudo apt-get update -q + - sudo apt-get install libuv1-dev || true + install: - ./travis-init.sh - composer install diff --git a/README.md b/README.md index 40cb822a..1d25bdac 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ single [`run()`](#run) call that is controlled by the user. * [ExtLibeventLoop](#extlibeventloop) * [ExtLibevLoop](#extlibevloop) * [ExtEvLoop](#extevloop) + * [ExtUvLoop](#extuvloop) * [LoopInterface](#loopinterface) * [run()](#run) * [stop()](#stop) @@ -208,6 +209,14 @@ provides an interface to `libev` library. This loop is known to work with PHP 5.4 through PHP 7+. +#### ExtUvLoop + +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. + +This loop is known to work with PHP 7+. #### ExtLibeventLoop diff --git a/composer.json b/composer.json index c8ff91ec..f6517df4 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,8 @@ }, "suggest": { "ext-event": "~1.0 for ExtEventLoop", - "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + "ext-pcntl": "For signal handling support when using the StreamSelectLoop", + "ext-uv": "* for ExtUvLoop" }, "autoload": { "psr-4": { diff --git a/src/ExtUvLoop.php b/src/ExtUvLoop.php new file mode 100644 index 00000000..aade9943 --- /dev/null +++ b/src/ExtUvLoop.php @@ -0,0 +1,316 @@ +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, + (int) ($interval * 1000) + 1, + 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); + }; + + $event = \uv_timer_init($this->uv); + $this->timers->attach($timer, $event); + \uv_timer_start( + $event, + (int) ($interval * 1000) + 1, + (int) ($interval * 1000) + 1, + $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) { + if (!isset($this->streamEvents[(int) $stream])) { + return; + } + + if (($events | 4) === 4) { + // Disconnected + return; + } + + 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; + } +} diff --git a/src/Factory.php b/src/Factory.php index 763c077b..d1767bf8 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -24,14 +24,17 @@ final class Factory public static function create() { // @codeCoverageIgnoreStart - if (\class_exists('libev\EventLoop', false)) { + if (\function_exists('uv_loop_new')) { + // only use ext-uv on PHP 7 + return new ExtUvLoop(); + } elseif (\class_exists('libev\EventLoop', false)) { return new ExtLibevLoop(); } elseif (\class_exists('EvLoop', false)) { return new ExtEvLoop(); } elseif (\class_exists('EventBase', false)) { return new ExtEventLoop(); - } elseif (\function_exists('event_base_new') && \PHP_VERSION_ID < 70000) { - // only use ext-libevent on PHP < 7 for now + } elseif (\function_exists('event_base_new') && \PHP_MAJOR_VERSION === 5) { + // only use ext-libevent on PHP 5 for now return new ExtLibeventLoop(); } diff --git a/tests/ExtUvLoopTest.php b/tests/ExtUvLoopTest.php new file mode 100644 index 00000000..61a94a9f --- /dev/null +++ b/tests/ExtUvLoopTest.php @@ -0,0 +1,17 @@ +markTestSkipped('uv tests skipped because ext-uv is not installed.'); + } + + return new ExtUvLoop(); + } +} diff --git a/tests/Timer/ExtUvTimerTest.php b/tests/Timer/ExtUvTimerTest.php new file mode 100644 index 00000000..e0c70233 --- /dev/null +++ b/tests/Timer/ExtUvTimerTest.php @@ -0,0 +1,17 @@ +markTestSkipped('uv tests skipped because ext-uv is not installed.'); + } + + return new ExtUvLoop(); + } +} diff --git a/travis-init.sh b/travis-init.sh index 9ed7afaa..9ea4e6f9 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -42,4 +42,11 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && echo "extension=libev.so" >> "$(php -r 'echo php_ini_loaded_file();')" fi + # install 'libuv' PHP extension (does not support php 5) + if [[ "$TRAVIS_PHP_VERSION" = "7.0" || + "$TRAVIS_PHP_VERSION" = "7.1" || + "$TRAVIS_PHP_VERSION" = "7.2" ]]; then + echo "yes" | pecl install uv-beta + fi + fi From 4a3e85f028f6938bd61bd21cce55ca723924b558 Mon Sep 17 00:00:00 2001 From: Charlotte Dunois Date: Mon, 14 Jan 2019 11:52:07 +0100 Subject: [PATCH 04/85] Add async signal dispatching if available --- src/StreamSelectLoop.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 625b6fb6..3e6ff07f 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -62,6 +62,7 @@ final class StreamSelectLoop implements LoopInterface private $writeListeners = array(); private $running; private $pcntl = false; + private $pcntlActive = false; private $signals; public function __construct() @@ -69,7 +70,12 @@ public function __construct() $this->futureTickQueue = new FutureTickQueue(); $this->timers = new Timers(); $this->pcntl = \extension_loaded('pcntl'); + $this->pcntlActive = $this->pcntl && !\function_exists('pcntl_async_signals'); $this->signals = new SignalsHandler(); + + if ($this->pcntl && !$this->pcntlActive) { + \pcntl_async_signals(true); + } } public function addReadStream($stream, $listener) @@ -222,7 +228,7 @@ private function waitForStreamActivity($timeout) $write = $this->writeStreams; $available = $this->streamSelect($read, $write, $timeout); - if ($this->pcntl) { + if ($this->pcntlActive) { \pcntl_signal_dispatch(); } if (false === $available) { From dbdf5275b2d0bbe1fcecec1aaefb968e107eb4b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 3 Dec 2018 12:57:02 +0100 Subject: [PATCH 05/85] Skip signal tests w/o PCNTL and skip timer tests on inaccurate platforms The signal constants are only available when ext-pcntl is loaded. If it is not loaded, simply skip all tests that reference these constants. Before running a timer test and asserting its time interval, run a simple test to check the accuracy on the current platform. This is known to work on most common platforms, but will now avoid false negative test results on platforms that are known to be slow (Travis CI and others). --- phpunit.xml.dist | 2 +- tests/AbstractLoopTest.php | 14 +++++++++++++- tests/SignalsHandlerTest.php | 3 +++ tests/StreamSelectLoopTest.php | 10 ++-------- tests/Timer/AbstractTimerTest.php | 24 +++++++++++++++++++++--- tests/bootstrap.php | 9 --------- 6 files changed, 40 insertions(+), 22 deletions(-) delete mode 100644 tests/bootstrap.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index cba6d4dd..13d3fab0 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -9,7 +9,7 @@ processIsolation="false" stopOnFailure="false" syntaxCheck="false" - bootstrap="tests/bootstrap.php" + bootstrap="vendor/autoload.php" > diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 3d844382..83f5756b 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -491,10 +491,13 @@ function () { public function testRemoveSignalNotRegisteredIsNoOp() { - $this->loop->removeSignal(SIGINT, function () { }); + $this->loop->removeSignal(2, function () { }); $this->assertTrue(true); } + /** + * @requires extension pcntl + */ public function testSignal() { if (!function_exists('posix_kill') || !function_exists('posix_getpid')) { @@ -528,6 +531,9 @@ public function testSignal() $this->assertTrue($calledShouldNot); } + /** + * @requires extension pcntl + */ public function testSignalMultipleUsagesForTheSameListener() { $funcCallCount = 0; @@ -552,6 +558,9 @@ public function testSignalMultipleUsagesForTheSameListener() $this->assertSame(1, $funcCallCount); } + /** + * @requires extension pcntl + */ public function testSignalsKeepTheLoopRunning() { $loop = $this->loop; @@ -565,6 +574,9 @@ public function testSignalsKeepTheLoopRunning() $this->assertRunSlowerThan(1.5); } + /** + * @requires extension pcntl + */ public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop() { $loop = $this->loop; diff --git a/tests/SignalsHandlerTest.php b/tests/SignalsHandlerTest.php index f8b7df3d..a8cc4221 100644 --- a/tests/SignalsHandlerTest.php +++ b/tests/SignalsHandlerTest.php @@ -6,6 +6,9 @@ final class SignalsHandlerTest extends TestCase { + /** + * @requires extension pcntl + */ public function testEmittedEventsAndCallHandling() { $callCount = 0; diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index bd19e1cd..2eb388f0 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -49,13 +49,10 @@ public function signalProvider() /** * Test signal interrupt when no stream is attached to the loop * @dataProvider signalProvider + * @requires extension pcntl */ public function testSignalInterruptNoStream($signal) { - if (!extension_loaded('pcntl')) { - $this->markTestSkipped('"pcntl" extension is required to run this test.'); - } - // dispatch signal handler every 10ms for 0.1s $check = $this->loop->addPeriodicTimer(0.01, function() { pcntl_signal_dispatch(); @@ -80,13 +77,10 @@ public function testSignalInterruptNoStream($signal) /** * Test signal interrupt when a stream is attached to the loop * @dataProvider signalProvider + * @requires extension pcntl */ 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(); diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index 294e683f..11187b31 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -22,8 +22,28 @@ public function testAddTimerReturnsNonPeriodicTimerInstance() $this->assertFalse($timer->isPeriodic()); } + /** + * @depends testPlatformHasHighAccuracy + */ public function testAddTimerWillBeInvokedOnceAndBlocksLoopWhenRunning() { + // Make no strict assumptions about actual time interval. Common + // environments usually provide millisecond accuracy (or better), but + // Travis and other CI systems are slow. + // We try to compensate for this by skipping accurate tests when the + // current platform is known to be inaccurate. We test this by sleeping + // 3x1ms and then measure the time for each iteration before running the + // actual test. + for ($i = 0; $i < 3; ++$i) { + $start = microtime(true); + usleep(1000); + $time = microtime(true) - $start; + + if ($time < 0.001 || $time > 0.002) { + $this->markTestSkipped('Platform provides insufficient accuracy (' . $time . ' s)'); + } + } + $loop = $this->createLoop(); $loop->addTimer(0.001, $this->expectCallableOnce()); @@ -32,10 +52,8 @@ public function testAddTimerWillBeInvokedOnceAndBlocksLoopWhenRunning() $loop->run(); $end = microtime(true); - // make no strict assumptions about actual time interval. - // must be at least 0.001s (1ms) and should not take longer than 0.1s $this->assertGreaterThanOrEqual(0.001, $end - $start); - $this->assertLessThan(0.1, $end - $start); + $this->assertLessThan(0.002, $end - $start); } public function testAddPeriodicTimerReturnsPeriodicTimerInstance() diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index aeb44352..00000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,9 +0,0 @@ - Date: Mon, 4 Feb 2019 12:02:08 +0100 Subject: [PATCH 06/85] Forward compatibility with PHPUnit 7 and use legacy PHPUnit 5 on HHVM --- .travis.yml | 4 +++- composer.json | 2 +- phpunit.xml.dist | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4e946173..fe9bd21c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ php: - 7.1 - 7.2 - 7.3 - - hhvm # ignore errors, see below +# - hhvm # requires legacy phpunit & ignore errors, see below # lock distro so new future defaults will not break the build dist: trusty @@ -18,6 +18,8 @@ matrix: include: - php: 5.3 dist: precise + - php: hhvm + install: composer require phpunit/phpunit:^5 --dev --no-interaction allow_failures: - php: hhvm diff --git a/composer.json b/composer.json index f6517df4..cc6abf06 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "~4.8.35 || ^5.7 || ^6.4" + "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35" }, "suggest": { "ext-event": "~1.0 for ExtEventLoop", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 13d3fab0..04d426b5 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -8,7 +8,6 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - syntaxCheck="false" bootstrap="vendor/autoload.php" > From eae70963f61cf95e107cb99b5b337753fb5bcba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 9 Jan 2019 17:19:51 +0100 Subject: [PATCH 07/85] Use high resolution timer on PHP 7.3+ --- README.md | 17 +++++++++-------- src/LoopInterface.php | 8 ++++---- src/StreamSelectLoop.php | 9 +++++---- src/Timer/Timers.php | 9 +++++++-- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 1d25bdac..eb7bc195 100644 --- a/README.md +++ b/README.md @@ -182,13 +182,14 @@ 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, because a monotonic time source is not available in PHP by default. +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 and then adjust -your system time forward by 20s, the timer may trigger in 10s. +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. #### ExtEventLoop @@ -360,8 +361,8 @@ 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 -not available on PHP by default, event loop implementations MAY fall back -to using wall-clock time. +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). @@ -433,8 +434,8 @@ 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 -not available on PHP by default, event loop implementations MAY fall back -to using wall-clock time. +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). diff --git a/src/LoopInterface.php b/src/LoopInterface.php index 1cc8640f..8146757a 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -185,8 +185,8 @@ public function removeWriteStream($stream); * * This interface suggests that event loop implementations SHOULD use a * monotonic time source if available. Given that a monotonic time source is - * not available on PHP by default, event loop implementations MAY fall back - * to using wall-clock time. + * 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). @@ -263,8 +263,8 @@ public function addTimer($interval, $callback); * * This interface suggests that event loop implementations SHOULD use a * monotonic time source if available. Given that a monotonic time source is - * not available on PHP by default, event loop implementations MAY fall back - * to using wall-clock time. + * 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). diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 3e6ff07f..9867327e 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -38,13 +38,14 @@ * 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, because a monotonic time source is not available in PHP by default. + * 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 and then adjust - * your system time forward by 20s, the timer may trigger in 10s. + * 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 http://php.net/manual/en/function.stream-select.php diff --git a/src/Timer/Timers.php b/src/Timer/Timers.php index 1d4ac9b7..a141ff12 100644 --- a/src/Timer/Timers.php +++ b/src/Timer/Timers.php @@ -21,7 +21,12 @@ final class Timers public function updateTime() { - return $this->time = \microtime(true); + // prefer high-resolution timer, available as of PHP 7.3+ + if (\function_exists('hrtime')) { + return $this->time = \hrtime(true) * 1e-9; + } + + return $this->time = \microtime(true) + 1000; } public function getTime() @@ -33,7 +38,7 @@ public function add(TimerInterface $timer) { $id = \spl_object_hash($timer); $this->timers[$id] = $timer; - $this->schedule[$id] = $timer->getInterval() + \microtime(true); + $this->schedule[$id] = $timer->getInterval() + $this->updateTime(); $this->sorted = false; } From 29bf39c1c63153c81e0e34d4a4a7e968aaf4c7be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 3 Feb 2019 10:23:43 +0100 Subject: [PATCH 08/85] Improve performance by avoiding platform checks during runtime --- src/Timer/Timers.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Timer/Timers.php b/src/Timer/Timers.php index a141ff12..70adc132 100644 --- a/src/Timer/Timers.php +++ b/src/Timer/Timers.php @@ -18,15 +18,17 @@ final class Timers private $timers = array(); private $schedule = array(); private $sorted = true; + private $useHighResolution; - public function updateTime() + public function __construct() { // prefer high-resolution timer, available as of PHP 7.3+ - if (\function_exists('hrtime')) { - return $this->time = \hrtime(true) * 1e-9; - } + $this->useHighResolution = \function_exists('hrtime'); + } - return $this->time = \microtime(true) + 1000; + public function updateTime() + { + return $this->time = $this->useHighResolution ? \hrtime(true) * 1e-9 : \microtime(true); } public function getTime() From 4294607c4142d91b0b9be3a0d3d82452a1932a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 11 Nov 2018 23:29:07 +0100 Subject: [PATCH 09/85] Fix high CPU usage when only listening for signals with default loop --- src/StreamSelectLoop.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 3e6ff07f..4e9a3e4e 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -2,7 +2,6 @@ namespace React\EventLoop; -use React\EventLoop\Signal\Pcntl; use React\EventLoop\Tick\FutureTickQueue; use React\EventLoop\Timer\Timer; use React\EventLoop\Timer\Timers; @@ -258,12 +257,12 @@ private function waitForStreamActivity($timeout) * 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. + * @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 integer|false The total number of streams that are ready for read/write. - * Can return false if stream_select() is interrupted by a signal. + * @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) { @@ -274,7 +273,13 @@ private function streamSelect(array &$read, array &$write, $timeout) return @\stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); } - $timeout && \usleep($timeout); + 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; } From a0ecac955c67b57c40fe4a1b88a7cca1b58c982d Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 7 Feb 2019 17:19:49 +0100 Subject: [PATCH 10/85] Prepare v1.1.0 release Signed-off-by: Cees-Jan Kiewiet --- CHANGELOG.md | 17 +++++++++++++++++ README.md | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bd7f5f3..35cc703d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## 1.1.0 (2019-02-07) + +* New UV based event loop (ext-uv). + (#112 by @WyriHaximus) + +* Use high resolution timer on PHP 7.3+. + (#182 by @clue) + +* Improve PCNTL signals by using async signal dispatching if available. + (#179 by @CharlotteDunois) + +* Improve test suite and test suite set up. + (#174 by @WyriHaximus, #181 by @clue) + +* Fix PCNTL signals edge case. + (#183 by @clue) + ## 1.0.0 (2018-07-11) * First stable LTS release, now following [SemVer](https://semver.org/). diff --git a/README.md b/README.md index eb7bc195..029042e0 100644 --- a/README.md +++ b/README.md @@ -672,7 +672,7 @@ This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -$ composer require react/event-loop:^1.0 +$ composer require react/event-loop:^1.1 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From f80ace6d54dea5cfe34cc44a73d59533b7ff5334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 7 May 2019 21:24:38 +0200 Subject: [PATCH 11/85] Check PCNTL functions for signal support instead of PCNTL extension This helps avoiding problems when the PCNTL extension is loaded, but (some of) its functions are disabled via PHPs `disable_function` configuration. This is particularly common for the PCNTL functions in CGI environments, e.g. they are disabled by default on Debian- / Ubuntu-based distributions. Previously, even when not attaching any signal listeners, the loop would print a warning on each loop tick: > PHP Warning: pcntl_signal_dispatch() has been disabled for security reasons in {file} on {line} --- src/StreamSelectLoop.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index a11aca3e..3362d3e5 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -62,18 +62,19 @@ final class StreamSelectLoop implements LoopInterface private $writeListeners = array(); private $running; private $pcntl = false; - private $pcntlActive = false; + private $pcntlPoll = false; private $signals; public function __construct() { $this->futureTickQueue = new FutureTickQueue(); $this->timers = new Timers(); - $this->pcntl = \extension_loaded('pcntl'); - $this->pcntlActive = $this->pcntl && !\function_exists('pcntl_async_signals'); + $this->pcntl = \function_exists('pcntl_signal') && \function_exists('pcntl_signal_dispatch'); + $this->pcntlPoll = $this->pcntl && !\function_exists('pcntl_async_signals'); $this->signals = new SignalsHandler(); - if ($this->pcntl && !$this->pcntlActive) { + // 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); } } @@ -228,7 +229,7 @@ private function waitForStreamActivity($timeout) $write = $this->writeStreams; $available = $this->streamSelect($read, $write, $timeout); - if ($this->pcntlActive) { + if ($this->pcntlPoll) { \pcntl_signal_dispatch(); } if (false === $available) { From 429077cde7fa7128825c92fdfe110fae5c13b4c5 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 23 May 2019 07:47:51 +0200 Subject: [PATCH 12/85] Lock travis to xenial and keep older PHP versions to trusty --- .travis.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index fe9bd21c..45094a4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,8 @@ language: php php: # - 5.3 # requires old distro, see below - - 5.4 - - 5.5 +# - 5.4 # requires old distro, see below +# - 5.5 # requires old distro, see below - 5.6 - 7.0 - 7.1 @@ -12,12 +12,16 @@ php: # - hhvm # requires legacy phpunit & ignore errors, see below # lock distro so new future defaults will not break the build -dist: trusty +dist: xenial matrix: include: - php: 5.3 dist: precise + - php: 5.4 + dist: trusty + - php: 5.5 + dist: trusty - php: hhvm install: composer require phpunit/phpunit:^5 --dev --no-interaction allow_failures: From 5e66968a32f1a6843f7cf0b0e29224dbc07f9f11 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 23 May 2019 07:58:51 +0200 Subject: [PATCH 13/85] Install ext-uv on PHP 7.3 --- travis-init.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/travis-init.sh b/travis-init.sh index 9ea4e6f9..63ea758c 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -45,7 +45,8 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && # install 'libuv' PHP extension (does not support php 5) if [[ "$TRAVIS_PHP_VERSION" = "7.0" || "$TRAVIS_PHP_VERSION" = "7.1" || - "$TRAVIS_PHP_VERSION" = "7.2" ]]; then + "$TRAVIS_PHP_VERSION" = "7.2" || + "$TRAVIS_PHP_VERSION" = "7.3" ]]; then echo "yes" | pecl install uv-beta fi From 5d2e21969a8772b20fd7b5891bf7946999611a17 Mon Sep 17 00:00:00 2001 From: PabloKowalczyk <11366345+PabloKowalczyk@users.noreply.github.com> Date: Sat, 27 Jul 2019 08:20:31 +0200 Subject: [PATCH 14/85] Prevent "interval" overflow in ExtUvLoop --- src/ExtUvLoop.php | 29 ++++++++++++-- tests/AbstractLoopTest.php | 11 +++++- tests/ExtUvLoopTest.php | 78 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 4 deletions(-) diff --git a/src/ExtUvLoop.php b/src/ExtUvLoop.php index aade9943..70471b59 100644 --- a/src/ExtUvLoop.php +++ b/src/ExtUvLoop.php @@ -127,7 +127,7 @@ public function addTimer($interval, $callback) $this->timers->attach($timer, $event); \uv_timer_start( $event, - (int) ($interval * 1000) + 1, + $this->convertFloatSecondsToMilliseconds($interval), 0, $callback ); @@ -146,12 +146,13 @@ public function addPeriodicTimer($interval, $callback) \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, - (int) ($interval * 1000) + 1, - (int) ($interval * 1000) + 1, + $interval, + (int) $interval === 0 ? 1 : $interval, $callback ); @@ -313,4 +314,26 @@ private function createStreamListener() 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); + } } diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 83f5756b..a2d24513 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -589,11 +589,20 @@ public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop() $this->assertRunFasterThan(1.6); } + public function testTimerIntervalBelowZeroRunsImmediately() + { + $this->loop->addTimer(-1, function () {}); + + $this->assertRunFasterThan(0.002); + } + 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(PHP_INT_MAX, function () { }); + $timer = $this->loop->addTimer($interval, function () { }); $this->loop->futureTick(function () use ($timer, $loop) { $loop->cancelTimer($timer); diff --git a/tests/ExtUvLoopTest.php b/tests/ExtUvLoopTest.php index 61a94a9f..267eddf1 100644 --- a/tests/ExtUvLoopTest.php +++ b/tests/ExtUvLoopTest.php @@ -14,4 +14,82 @@ public function createLoop() 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.", + ), + ); + } } From a3165da14fcf467518445be4a0bd794d96cf5ce9 Mon Sep 17 00:00:00 2001 From: PabloKowalczyk <11366345+PabloKowalczyk@users.noreply.github.com> Date: Sat, 3 Aug 2019 09:55:15 +0200 Subject: [PATCH 15/85] Move "testTimerIntervalBelowZeroRunsImmediately" from AbstractLoopTest to AbstractTimerTest --- tests/AbstractLoopTest.php | 7 ------- tests/Timer/AbstractTimerTest.php | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index a2d24513..2e46b66e 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -589,13 +589,6 @@ public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop() $this->assertRunFasterThan(1.6); } - public function testTimerIntervalBelowZeroRunsImmediately() - { - $this->loop->addTimer(-1, function () {}); - - $this->assertRunFasterThan(0.002); - } - public function testTimerIntervalCanBeFarInFuture() { // Maximum interval for ExtUvLoop implementation diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index 11187b31..0f96c9fe 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -137,4 +137,22 @@ public function testMinimumIntervalOneMicrosecond() $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 + $this->assertLessThan(0.001, $end - $start); + } } From 42a01eece57e2555d342437308f81bf620e9290b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 2 Dec 2019 20:04:39 +0100 Subject: [PATCH 16/85] Fix Travis CI builds, do not install libuv on legacy PHP setups --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 45094a4a..be9cf010 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,12 +18,16 @@ matrix: include: - php: 5.3 dist: precise + before_install: [] # skip libuv - php: 5.4 dist: trusty + before_install: [] # skip libuv - php: 5.5 dist: trusty + before_install: [] # skip libuv - php: hhvm install: composer require phpunit/phpunit:^5 --dev --no-interaction + before_install: [] # skip libuv allow_failures: - php: hhvm @@ -41,7 +45,7 @@ cache: before_install: - sudo add-apt-repository ppa:ondrej/php -y - sudo apt-get update -q - - sudo apt-get install libuv1-dev || true + - sudo apt-get install libuv1-dev install: - ./travis-init.sh From a949df7f87a663c84aba4d0bd35a386ca7818618 Mon Sep 17 00:00:00 2001 From: Sam Reed Date: Sun, 1 Dec 2019 01:59:57 +0000 Subject: [PATCH 17/85] Add .gitattributes to exclude dev files from exports --- .gitattributes | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..e15a4094 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/examples export-ignore +/phpunit.xml.dist export-ignore +/tests export-ignore +/travis-init.sh export-ignore From 858b18a37f72af171268ecec3f5ffdf81400748d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 21 Dec 2019 15:20:52 +0100 Subject: [PATCH 18/85] Fix failing test cases due to inaccurate timers --- tests/Timer/AbstractTimerTest.php | 47 ++++++++++--------------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index 0f96c9fe..cd53bd13 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -22,38 +22,21 @@ public function testAddTimerReturnsNonPeriodicTimerInstance() $this->assertFalse($timer->isPeriodic()); } - /** - * @depends testPlatformHasHighAccuracy - */ public function testAddTimerWillBeInvokedOnceAndBlocksLoopWhenRunning() { - // Make no strict assumptions about actual time interval. Common - // environments usually provide millisecond accuracy (or better), but - // Travis and other CI systems are slow. - // We try to compensate for this by skipping accurate tests when the - // current platform is known to be inaccurate. We test this by sleeping - // 3x1ms and then measure the time for each iteration before running the - // actual test. - for ($i = 0; $i < 3; ++$i) { - $start = microtime(true); - usleep(1000); - $time = microtime(true) - $start; - - if ($time < 0.001 || $time > 0.002) { - $this->markTestSkipped('Platform provides insufficient accuracy (' . $time . ' s)'); - } - } - $loop = $this->createLoop(); - $loop->addTimer(0.001, $this->expectCallableOnce()); + $loop->addTimer(0.002, $this->expectCallableOnce()); $start = microtime(true); $loop->run(); $end = microtime(true); + // 1 invocation should take 2ms (± 1ms 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.002, $end - $start); + $this->assertLessThan(0.1, $end - $start); } public function testAddPeriodicTimerReturnsPeriodicTimerInstance() @@ -90,17 +73,17 @@ public function testAddPeriodicTimerWillBeInvokedWithMaximumAccuracyUntilItIsCan ++$i; }); - $loop->addTimer(0.02, function () use ($loop, $periodic) { + $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 20 times and should at least be - // invoked twice for really slow loops - $this->assertLessThanOrEqual(20, $i); - $this->assertGreaterThan(2, $i); + // 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() @@ -122,11 +105,11 @@ public function testAddPeriodicTimerCancelsItself() $this->assertEquals(5, $i); - // make no strict assumptions about time interval. - // 5 invocations must take at least 0.005s (5ms) and should not take - // longer than 0.1s for slower loops. - $this->assertGreaterThanOrEqual(0.005, $end - $start); - $this->assertLessThan(0.1, $end - $start); + // 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() From 131d92dcf7397ee4b93b268f7248c1ce20df99b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 23 Dec 2019 20:12:06 +0100 Subject: [PATCH 19/85] Run tests on Windows via Travis CI --- .travis.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.travis.yml b/.travis.yml index be9cf010..3329ead7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,8 +28,18 @@ matrix: - php: hhvm install: composer require phpunit/phpunit:^5 --dev --no-interaction before_install: [] # skip libuv + - name: "Windows" + os: windows + language: shell # no built-in php support + before_install: + - choco install php + - choco install composer + - export PATH="$(powershell -Command '("Process", "Machine" | % { [Environment]::GetEnvironmentVariable("PATH", $_) -Split ";" -Replace "\\$", "" } | Select -Unique | % { cygpath $_ }) -Join ":"')" + install: + - composer install allow_failures: - php: hhvm + - os: windows sudo: false From 2f85e8eeffc6c4977a8688e7faab73022cb02db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 23 Dec 2019 22:13:32 +0100 Subject: [PATCH 20/85] Fix unsupported EventConfig for ext-event on Windows --- .travis.yml | 14 ++++++++++++++ src/ExtEventLoop.php | 10 +++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3329ead7..7182febc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,6 +37,20 @@ matrix: - export PATH="$(powershell -Command '("Process", "Machine" | % { [Environment]::GetEnvironmentVariable("PATH", $_) -Split ";" -Replace "\\$", "" } | Select -Unique | % { cygpath $_ }) -Join ":"')" install: - composer install + - name: "Windows PHP 7.2 with ext-event" + os: windows + language: shell # no built-in php support + before_install: + - curl -OL https://windows.php.net/downloads/pecl/releases/event/2.5.3/php_event-2.5.3-7.2-nts-vc15-x64.zip # latest version as of 2019-12-23 + - choco install php --version=7.2.26 # latest version supported by ext-event as of 2019-12-23 + - choco install composer + - export PATH="$(powershell -Command '("Process", "Machine" | % { [Environment]::GetEnvironmentVariable("PATH", $_) -Split ";" -Replace "\\$", "" } | Select -Unique | % { cygpath $_ }) -Join ":"')" + - php -r "\$z=new ZipArchive();\$z->open(glob('php_event*.zip')[0]);\$z->extractTo(dirname(php_ini_loaded_file()).'/ext','php_event.dll');" + - php -r "file_put_contents(php_ini_loaded_file(),'extension_dir=ext'.PHP_EOL,FILE_APPEND);" + - php -r "file_put_contents(php_ini_loaded_file(),'extension=sockets'.PHP_EOL,FILE_APPEND);" # ext-sockets needs to be loaded before ext-event + - php -r "file_put_contents(php_ini_loaded_file(),'extension=event'.PHP_EOL,FILE_APPEND);" + install: + - composer install allow_failures: - php: hhvm - os: windows diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index fd403d4a..fdf6d5fa 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -5,7 +5,6 @@ use BadMethodCallException; use Event; use EventBase; -use EventConfig as EventBaseConfig; use React\EventLoop\Tick\FutureTickQueue; use React\EventLoop\Timer\Timer; use SplObjectStorage; @@ -43,8 +42,13 @@ public function __construct() throw new BadMethodCallException('Cannot create ExtEventLoop, ext-event extension missing'); } - $config = new EventBaseConfig(); - $config->requireFeatures(EventBaseConfig::FEATURE_FDS); + // 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(); From 47ab46ffe61a2d817df828b04892542efdc1be0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 27 Dec 2019 20:38:25 +0100 Subject: [PATCH 21/85] Ignore unrelated SEGFAULT when all tests pass for ext-event on Windows --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 7182febc..944d6ba2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,6 +51,8 @@ matrix: - php -r "file_put_contents(php_ini_loaded_file(),'extension=event'.PHP_EOL,FILE_APPEND);" install: - composer install + script: + - vendor/bin/phpunit --coverage-text || ([[ $? = 139 ]] && echo && echo "Ignoring SEGFAULT.." >&2) # ignore unrelated SEGFAULT when all tests pass allow_failures: - php: hhvm - os: windows From 3698022d03d8adc964e696d884b6519aad5d8a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 27 Dec 2019 21:24:25 +0100 Subject: [PATCH 22/85] Avoid SEGFAULTs for ext-event on Windows by clearing all Event refs --- .travis.yml | 2 -- src/ExtEventLoop.php | 11 +++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 944d6ba2..7182febc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,8 +51,6 @@ matrix: - php -r "file_put_contents(php_ini_loaded_file(),'extension=event'.PHP_EOL,FILE_APPEND);" install: - composer install - script: - - vendor/bin/phpunit --coverage-text || ([[ $? = 139 ]] && echo && echo "Ignoring SEGFAULT.." >&2) # ignore unrelated SEGFAULT when all tests pass allow_failures: - php: hhvm - os: windows diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index fdf6d5fa..1f1b9ea4 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -59,6 +59,17 @@ public function __construct() $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; From 06ecbb73accd87ac66a3fd2e9dc251e41c440628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 24 Dec 2019 11:36:16 +0100 Subject: [PATCH 23/85] Add TCP/IP connection tests --- tests/AbstractLoopTest.php | 106 +++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 2e46b66e..127f2bb7 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -2,6 +2,8 @@ namespace React\Tests\EventLoop; +use React\EventLoop\StreamSelectLoop; + abstract class AbstractLoopTest extends TestCase { /** @@ -36,6 +38,110 @@ public function createSocketPair() 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() + { + // @link https://github.com/reactphp/event-loop/issues/206 + if ($this->loop instanceof StreamSelectLoop && DIRECTORY_SEPARATOR === '\\') { + $this->markTestIncomplete('StreamSelectLoop does not currently support detecting connection refused errors on Windows'); + } + + // 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 || $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() { list ($input, $output) = $this->createSocketPair(); From 527c60af4d4fbd9fc76a6a227ed18dbd264ef9fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 23 Dec 2019 22:23:09 +0100 Subject: [PATCH 24/85] Test ext-uv on Windows --- .travis.yml | 14 +++++++++++ tests/AbstractLoopTest.php | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/.travis.yml b/.travis.yml index 7182febc..1cd43c2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,6 +51,20 @@ matrix: - php -r "file_put_contents(php_ini_loaded_file(),'extension=event'.PHP_EOL,FILE_APPEND);" install: - composer install + - name: "Windows PHP 7.4 with ext-uv" + os: windows + language: shell # no built-in php support + before_install: + - curl -OL https://windows.php.net/downloads/pecl/releases/uv/0.2.4/php_uv-0.2.4-7.4-nts-vc15-x64.zip # latest version as of 2019-12-23 + - choco install php --version=7.4.0 # latest version supported by ext-uv as of 2019-12-23 + - choco install composer + - export PATH="$(powershell -Command '("Process", "Machine" | % { [Environment]::GetEnvironmentVariable("PATH", $_) -Split ";" -Replace "\\$", "" } | Select -Unique | % { cygpath $_ }) -Join ":"')" + - php -r "\$z=new ZipArchive();\$z->open(glob('php_uv*.zip')[0]);\$z->extractTo(dirname(php_ini_loaded_file()).'/ext','php_uv.dll');\$z->extractTo(dirname(php_ini_loaded_file()),'libuv.dll');" + - php -r "file_put_contents(php_ini_loaded_file(),'extension_dir=ext'.PHP_EOL,FILE_APPEND);" + - php -r "file_put_contents(php_ini_loaded_file(),'extension=sockets'.PHP_EOL,FILE_APPEND);" # ext-sockets needs to be loaded before ext-uv + - php -r "file_put_contents(php_ini_loaded_file(),'extension=uv'.PHP_EOL,FILE_APPEND);" + install: + - composer install allow_failures: - php: hhvm - os: windows diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 127f2bb7..44157d2c 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -3,6 +3,7 @@ namespace React\Tests\EventLoop; use React\EventLoop\StreamSelectLoop; +use React\EventLoop\ExtUvLoop; abstract class AbstractLoopTest extends TestCase { @@ -144,6 +145,10 @@ public function testAddWriteStreamTriggersWhenSocketConnectionRefused() 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)); @@ -157,6 +162,10 @@ public function testAddReadStream() 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)); @@ -206,6 +215,10 @@ private function subAddReadStreamReceivesDataFromStreamReference() 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)); @@ -215,6 +228,10 @@ public function testAddWriteStream() 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)); @@ -225,6 +242,10 @@ public function testAddWriteStreamIgnoresSecondCallable() 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()); @@ -236,6 +257,10 @@ public function testRemoveReadStreamInstantly() 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()); @@ -251,6 +276,10 @@ public function testRemoveReadStreamAfterReading() 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()); @@ -260,6 +289,10 @@ public function testRemoveWriteStreamInstantly() 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()); @@ -271,6 +304,10 @@ public function testRemoveWriteStreamAfterWriting() 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()); @@ -283,6 +320,10 @@ public function testRemoveStreamForReadOnly() 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"); @@ -505,6 +546,10 @@ public function testFutureTick() 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( @@ -525,6 +570,9 @@ function () { $this->tickLoop($this->loop); } + /** + * @depends testFutureTickFiresBeforeIO + */ public function testRecursiveFutureTick() { list ($stream) = $this->createSocketPair(); From 6d45a1935111d34f2f39d1490edc45cb2a7f97c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 29 Dec 2019 20:44:44 +0100 Subject: [PATCH 25/85] Fix reporting refused connections with ExtUvLoop The underlying `epoll_wait()` reports `EPOLLOUT|EPOLLERR|EPOLLHUP` on the affected file descriptor, which `ext-uv` emits as an error code `EBADF` with no events attached. We explicitly re-enable all active events on this error event to invoke the writable listener for this condition to match other event loop implementations and successfully detect this as a refused connection attempt. All tests are now green. --- src/ExtUvLoop.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/ExtUvLoop.php b/src/ExtUvLoop.php index 70471b59..002d6a2e 100644 --- a/src/ExtUvLoop.php +++ b/src/ExtUvLoop.php @@ -294,13 +294,15 @@ private function pollStream($stream) private function createStreamListener() { $callback = function ($event, $status, $events, $stream) { - if (!isset($this->streamEvents[(int) $stream])) { - return; - } - - if (($events | 4) === 4) { - // Disconnected - return; + // 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)) { From 6a894465c3f974fb26f153f79dab0d4909e42bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 30 Dec 2019 20:59:03 +0100 Subject: [PATCH 26/85] Fix reporting refused connections with StreamSelectLoop on Windows 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. See also https://docs.microsoft.com/de-de/windows/win32/api/winsock2/nf-winsock2-select 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. --- src/StreamSelectLoop.php | 22 +++++++++++++++++++++- tests/AbstractLoopTest.php | 7 +------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 3362d3e5..4426844a 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -269,10 +269,30 @@ private function waitForStreamActivity($timeout) 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 - return @\stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); + $ret = @\stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); + + if ($except) { + $write = \array_merge($write, $except); + } + return $ret; } if ($timeout > 0) { diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 44157d2c..9b55f959 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -111,16 +111,11 @@ public function testAddWriteStreamTriggersWhenSocketConnectionSucceeds() public function testAddWriteStreamTriggersWhenSocketConnectionRefused() { - // @link https://github.com/reactphp/event-loop/issues/206 - if ($this->loop instanceof StreamSelectLoop && DIRECTORY_SEPARATOR === '\\') { - $this->markTestIncomplete('StreamSelectLoop does not currently support detecting connection refused errors on Windows'); - } - // 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 || $errno !== SOCKET_ECONNREFUSED) { + 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); } From b98bb2f4a1ec8780bf7219a4a0e7803ba5e00ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 31 Dec 2019 11:52:18 +0100 Subject: [PATCH 27/85] Run tests on PHP 7.4 and simply test matrix and test setup --- .travis.yml | 25 ++++++++--------- travis-init.sh | 75 ++++++++++++++++++++------------------------------ 2 files changed, 41 insertions(+), 59 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1cd43c2b..be4e6a06 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,5 @@ language: php -php: -# - 5.3 # requires old distro, see below -# - 5.4 # requires old distro, see below -# - 5.5 # requires old distro, see below - - 5.6 - - 7.0 - - 7.1 - - 7.2 - - 7.3 -# - hhvm # requires legacy phpunit & ignore errors, see below - # lock distro so new future defaults will not break the build dist: xenial @@ -25,9 +14,17 @@ matrix: - php: 5.5 dist: trusty before_install: [] # skip libuv - - php: hhvm - install: composer require phpunit/phpunit:^5 --dev --no-interaction + - php: 5.6 + - php: 7.0 + - php: 7.1 + - php: 7.2 + - php: 7.3 + - php: 7.4 + - php: hhvm-3.18 + dist: trusty before_install: [] # skip libuv + install: + - composer require phpunit/phpunit:^5 --dev --no-interaction # requires legacy phpunit & skip ./travis-init.sh - name: "Windows" os: windows language: shell # no built-in php support @@ -66,7 +63,7 @@ matrix: install: - composer install allow_failures: - - php: hhvm + - php: hhvm-3.18 - os: windows sudo: false diff --git a/travis-init.sh b/travis-init.sh index 63ea758c..94ec4f36 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -2,52 +2,37 @@ set -e set -o pipefail -if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && - "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]]; then - - # install 'event' and 'ev' PHP extension - if [[ "$TRAVIS_PHP_VERSION" != "5.3" && - "$TRAVIS_PHP_VERSION" != "7.3" ]]; then - echo "yes" | pecl install event - echo "yes" | pecl install ev - fi - - # install 'libevent' PHP extension (does not support php 7) - if [[ "$TRAVIS_PHP_VERSION" != "7.0" && - "$TRAVIS_PHP_VERSION" != "7.1" && - "$TRAVIS_PHP_VERSION" != "7.2" && - "$TRAVIS_PHP_VERSION" != "7.3" ]]; 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 'event' and 'ev' PHP extension on PHP 5.4+ only +if [[ "$TRAVIS_PHP_VERSION" != "5.3" ]]; then + echo "yes" | pecl install event + echo "yes" | pecl install ev +fi - # install 'libev' PHP extension (does not support php 7) - if [[ "$TRAVIS_PHP_VERSION" != "7.0" && - "$TRAVIS_PHP_VERSION" != "7.1" && - "$TRAVIS_PHP_VERSION" != "7.2" && - "$TRAVIS_PHP_VERSION" != "7.3" ]]; 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 +# install 'libevent' PHP extension on legacy PHP 5 only +if [[ "$TRAVIS_PHP_VERSION" < "7.0" ]]; 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 'libuv' PHP extension (does not support php 5) - if [[ "$TRAVIS_PHP_VERSION" = "7.0" || - "$TRAVIS_PHP_VERSION" = "7.1" || - "$TRAVIS_PHP_VERSION" = "7.2" || - "$TRAVIS_PHP_VERSION" = "7.3" ]]; then - echo "yes" | pecl install uv-beta - fi +# install 'libev' PHP extension on legacy PHP 5 only +if [[ "$TRAVIS_PHP_VERSION" < "7.0" ]]; 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 +# install 'libuv' PHP extension on PHP 7+ only +if ! [[ "$TRAVIS_PHP_VERSION" < "7.0" ]]; then + echo "yes" | pecl install uv-beta fi From 6d24de090cd59cfc830263cfba965be77b563c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 1 Jan 2020 19:39:52 +0100 Subject: [PATCH 28/85] Prepare v1.1.1 release --- CHANGELOG.md | 21 +++++++++++++++++++++ README.md | 4 ++-- src/StreamSelectLoop.php | 4 ++-- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35cc703d..502996b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## 1.1.1 (2020-01-01) + +* Fix: Fix reporting connection refused errors with `ExtUvLoop` on Linux and `StreamSelectLoop` on Windows. + (#207 and #208 by @clue) + +* Fix: Fix unsupported EventConfig and `SEGFAULT` on shutdown with `ExtEventLoop` on Windows. + (#205 by @clue) + +* Fix: Check PCNTL functions for signal support instead of PCNTL extension with `StreamSelectLoop`. + (#195 by @clue) + +* Add `.gitattributes` to exclude dev files from exports. + (#201 by @reedy) + +* Improve test suite to fix testing `ExtUvLoop` on Travis, + fix Travis CI builds, do not install `libuv` on legacy PHP setups, + fix failing test cases due to inaccurate timers, + run tests on Windows via Travis CI and + run tests on PHP 7.4 and simplify test matrix and test setup. + (#197 by @WyriHaximus and #202, #203, #204 and #209 by @clue) + ## 1.1.0 (2019-02-07) * New UV based event loop (ext-uv). diff --git a/README.md b/README.md index 029042e0..cdff1c88 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ event loop implementation first or they will throw a `BadMethodCallException` on A `stream_select()` based event loop. -This uses the [`stream_select()`](http://php.net/manual/en/function.stream-select.php) +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.3 through PHP 7+ and HHVM. @@ -672,7 +672,7 @@ This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -$ composer require react/event-loop:^1.1 +$ composer require react/event-loop:^1.1.1 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 4426844a..b89d8000 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -9,7 +9,7 @@ /** * A `stream_select()` based event loop. * - * This uses the [`stream_select()`](http://php.net/manual/en/function.stream-select.php) + * 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. @@ -47,7 +47,7 @@ * then adjust your system time forward by 20s, the timer may trigger in 10s. * See also [`addTimer()`](#addtimer) for more details. * - * @link http://php.net/manual/en/function.stream-select.php + * @link https://www.php.net/manual/en/function.stream-select.php */ final class StreamSelectLoop implements LoopInterface { From 88aaa77ad3ffce3ff22f1361d88c22ec85a50e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 1 Jan 2020 19:54:56 +0100 Subject: [PATCH 29/85] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 502996b7..9d571cfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ * Fix: Fix unsupported EventConfig and `SEGFAULT` on shutdown with `ExtEventLoop` on Windows. (#205 by @clue) +* Fix: Prevent interval overflow for timers very far in the future with `ExtUvLoop`. + (#196 by @PabloKowalczyk) + * Fix: Check PCNTL functions for signal support instead of PCNTL extension with `StreamSelectLoop`. (#195 by @clue) From 02c6c530e9f5c93c0f25d8655de261f5ac44c854 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Fri, 17 Jul 2020 11:24:19 +0200 Subject: [PATCH 30/85] Run tests on PHPUnit 9 --- composer.json | 2 +- tests/AbstractLoopTest.php | 9 ++++++++- tests/CallableStub.php | 10 ---------- tests/ExtLibeventLoopTest.php | 5 ++++- tests/StreamSelectLoopTest.php | 5 ++++- tests/TestCase.php | 8 +++++++- 6 files changed, 24 insertions(+), 15 deletions(-) delete mode 100644 tests/CallableStub.php diff --git a/composer.json b/composer.json index cc6abf06..81cf1017 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.0 || ^5.7 || ^4.8.35" }, "suggest": { "ext-event": "~1.0 for ExtEventLoop", diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 9b55f959..292ffd1a 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -16,7 +16,10 @@ abstract class AbstractLoopTest extends TestCase const PHP_DEFAULT_CHUNK_SIZE = 8192; - public function setUp() + /** + * @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; @@ -111,6 +114,10 @@ public function testAddWriteStreamTriggersWhenSocketConnectionSucceeds() 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 diff --git a/tests/CallableStub.php b/tests/CallableStub.php deleted file mode 100644 index 913d403a..00000000 --- a/tests/CallableStub.php +++ /dev/null @@ -1,10 +0,0 @@ -fifoPath)) { unlink($this->fifoPath); diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index 2eb388f0..d6800c5e 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -7,7 +7,10 @@ class StreamSelectLoopTest extends AbstractLoopTest { - protected function tearDown() + /** + * @after + */ + protected function tearDownSignalHandlers() { parent::tearDown(); if (strncmp($this->getName(false), 'testSignal', 10) === 0 && extension_loaded('pcntl')) { diff --git a/tests/TestCase.php b/tests/TestCase.php index dbdd54ce..69b3b227 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -39,7 +39,13 @@ protected function expectCallableNever() protected function createCallableMock() { - return $this->getMockBuilder('React\Tests\EventLoop\CallableStub')->getMock(); + 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) From 14e799d9bf22cfc8a7a27a79c738dbe1fd61a2cd Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Fri, 17 Jul 2020 11:25:15 +0200 Subject: [PATCH 31/85] Clean up test suite --- .travis.yml | 6 ++---- phpunit.xml.dist | 11 +---------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index be4e6a06..4a99e2a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: php # lock distro so new future defaults will not break the build dist: xenial -matrix: +jobs: include: - php: 5.3 dist: precise @@ -24,7 +24,7 @@ matrix: dist: trusty before_install: [] # skip libuv install: - - composer require phpunit/phpunit:^5 --dev --no-interaction # requires legacy phpunit & skip ./travis-init.sh + - composer install # skip ./travis-init.sh - name: "Windows" os: windows language: shell # no built-in php support @@ -66,8 +66,6 @@ matrix: - php: hhvm-3.18 - os: windows -sudo: false - addons: apt: packages: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 04d426b5..0e947b87 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,15 +1,6 @@ - + ./tests/ From 7b6168bb8287ac88eebd54b1d25a48abaa5da99b Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 24 Aug 2020 18:57:22 +0200 Subject: [PATCH 32/85] Add full core team to the license Added the full core team in order of joining the team --- LICENSE | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index a808108c..d6f8901f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,6 @@ -Copyright (c) 2012 Igor Wiedler, Chris Boden +The MIT License (MIT) + +Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 9fec6d43b42689d5bfb75004d439492969881f5e Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 24 Aug 2020 19:47:28 +0200 Subject: [PATCH 33/85] Add full core team to composer authors list --- composer.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/composer.json b/composer.json index 81cf1017..d4d50398 100644 --- a/composer.json +++ b/composer.json @@ -3,6 +3,28 @@ "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", "keywords": ["event-loop", "asynchronous"], "license": "MIT", + "authors": [ + { + "name": "Christian Lück", + "homepage": "https://clue.engineering/", + "email": "christian@clue.engineering" + }, + { + "name": "Cees-Jan Kiewiet", + "homepage": "https://wyrihaximus.net/", + "email": "reactphp@ceesjankiewiet.nl" + }, + { + "name": "Jan Sorgalla", + "homepage": "https://sorgalla.com/", + "email": "jsorgalla@gmail.com" + }, + { + "name": "Chris Boden", + "homepage": "https://cboden.dev/", + "email": "cboden@gmail.com" + } + ], "require": { "php": ">=5.3.0" }, From 84e22e0e47cef46ff981c0b900005423e1118528 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Wed, 9 Sep 2020 12:45:08 +0200 Subject: [PATCH 34/85] Update PHPUnit configuration schema for PHPUnit 9.3 --- .gitattributes | 1 + .travis.yml | 3 ++- composer.json | 2 +- phpunit.xml.dist | 18 +++++++++++------- phpunit.xml.legacy | 18 ++++++++++++++++++ 5 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 phpunit.xml.legacy diff --git a/.gitattributes b/.gitattributes index e15a4094..ce7c8e68 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,5 +3,6 @@ /.travis.yml export-ignore /examples export-ignore /phpunit.xml.dist export-ignore +/phpunit.xml.legacy export-ignore /tests export-ignore /travis-init.sh export-ignore diff --git a/.travis.yml b/.travis.yml index 4a99e2a1..e3ac120a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -85,4 +85,5 @@ install: - composer install script: - - ./vendor/bin/phpunit --coverage-text + - if [[ "$TRAVIS_PHP_VERSION" > "7.2" ]]; then vendor/bin/phpunit --coverage-text; fi + - if [[ "$TRAVIS_PHP_VERSION" < "7.3" ]]; then vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy; fi diff --git a/composer.json b/composer.json index d4d50398..d9b032e1 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^9.0 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" }, "suggest": { "ext-event": "~1.0 for ExtEventLoop", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0e947b87..bfdeecf6 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,15 +1,19 @@ - + + - + ./tests/ - - - + + ./src/ - - + + diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy new file mode 100644 index 00000000..632fccd1 --- /dev/null +++ b/phpunit.xml.legacy @@ -0,0 +1,18 @@ + + + + + + + ./tests/ + + + + + ./src/ + + + From b6e704ab8efd0f22de9a6827f55a15a5e0e516d3 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 5 Apr 2021 12:58:37 +0200 Subject: [PATCH 35/85] Global event-loop accessor part four --- README.md | 36 ++++++++++++--- examples/01-timers.php | 10 ++-- examples/02-periodic.php | 12 ++--- examples/03-ticks.php | 10 ++-- examples/04-signals.php | 10 ++-- examples/11-consume-stdin.php | 10 ++-- examples/12-generate-yes.php | 10 ++-- examples/13-http-client-blocking.php | 10 ++-- examples/14-http-client-async.php | 17 ++++--- examples/21-http-server.php | 14 +++--- examples/91-benchmark-ticks.php | 8 ++-- examples/92-benchmark-timers.php | 8 ++-- examples/93-benchmark-ticks-delay.php | 10 ++-- examples/94-benchmark-timers-delay.php | 10 ++-- examples/95-benchmark-memory.php | 23 ++++----- src/Factory.php | 31 +++++++++++-- src/Loop.php | 50 ++++++++++++++++++++ tests/LoopTest.php | 64 ++++++++++++++++++++++++++ 18 files changed, 246 insertions(+), 97 deletions(-) create mode 100644 src/Loop.php create mode 100644 tests/LoopTest.php diff --git a/README.md b/README.md index cdff1c88..402309b1 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ single [`run()`](#run) call that is controlled by the user. * [Quickstart example](#quickstart-example) * [Usage](#usage) + * [Loop](#loop) + * [get()](#get) * [Factory](#factory) * [create()](#create) * [Loop implementations](#loop-implementations) @@ -45,32 +47,32 @@ single [`run()`](#run) call that is controlled by the user. Here is an async HTTP server built with just the event loop. ```php -$loop = React\EventLoop\Factory::create(); +use React\EventLoop\Loop; $server = stream_socket_server('tcp://127.0.0.1:8080'); stream_set_blocking($server, false); -$loop->addReadStream($server, function ($server) use ($loop) { +Loop::get()->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, $loop) { + Loop::get()->addWriteStream($conn, function ($conn) use (&$data) { $written = fwrite($conn, $data); if ($written === strlen($data)) { fclose($conn); - $loop->removeWriteStream($conn); + Loop::get()->removeWriteStream($conn); } else { $data = substr($data, $written); } }); }); -$loop->addPeriodicTimer(5, function () { +Loop::get()->addPeriodicTimer(5, function () { $memory = memory_get_usage() / 1024; $formatted = number_format($memory, 3).'K'; echo "Current memory usage: {$formatted}\n"; }); -$loop->run(); +Loop::get()->run(); ``` See also the [examples](examples). @@ -110,6 +112,28 @@ $loop->run(); purposes. 3. The loop is run with a single [`$loop->run()`](#run) call at the end of the program. +### Loop + +The `Loop` class exists as a convenient global accessor for the event loop. + +#### get() + +The `get(): LoopInterface` method is the preferred way to get and use the event loop. With +it there is no need to always pass the loop around anymore. + +```php +use React\EventLoop\Loop; + +Loop::get()->addTimer(0.02, function () { + echo 'World!'; +}); +Loop::get()->addTimer(0.01, function () { + echo 'Hello '; +}); + +Loop::get()->run(); +``` + ### Factory The `Factory` class exists as a convenient way to pick the best available diff --git a/examples/01-timers.php b/examples/01-timers.php index e6107e46..5be2f3e2 100644 --- a/examples/01-timers.php +++ b/examples/01-timers.php @@ -1,15 +1,15 @@ addTimer(0.8, function () { +Loop::get()->addTimer(0.8, function () { echo 'world!' . PHP_EOL; }); -$loop->addTimer(0.3, function () { +Loop::get()->addTimer(0.3, function () { echo 'hello '; }); -$loop->run(); +Loop::get()->run(); diff --git a/examples/02-periodic.php b/examples/02-periodic.php index 5e138a62..6f549055 100644 --- a/examples/02-periodic.php +++ b/examples/02-periodic.php @@ -1,16 +1,16 @@ addPeriodicTimer(0.1, function () { +$timer = Loop::get()->addPeriodicTimer(0.1, function () { echo 'tick!' . PHP_EOL; }); -$loop->addTimer(1.0, function () use ($loop, $timer) { - $loop->cancelTimer($timer); +Loop::get()->addTimer(1.0, function () use ($timer) { + Loop::get()->cancelTimer($timer); echo 'Done' . PHP_EOL; }); -$loop->run(); +Loop::get()->run(); diff --git a/examples/03-ticks.php b/examples/03-ticks.php index 3f36c6d4..0eee59bd 100644 --- a/examples/03-ticks.php +++ b/examples/03-ticks.php @@ -1,15 +1,15 @@ futureTick(function () { +Loop::get()->futureTick(function () { echo 'b'; }); -$loop->futureTick(function () { +Loop::get()->futureTick(function () { echo 'c'; }); echo 'a'; -$loop->run(); +Loop::get()->run(); diff --git a/examples/04-signals.php b/examples/04-signals.php index 90b68989..dc7e0293 100644 --- a/examples/04-signals.php +++ b/examples/04-signals.php @@ -1,5 +1,7 @@ addSignal(SIGINT, $func = function ($signal) use ($loop, &$func) { +Loop::get()->addSignal(SIGINT, $func = function ($signal) use (&$func) { echo 'Signal: ', (string)$signal, PHP_EOL; - $loop->removeSignal(SIGINT, $func); + Loop::get()->removeSignal(SIGINT, $func); }); echo 'Listening for SIGINT. Use "kill -SIGINT ' . getmypid() . '" or CTRL+C' . PHP_EOL; -$loop->run(); +Loop::get()->run(); diff --git a/examples/11-consume-stdin.php b/examples/11-consume-stdin.php index 2a772455..146a55eb 100644 --- a/examples/11-consume-stdin.php +++ b/examples/11-consume-stdin.php @@ -1,6 +1,6 @@ addReadStream(STDIN, function ($stream) use ($loop) { +Loop::get()->addReadStream(STDIN, function ($stream) { $chunk = fread($stream, 64 * 1024); // reading nothing means we reached EOF if ($chunk === '') { - $loop->removeReadStream($stream); + Loop::get()->removeReadStream($stream); stream_set_blocking($stream, true); fclose($stream); return; @@ -27,4 +25,4 @@ echo strlen($chunk) . ' bytes' . PHP_EOL; }); -$loop->run(); +Loop::get()->run(); diff --git a/examples/12-generate-yes.php b/examples/12-generate-yes.php index ebc2beb4..b0da7c60 100644 --- a/examples/12-generate-yes.php +++ b/examples/12-generate-yes.php @@ -1,5 +1,7 @@ addWriteStream(STDOUT, function ($stdout) use ($loop, &$data) { +Loop::get()->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); + Loop::get()->removeWriteStream($stdout); fclose($stdout); stream_set_blocking($stdout, true); fwrite(STDERR, 'Stopped because STDOUT closed' . PHP_EOL); @@ -38,4 +38,4 @@ } }); -$loop->run(); +Loop::get()->run(); diff --git a/examples/13-http-client-blocking.php b/examples/13-http-client-blocking.php index a2dde55c..eb34b24d 100644 --- a/examples/13-http-client-blocking.php +++ b/examples/13-http-client-blocking.php @@ -1,11 +1,9 @@ addReadStream($stream, function ($stream) use ($loop) { +Loop::get()->addReadStream($stream, function ($stream) { $chunk = fread($stream, 64 * 1024); // reading nothing means we reached EOF if ($chunk === '') { echo '[END]' . PHP_EOL; - $loop->removeReadStream($stream); + Loop::get()->removeReadStream($stream); fclose($stream); return; } @@ -32,4 +30,4 @@ echo $chunk; }); -$loop->run(); +Loop::get()->run(); diff --git a/examples/14-http-client-async.php b/examples/14-http-client-async.php index c82c9887..2e3e7e35 100644 --- a/examples/14-http-client-async.php +++ b/examples/14-http-client-async.php @@ -1,11 +1,10 @@ addPeriodicTimer(0.01, function () { +$timer = Loop::get()->addPeriodicTimer(0.01, function () { echo '.'; }); // wait for connection success/error -$loop->addWriteStream($stream, function ($stream) use ($loop, $timer) { - $loop->removeWriteStream($stream); - $loop->cancelTimer($timer); +Loop::get()->addWriteStream($stream, function ($stream) use ($timer) { + Loop::get()->removeWriteStream($stream); + Loop::get()->cancelTimer($timer); // check for socket error (connection rejected) if (stream_socket_get_name($stream, true) === false) { @@ -45,13 +44,13 @@ 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) use ($loop) { + Loop::get()->addReadStream($stream, function ($stream) { $chunk = fread($stream, 64 * 1024); // reading nothing means we reached EOF if ($chunk === '') { echo '[END]' . PHP_EOL; - $loop->removeReadStream($stream); + Loop::get()->removeReadStream($stream); fclose($stream); return; } @@ -60,4 +59,4 @@ }); }); -$loop->run(); +Loop::get()->run(); diff --git a/examples/21-http-server.php b/examples/21-http-server.php index 89520cec..4e238158 100644 --- a/examples/21-http-server.php +++ b/examples/21-http-server.php @@ -1,8 +1,8 @@ addReadStream($server, function ($server) use ($loop) { +Loop::get()->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, $loop) { + Loop::get()->addWriteStream($conn, function ($conn) use (&$data) { $written = fwrite($conn, $data); if ($written === strlen($data)) { fclose($conn); - $loop->removeWriteStream($conn); + Loop::get()->removeWriteStream($conn); } else { $data = substr($data, $written); } }); }); -$loop->addPeriodicTimer(5, function () { +Loop::get()->addPeriodicTimer(5, function () { $memory = memory_get_usage() / 1024; $formatted = number_format($memory, 3).'K'; echo "Current memory usage: {$formatted}\n"; }); -$loop->run(); +Loop::get()->run(); diff --git a/examples/91-benchmark-ticks.php b/examples/91-benchmark-ticks.php index 3f4690b3..8a768568 100644 --- a/examples/91-benchmark-ticks.php +++ b/examples/91-benchmark-ticks.php @@ -1,15 +1,13 @@ futureTick(function () { }); + Loop::get()->futureTick(function () { }); } -$loop->run(); +Loop::get()->run(); diff --git a/examples/92-benchmark-timers.php b/examples/92-benchmark-timers.php index e2e02e49..51cb9596 100644 --- a/examples/92-benchmark-timers.php +++ b/examples/92-benchmark-timers.php @@ -1,15 +1,13 @@ addTimer(0, function () { }); + Loop::get()->addTimer(0, function () { }); } -$loop->run(); +Loop::get()->run(); diff --git a/examples/93-benchmark-ticks-delay.php b/examples/93-benchmark-ticks-delay.php index 95ee78c6..b37cfbc2 100644 --- a/examples/93-benchmark-ticks-delay.php +++ b/examples/93-benchmark-ticks-delay.php @@ -1,17 +1,15 @@ 0) { --$ticks; //$loop->addTimer(0, $tick); - $loop->futureTick($tick); + Loop::get()->futureTick($tick); } else { echo 'done'; } @@ -19,4 +17,4 @@ $tick(); -$loop->run(); +Loop::get()->run(); diff --git a/examples/94-benchmark-timers-delay.php b/examples/94-benchmark-timers-delay.php index 2d6cfa25..e8e380a2 100644 --- a/examples/94-benchmark-timers-delay.php +++ b/examples/94-benchmark-timers-delay.php @@ -1,17 +1,15 @@ 0) { --$ticks; //$loop->futureTick($tick); - $loop->addTimer(0, $tick); + Loop::get()->addTimer(0, $tick); } else { echo 'done'; } @@ -19,4 +17,4 @@ $tick(); -$loop->run(); +Loop::get()->run(); diff --git a/examples/95-benchmark-memory.php b/examples/95-benchmark-memory.php index 084c4042..14d77872 100644 --- a/examples/95-benchmark-memory.php +++ b/examples/95-benchmark-memory.php @@ -8,6 +8,7 @@ */ use React\EventLoop\Factory; +use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use React\EventLoop\TimerInterface; @@ -15,10 +16,10 @@ $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' : Factory::create(); +$loop = isset($args['l']) && class_exists('React\EventLoop\\' . $args['l'] . 'Loop') ? 'React\EventLoop\\' . $args['l'] . 'Loop' : Loop::get(); if (!($loop instanceof LoopInterface)) { - $loop = new $loop(); + Loop::set(new $loop()); } $r = isset($args['r']) ? (int)$args['r'] : 2; @@ -26,21 +27,21 @@ $runs = 0; if (5 < $t) { - $loop->addTimer($t, function () use ($loop) { - $loop->stop(); + Loop::get()->addTimer($t, function () { + Loop::get()->stop(); }); } -$loop->addPeriodicTimer(0.001, function () use (&$runs, $loop) { +Loop::get()->addPeriodicTimer(0.001, function () use (&$runs) { $runs++; - $loop->addPeriodicTimer(1, function (TimerInterface $timer) use ($loop) { - $loop->cancelTimer($timer); + Loop::get()->addPeriodicTimer(1, function (TimerInterface $timer) { + Loop::get()->cancelTimer($timer); }); }); -$loop->addPeriodicTimer($r, function () use (&$runs) { +Loop::get()->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"; @@ -50,18 +51,18 @@ }); echo "PHP Version:\t\t", phpversion(), "\n"; -echo "Loop\t\t\t", get_class($loop), "\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(); +Loop::get()->run(); $endTime = time(); $timeTaken = $endTime - $beginTime; echo "PHP Version:\t\t", phpversion(), "\n"; -echo "Loop\t\t\t", get_class($loop), "\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"; diff --git a/src/Factory.php b/src/Factory.php index d1767bf8..1843d86e 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -19,21 +19,44 @@ final class Factory * * This method should usually only be called once at the beginning of the program. * + * @deprecated Use Loop::get instead + * * @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(); - } elseif (\class_exists('libev\EventLoop', false)) { + } + + if (\class_exists('libev\EventLoop', false)) { return new ExtLibevLoop(); - } elseif (\class_exists('EvLoop', false)) { + } + + if (\class_exists('EvLoop', false)) { return new ExtEvLoop(); - } elseif (\class_exists('EventBase', false)) { + } + + if (\class_exists('EventBase', false)) { return new ExtEventLoop(); - } elseif (\function_exists('event_base_new') && \PHP_MAJOR_VERSION === 5) { + } + + if (\function_exists('event_base_new') && \PHP_MAJOR_VERSION === 5) { // only use ext-libevent on PHP 5 for now return new ExtLibeventLoop(); } diff --git a/src/Loop.php b/src/Loop.php new file mode 100644 index 00000000..e91291e3 --- /dev/null +++ b/src/Loop.php @@ -0,0 +1,50 @@ + + */ + public function numberOfTests() + { + return array(array(), array(), array()); + } + + /** + * @after + * @before + */ + public function unsetLoopFromLoopAccessor() + { + $ref = new ReflectionClass('\React\EventLoop\Loop'); + $prop = $ref->getProperty('instance'); + $prop->setAccessible(true); + $prop->setValue(null); + $prop->setAccessible(false); + } +} From 1854d5dac3e687184ab2e2c75c65728f915991f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 29 Jun 2021 12:49:45 +0200 Subject: [PATCH 36/85] Use GitHub actions for continuous integration (CI) Bye bye Travis CI, you've served us well. --- .gitattributes | 2 +- .github/workflows/ci.yml | 74 +++++++++++++++++++++++++++++++++ .gitignore | 6 +-- .travis.yml | 89 ---------------------------------------- README.md | 2 +- travis-init.sh | 5 +++ 6 files changed, 84 insertions(+), 94 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.gitattributes b/.gitattributes index ce7c8e68..35ea0de2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,6 @@ /.gitattributes export-ignore +/.github/ export-ignore /.gitignore export-ignore -/.travis.yml export-ignore /examples export-ignore /phpunit.xml.dist export-ignore /phpunit.xml.legacy export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..4ff1a0c3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,74 @@ +name: CI + +on: + push: + pull_request: + +jobs: + PHPUnit: + name: PHPUnit (PHP ${{ matrix.php }}) + runs-on: ubuntu-18.04 # legacy Ubuntu 18.04 for legacy libevent + strategy: + matrix: + php: + - 7.4 + - 7.3 + - 7.2 + - 7.1 + - 7.0 + - 5.6 + - 5.5 + - 5.4 + - 5.3 + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: xdebug + - run: sudo apt-get update && sudo apt-get install libevent-dev + - run: sudo add-apt-repository ppa:ondrej/php -y && sudo apt-get update -q && sudo apt-get install libuv1-dev + if: ${{ matrix.php >= 5.6 }} + - run: sudo sh -c "TRAVIS_PHP_VERSION=${{ matrix.php }} ./travis-init.sh" + if: ${{ matrix.php != 7.0 }} # exclude flaky PHP 7.0 build + - run: composer install + - run: vendor/bin/phpunit --coverage-text + if: ${{ matrix.php >= 7.3 }} + - run: vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy + if: ${{ matrix.php < 7.3 }} + + PHPUnit-Windows: + name: PHPUnit (PHP ${{ matrix.php }} on Windows) + runs-on: windows-2019 + continue-on-error: true + strategy: + matrix: + php: + - 7.4 + - 7.3 + - 7.2 + - 7.1 + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: xdebug + extensions: sockets,event # future: add uv-beta (installs, but can not load) + - run: composer install + - run: vendor/bin/phpunit --coverage-text + if: ${{ matrix.php >= 7.3 }} + - run: vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy + if: ${{ matrix.php < 7.3 }} + + PHPUnit-hhvm: + name: PHPUnit (HHVM) + runs-on: ubuntu-18.04 + continue-on-error: true + steps: + - uses: actions/checkout@v2 + - uses: azjezz/setup-hhvm@v1 + with: + version: lts-3.30 + - run: hhvm $(which composer) install + - run: hhvm vendor/bin/phpunit diff --git a/.gitignore b/.gitignore index 81b92580..5cf9a2cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -composer.lock -phpunit.xml -vendor +/composer.lock +/phpunit.xml +/vendor/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e3ac120a..00000000 --- a/.travis.yml +++ /dev/null @@ -1,89 +0,0 @@ -language: php - -# lock distro so new future defaults will not break the build -dist: xenial - -jobs: - include: - - php: 5.3 - dist: precise - before_install: [] # skip libuv - - php: 5.4 - dist: trusty - before_install: [] # skip libuv - - php: 5.5 - dist: trusty - before_install: [] # skip libuv - - php: 5.6 - - php: 7.0 - - php: 7.1 - - php: 7.2 - - php: 7.3 - - php: 7.4 - - php: hhvm-3.18 - dist: trusty - before_install: [] # skip libuv - install: - - composer install # skip ./travis-init.sh - - name: "Windows" - os: windows - language: shell # no built-in php support - before_install: - - choco install php - - choco install composer - - export PATH="$(powershell -Command '("Process", "Machine" | % { [Environment]::GetEnvironmentVariable("PATH", $_) -Split ";" -Replace "\\$", "" } | Select -Unique | % { cygpath $_ }) -Join ":"')" - install: - - composer install - - name: "Windows PHP 7.2 with ext-event" - os: windows - language: shell # no built-in php support - before_install: - - curl -OL https://windows.php.net/downloads/pecl/releases/event/2.5.3/php_event-2.5.3-7.2-nts-vc15-x64.zip # latest version as of 2019-12-23 - - choco install php --version=7.2.26 # latest version supported by ext-event as of 2019-12-23 - - choco install composer - - export PATH="$(powershell -Command '("Process", "Machine" | % { [Environment]::GetEnvironmentVariable("PATH", $_) -Split ";" -Replace "\\$", "" } | Select -Unique | % { cygpath $_ }) -Join ":"')" - - php -r "\$z=new ZipArchive();\$z->open(glob('php_event*.zip')[0]);\$z->extractTo(dirname(php_ini_loaded_file()).'/ext','php_event.dll');" - - php -r "file_put_contents(php_ini_loaded_file(),'extension_dir=ext'.PHP_EOL,FILE_APPEND);" - - php -r "file_put_contents(php_ini_loaded_file(),'extension=sockets'.PHP_EOL,FILE_APPEND);" # ext-sockets needs to be loaded before ext-event - - php -r "file_put_contents(php_ini_loaded_file(),'extension=event'.PHP_EOL,FILE_APPEND);" - install: - - composer install - - name: "Windows PHP 7.4 with ext-uv" - os: windows - language: shell # no built-in php support - before_install: - - curl -OL https://windows.php.net/downloads/pecl/releases/uv/0.2.4/php_uv-0.2.4-7.4-nts-vc15-x64.zip # latest version as of 2019-12-23 - - choco install php --version=7.4.0 # latest version supported by ext-uv as of 2019-12-23 - - choco install composer - - export PATH="$(powershell -Command '("Process", "Machine" | % { [Environment]::GetEnvironmentVariable("PATH", $_) -Split ";" -Replace "\\$", "" } | Select -Unique | % { cygpath $_ }) -Join ":"')" - - php -r "\$z=new ZipArchive();\$z->open(glob('php_uv*.zip')[0]);\$z->extractTo(dirname(php_ini_loaded_file()).'/ext','php_uv.dll');\$z->extractTo(dirname(php_ini_loaded_file()),'libuv.dll');" - - php -r "file_put_contents(php_ini_loaded_file(),'extension_dir=ext'.PHP_EOL,FILE_APPEND);" - - php -r "file_put_contents(php_ini_loaded_file(),'extension=sockets'.PHP_EOL,FILE_APPEND);" # ext-sockets needs to be loaded before ext-uv - - php -r "file_put_contents(php_ini_loaded_file(),'extension=uv'.PHP_EOL,FILE_APPEND);" - install: - - composer install - allow_failures: - - php: hhvm-3.18 - - os: windows - -addons: - apt: - packages: - - libevent-dev # Used by 'event' and 'libevent' PHP extensions - -cache: - directories: - - $HOME/.composer/cache/files - -before_install: - - sudo add-apt-repository ppa:ondrej/php -y - - sudo apt-get update -q - - sudo apt-get install libuv1-dev - -install: - - ./travis-init.sh - - composer install - -script: - - if [[ "$TRAVIS_PHP_VERSION" > "7.2" ]]; then vendor/bin/phpunit --coverage-text; fi - - if [[ "$TRAVIS_PHP_VERSION" < "7.3" ]]; then vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy; fi diff --git a/README.md b/README.md index 402309b1..f76b3392 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # EventLoop Component -[![Build Status](https://travis-ci.org/reactphp/event-loop.svg?branch=master)](https://travis-ci.org/reactphp/event-loop) +[![CI status](https://github.com/reactphp/event-loop/workflows/CI/badge.svg)](https://github.com/reactphp/event-loop/actions) [ReactPHP](https://reactphp.org/)'s core reactor event loop that libraries can use for evented I/O. diff --git a/travis-init.sh b/travis-init.sh index 94ec4f36..987f0b1a 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -6,6 +6,10 @@ set -o pipefail if [[ "$TRAVIS_PHP_VERSION" != "5.3" ]]; then echo "yes" | pecl install event echo "yes" | pecl install ev + if ! [[ "$TRAVIS_PHP_VERSION" < "7.0" ]]; then + echo "extension=event.so" >> "$(php -r 'echo php_ini_loaded_file();')" + echo "extension=ev.so" >> "$(php -r 'echo php_ini_loaded_file();')" + fi fi # install 'libevent' PHP extension on legacy PHP 5 only @@ -35,4 +39,5 @@ fi # install 'libuv' PHP extension on PHP 7+ only if ! [[ "$TRAVIS_PHP_VERSION" < "7.0" ]]; then echo "yes" | pecl install uv-beta + echo "extension=uv.so" >> "$(php -r 'echo php_ini_loaded_file();')" fi From 129d9963d549fdbbad7680e128cf6d9e409fecb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 29 Jun 2021 13:12:04 +0200 Subject: [PATCH 37/85] Fix failing test cases due to inaccurate timers --- tests/AbstractLoopTest.php | 2 +- tests/Timer/AbstractTimerTest.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 292ffd1a..61790882 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -727,7 +727,7 @@ public function testSignalsKeepTheLoopRunning() $loop->stop(); }); - $this->assertRunSlowerThan(1.5); + $this->assertRunSlowerThan(1.4); } /** diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index cd53bd13..c5198385 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -26,13 +26,13 @@ public function testAddTimerWillBeInvokedOnceAndBlocksLoopWhenRunning() { $loop = $this->createLoop(); - $loop->addTimer(0.002, $this->expectCallableOnce()); + $loop->addTimer(0.005, $this->expectCallableOnce()); $start = microtime(true); $loop->run(); $end = microtime(true); - // 1 invocation should take 2ms (± 1ms due to timer inaccuracies) + // 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); @@ -57,7 +57,7 @@ public function testAddPeriodicTimerWillBeInvokedUntilItIsCancelled() // make no strict assumptions about actual time interval. // leave some room to ensure this ticks exactly 3 times. - $loop->addTimer(0.399, function () use ($loop, $periodic) { + $loop->addTimer(0.350, function () use ($loop, $periodic) { $loop->cancelTimer($periodic); }); @@ -135,7 +135,7 @@ function () use (&$start) { $loop->run(); $end = \microtime(true); - // 1ms should be enough even on slow machines - $this->assertLessThan(0.001, $end - $start); + // 1ms should be enough even on slow machines (± 1ms due to timer inaccuracies) + $this->assertLessThan(0.002, $end - $start); } } From 0322d2ce3bd5dfd45bcc7fdab69993d8c5414fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 29 Jun 2021 14:37:11 +0200 Subject: [PATCH 38/85] Clean up CI config to simplify installing PHP extensions --- .gitattributes | 5 ++--- .github/workflows/ci.yml | 44 ++++++++++++++++++++++++++++++++++++---- travis-init.sh | 43 --------------------------------------- 3 files changed, 42 insertions(+), 50 deletions(-) delete mode 100755 travis-init.sh diff --git a/.gitattributes b/.gitattributes index 35ea0de2..fc0be872 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,7 @@ /.gitattributes export-ignore /.github/ export-ignore /.gitignore export-ignore -/examples export-ignore +/examples/ export-ignore /phpunit.xml.dist export-ignore /phpunit.xml.legacy export-ignore -/tests export-ignore -/travis-init.sh export-ignore +/tests/ export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ff1a0c3..d3310c44 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,10 +27,46 @@ jobs: php-version: ${{ matrix.php }} coverage: xdebug - run: sudo apt-get update && sudo apt-get install libevent-dev - - run: sudo add-apt-repository ppa:ondrej/php -y && sudo apt-get update -q && sudo apt-get install libuv1-dev - if: ${{ matrix.php >= 5.6 }} - - run: sudo sh -c "TRAVIS_PHP_VERSION=${{ matrix.php }} ./travis-init.sh" - if: ${{ matrix.php != 7.0 }} # exclude flaky PHP 7.0 build + - name: Install ext-event on PHP >= 5.4 + run: | + echo "yes" | sudo pecl install event + # explicitly enable extensions in php.ini on PHP 5.6+ + php -r 'exit((int)(PHP_VERSION_ID >= 50600));' || echo "extension=event.so" | sudo tee -a "$(php -r 'echo php_ini_loaded_file();')" + if: ${{ matrix.php >= 5.4 }} + - name: Install ext-ev on PHP >= 5.4 + run: | + echo "yes" | sudo pecl install ev + # explicitly enable extensions in php.ini on PHP 5.6+ + php -r 'exit((int)(PHP_VERSION_ID >= 50600));' || echo "extension=ev.so" | sudo tee -a "$(php -r 'echo php_ini_loaded_file();')" + if: ${{ matrix.php >= 5.4 }} + - name: Install ext-uv on PHP >= 7.0 + run: | + sudo add-apt-repository ppa:ondrej/php -y && sudo apt-get update -q && sudo apt-get install libuv1-dev + echo "yes" | sudo pecl install uv-beta + echo "extension=uv.so" >> "$(php -r 'echo php_ini_loaded_file();')" + if: ${{ matrix.php >= 7.0 }} + - name: Install legacy ext-libevent on PHP < 7.0 + run: | + curl http://pecl.php.net/get/libevent-0.1.0.tgz | tar -xz + pushd libevent-0.1.0 + phpize + ./configure + make + sudo make install + popd + echo "extension=libevent.so" | sudo tee -a "$(php -r 'echo php_ini_loaded_file();')" + if: ${{ matrix.php < 7.0 }} + - name: Install legacy ext-libev on PHP < 7.0 + run: | + git clone --recursive https://github.com/m4rw3r/php-libev + pushd php-libev + phpize + ./configure --with-libev + make + sudo make install + popd + echo "extension=libev.so" | sudo tee -a "$(php -r 'echo php_ini_loaded_file();')" + if: ${{ matrix.php < 7.0 }} - run: composer install - run: vendor/bin/phpunit --coverage-text if: ${{ matrix.php >= 7.3 }} diff --git a/travis-init.sh b/travis-init.sh deleted file mode 100755 index 987f0b1a..00000000 --- a/travis-init.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -set -e -set -o pipefail - -# install 'event' and 'ev' PHP extension on PHP 5.4+ only -if [[ "$TRAVIS_PHP_VERSION" != "5.3" ]]; then - echo "yes" | pecl install event - echo "yes" | pecl install ev - if ! [[ "$TRAVIS_PHP_VERSION" < "7.0" ]]; then - echo "extension=event.so" >> "$(php -r 'echo php_ini_loaded_file();')" - echo "extension=ev.so" >> "$(php -r 'echo php_ini_loaded_file();')" - fi -fi - -# install 'libevent' PHP extension on legacy PHP 5 only -if [[ "$TRAVIS_PHP_VERSION" < "7.0" ]]; 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 on legacy PHP 5 only -if [[ "$TRAVIS_PHP_VERSION" < "7.0" ]]; 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 - -# install 'libuv' PHP extension on PHP 7+ only -if ! [[ "$TRAVIS_PHP_VERSION" < "7.0" ]]; then - echo "yes" | pecl install uv-beta - echo "extension=uv.so" >> "$(php -r 'echo php_ini_loaded_file();')" -fi From fb5966123661d564a498b97e6ad8e39867b32116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 29 Jun 2021 15:53:01 +0200 Subject: [PATCH 39/85] Support PHP 8 --- .github/workflows/ci.yml | 10 ++++++---- README.md | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3310c44..92f06ab7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: strategy: matrix: php: + - 8.0 - 7.4 - 7.3 - 7.2 @@ -27,24 +28,24 @@ jobs: php-version: ${{ matrix.php }} coverage: xdebug - run: sudo apt-get update && sudo apt-get install libevent-dev - - name: Install ext-event on PHP >= 5.4 + - name: Install ext-event between PHP 5.4 and PHP 7.x run: | echo "yes" | sudo pecl install event # explicitly enable extensions in php.ini on PHP 5.6+ php -r 'exit((int)(PHP_VERSION_ID >= 50600));' || echo "extension=event.so" | sudo tee -a "$(php -r 'echo php_ini_loaded_file();')" - if: ${{ matrix.php >= 5.4 }} + if: ${{ matrix.php >= 5.4 && matrix.php < 8.0 }} - name: Install ext-ev on PHP >= 5.4 run: | echo "yes" | sudo pecl install ev # explicitly enable extensions in php.ini on PHP 5.6+ php -r 'exit((int)(PHP_VERSION_ID >= 50600));' || echo "extension=ev.so" | sudo tee -a "$(php -r 'echo php_ini_loaded_file();')" if: ${{ matrix.php >= 5.4 }} - - name: Install ext-uv on PHP >= 7.0 + - name: Install ext-uv on PHP 7.x run: | sudo add-apt-repository ppa:ondrej/php -y && sudo apt-get update -q && sudo apt-get install libuv1-dev echo "yes" | sudo pecl install uv-beta echo "extension=uv.so" >> "$(php -r 'echo php_ini_loaded_file();')" - if: ${{ matrix.php >= 7.0 }} + if: ${{ matrix.php >= 7.0 && matrix.php < 8.0 }} - name: Install legacy ext-libevent on PHP < 7.0 run: | curl http://pecl.php.net/get/libevent-0.1.0.tgz | tar -xz @@ -80,6 +81,7 @@ jobs: strategy: matrix: php: + - 8.0 - 7.4 - 7.3 - 7.2 diff --git a/README.md b/README.md index f76b3392..853766b2 100644 --- a/README.md +++ b/README.md @@ -702,7 +702,7 @@ $ composer require react/event-loop:^1.1.1 See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. This project aims to run on any platform and thus does not require any PHP -extensions and supports running on legacy PHP 5.3 through current PHP 7+ and +extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM. It's *highly recommended to use PHP 7+* for this project. From eef0298ae13bab89c9e70e8f9360529938f9d0fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 28 Jun 2021 14:27:52 +0200 Subject: [PATCH 40/85] Add static Loop methods --- README.md | 200 ++++++++++++++++++++----- examples/01-timers.php | 6 +- examples/02-periodic.php | 8 +- examples/03-ticks.php | 6 +- examples/04-signals.php | 6 +- examples/11-consume-stdin.php | 6 +- examples/12-generate-yes.php | 6 +- examples/13-http-client-blocking.php | 6 +- examples/14-http-client-async.php | 14 +- examples/21-http-server.php | 10 +- examples/91-benchmark-ticks.php | 4 +- examples/92-benchmark-timers.php | 4 +- examples/93-benchmark-ticks-delay.php | 4 +- examples/94-benchmark-timers-delay.php | 4 +- examples/95-benchmark-memory.php | 15 +- src/Loop.php | 150 +++++++++++++++++++ tests/LoopTest.php | 152 +++++++++++++++++++ 17 files changed, 515 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 853766b2..b5559f6c 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ single [`run()`](#run) call that is controlled by the user. * [Quickstart example](#quickstart-example) * [Usage](#usage) * [Loop](#loop) + * [Loop methods](#loop-methods) * [get()](#get) * [Factory](#factory) * [create()](#create) @@ -52,88 +53,215 @@ use React\EventLoop\Loop; $server = stream_socket_server('tcp://127.0.0.1:8080'); stream_set_blocking($server, false); -Loop::get()->addReadStream($server, function ($server) { +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::get()->addWriteStream($conn, function ($conn) use (&$data) { + Loop::addWriteStream($conn, function ($conn) use (&$data) { $written = fwrite($conn, $data); if ($written === strlen($data)) { fclose($conn); - Loop::get()->removeWriteStream($conn); + Loop::removeWriteStream($conn); } else { $data = substr($data, $written); } }); }); -Loop::get()->addPeriodicTimer(5, function () { +Loop::addPeriodicTimer(5, function () { $memory = memory_get_usage() / 1024; $formatted = number_format($memory, 3).'K'; echo "Current memory usage: {$formatted}\n"; }); -Loop::get()->run(); +Loop::run(); ``` See also the [examples](examples). ## Usage -Typical applications use a single event loop which is created at the beginning -and run at the end of the program. +As of `v1.2.0`, typical applications would use the [`Loop` object](#loop) +to use the currently active event loop instance like this: ```php -// [1] -$loop = React\EventLoop\Factory::create(); +use React\EventLoop\Loop; -// [2] -$loop->addPeriodicTimer(1, function () { - echo "Tick\n"; +$timer = Loop::addPeriodicTimer(0.1, function () { + echo "Tick" . PHP_EOL; +}); +Loop::addTimer(1.0, function () use ($timer) { + Loop::cancelTimer($timer); + echo 'Done' . PHP_EOL; }); -$stream = new React\Stream\ReadableResourceStream( - fopen('file.txt', 'r'), - $loop -); +Loop::run(); +``` + +As an alternative, you can also explicitly create an event loop instance at the +beginning, reuse it throughout your program and finally run it at the end of the +program like this: + +```php +$loop = React\EventLoop\Loop::get(); // or deprecated React\EventLoop\Factory::create(); + +$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; +}); -// [3] $loop->run(); ``` -1. The loop instance is created at the beginning of the program. A convenience - factory [`React\EventLoop\Factory::create()`](#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()`](#run) call at the end of the program. +While the former is more concise, the latter is more explicit. +In both cases, the program would perform the exact same steps. + +1. The event loop instance is created at the beginning of the program. This is + implicitly done the first time you call the [`Loop` class](#loop) or + explicitly when using the deprecated [`Factory::create() method`](#create) + (or manually instantiating any of the [loop implementation](#loop-implementations)). +2. The event loop is used directly or passed as an instance to library and + application code. In this example, a periodic timer is registered with the + event loop which simply outputs `Tick` every fraction of a second until another + timer stops the periodic timer after a second. +3. The event loop is run at the end of the program with a single [`run()`](#run) + call at the end of the program. + +As of `v1.2.0`, we highly recommend using the [`Loop` class](#loop). +The explicit loop instructions are still valid and may still be useful in some +applications, especially for a transition period towards the more concise style. ### Loop The `Loop` class exists as a convenient global accessor for the event loop. -#### get() +#### Loop methods -The `get(): LoopInterface` method is the preferred way to get and use the event loop. With -it there is no need to always pass the loop around anymore. +The `Loop` class provides all methods that exist on the [`LoopInterface`](#loopinterface) +as static methods: + +* [run()](#run) +* [stop()](#stop) +* [addTimer()](#addtimer) +* [addPeriodicTimer()](#addperiodictimer) +* [cancelTimer()](#canceltimer) +* [futureTick()](#futuretick) +* [addSignal()](#addsignal) +* [removeSignal()](#removesignal) +* [addReadStream()](#addreadstream) +* [addWriteStream()](#addwritestream) +* [removeReadStream()](#removereadstream) +* [removeWriteStream()](#removewritestream) + +If you're working with the event loop in your application code, it's often +easiest to directly interface with the static methods defined on the `Loop` class +like this: ```php use React\EventLoop\Loop; -Loop::get()->addTimer(0.02, function () { - echo 'World!'; +$timer = Loop::addPeriodicTimer(0.1, function () { + echo 'tick!' . PHP_EOL; }); -Loop::get()->addTimer(0.01, function () { - echo 'Hello '; + +Loop::addTimer(1.0, function () use ($timer) { + Loop::cancelTimer($timer); + echo 'Done' . PHP_EOL; }); -Loop::get()->run(); +Loop::run(); ``` +On the other hand, if you're familiar with object-oriented programming (OOP) and +dependency injection (DI), you may want to inject an event loop instance and +invoke instance methods on the `LoopInterface` like this: + +```php +use React\EventLoop\Loop; +use React\EventLoop\LoopInterface; + +class Greeter +{ + private $loop; + + public function __construct(LoopInterface $loop) + { + $this->loop = $loop; + } + + public function greet(string $name) + { + $this->loop->addTimer(1.0, function () use ($name) { + echo 'Hello ' . $name . '!' . PHP_EOL; + }); + } +} + +$greeter = new Greeter(Loop::get()); +$greeter->greet('Alice'); +$greeter->greet('Bob'); + +Loop::run(); +``` + +Each static method call will be forwarded as-is to the underlying event loop +instance by using the [`Loop::get()`](#get) call internally. +See [`LoopInterface`](#loopinterface) for more details about available methods. + +#### get() + +The `get(): LoopInterface` method can be used to +get the currently active event loop instance. + +This method will always return the same event loop instance throughout the +lifetime of your application. + +```php +use React\EventLoop\Loop; +use React\EventLoop\LoopInterface; + +$loop = Loop::get(); + +assert($loop instanceof LoopInterface); +assert($loop === Loop::get()); +``` + +This is particularly useful if you're using object-oriented programming (OOP) +and dependency injection (DI). In this case, you may want to inject an event +loop instance and invoke instance methods on the `LoopInterface` like this: + +```php +use React\EventLoop\Loop; +use React\EventLoop\LoopInterface; + +class Greeter +{ + private $loop; + + public function __construct(LoopInterface $loop) + { + $this->loop = $loop; + } + + public function greet(string $name) + { + $this->loop->addTimer(1.0, function () use ($name) { + echo 'Hello ' . $name . '!' . PHP_EOL; + }); + } +} + +$greeter = new Greeter(Loop::get()); +$greeter->greet('Alice'); +$greeter->greet('Bob'); + +Loop::run(); +``` + +See [`LoopInterface`](#loopinterface) for more details about available methods. + ### Factory The `Factory` class exists as a convenient way to pick the best available diff --git a/examples/01-timers.php b/examples/01-timers.php index 5be2f3e2..a7bf3945 100644 --- a/examples/01-timers.php +++ b/examples/01-timers.php @@ -4,12 +4,12 @@ require __DIR__ . '/../vendor/autoload.php'; -Loop::get()->addTimer(0.8, function () { +Loop::addTimer(0.8, function () { echo 'world!' . PHP_EOL; }); -Loop::get()->addTimer(0.3, function () { +Loop::addTimer(0.3, function () { echo 'hello '; }); -Loop::get()->run(); +Loop::run(); diff --git a/examples/02-periodic.php b/examples/02-periodic.php index 6f549055..0604f846 100644 --- a/examples/02-periodic.php +++ b/examples/02-periodic.php @@ -4,13 +4,13 @@ require __DIR__ . '/../vendor/autoload.php'; -$timer = Loop::get()->addPeriodicTimer(0.1, function () { +$timer = Loop::addPeriodicTimer(0.1, function () { echo 'tick!' . PHP_EOL; }); -Loop::get()->addTimer(1.0, function () use ($timer) { - Loop::get()->cancelTimer($timer); +Loop::addTimer(1.0, function () use ($timer) { + Loop::cancelTimer($timer); echo 'Done' . PHP_EOL; }); -Loop::get()->run(); +Loop::run(); diff --git a/examples/03-ticks.php b/examples/03-ticks.php index 0eee59bd..4b2077da 100644 --- a/examples/03-ticks.php +++ b/examples/03-ticks.php @@ -4,12 +4,12 @@ require __DIR__ . '/../vendor/autoload.php'; -Loop::get()->futureTick(function () { +Loop::futureTick(function () { echo 'b'; }); -Loop::get()->futureTick(function () { +Loop::futureTick(function () { echo 'c'; }); echo 'a'; -Loop::get()->run(); +Loop::run(); diff --git a/examples/04-signals.php b/examples/04-signals.php index dc7e0293..ceca3521 100644 --- a/examples/04-signals.php +++ b/examples/04-signals.php @@ -9,11 +9,11 @@ exit(1); } -Loop::get()->addSignal(SIGINT, $func = function ($signal) use (&$func) { +Loop::addSignal(SIGINT, $func = function ($signal) use (&$func) { echo 'Signal: ', (string)$signal, PHP_EOL; - Loop::get()->removeSignal(SIGINT, $func); + Loop::removeSignal(SIGINT, $func); }); echo 'Listening for SIGINT. Use "kill -SIGINT ' . getmypid() . '" or CTRL+C' . PHP_EOL; -Loop::get()->run(); +Loop::run(); diff --git a/examples/11-consume-stdin.php b/examples/11-consume-stdin.php index 146a55eb..f567d84a 100644 --- a/examples/11-consume-stdin.php +++ b/examples/11-consume-stdin.php @@ -11,12 +11,12 @@ // read everything from STDIN and report number of bytes // for illustration purposes only, should use react/stream instead -Loop::get()->addReadStream(STDIN, function ($stream) { +Loop::addReadStream(STDIN, function ($stream) { $chunk = fread($stream, 64 * 1024); // reading nothing means we reached EOF if ($chunk === '') { - Loop::get()->removeReadStream($stream); + Loop::removeReadStream($stream); stream_set_blocking($stream, true); fclose($stream); return; @@ -25,4 +25,4 @@ echo strlen($chunk) . ' bytes' . PHP_EOL; }); -Loop::get()->run(); +Loop::run(); diff --git a/examples/12-generate-yes.php b/examples/12-generate-yes.php index b0da7c60..4424b8ec 100644 --- a/examples/12-generate-yes.php +++ b/examples/12-generate-yes.php @@ -17,13 +17,13 @@ // write data to STDOUT whenever its write buffer accepts data // for illustrations purpose only, should use react/stream instead -Loop::get()->addWriteStream(STDOUT, function ($stdout) use (&$data) { +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::get()->removeWriteStream($stdout); + Loop::removeWriteStream($stdout); fclose($stdout); stream_set_blocking($stdout, true); fwrite(STDERR, 'Stopped because STDOUT closed' . PHP_EOL); @@ -38,4 +38,4 @@ } }); -Loop::get()->run(); +Loop::run(); diff --git a/examples/13-http-client-blocking.php b/examples/13-http-client-blocking.php index eb34b24d..efd8cc86 100644 --- a/examples/13-http-client-blocking.php +++ b/examples/13-http-client-blocking.php @@ -16,13 +16,13 @@ fwrite($stream, "GET / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n"); // wait for HTTP response -Loop::get()->addReadStream($stream, function ($stream) { +Loop::addReadStream($stream, function ($stream) { $chunk = fread($stream, 64 * 1024); // reading nothing means we reached EOF if ($chunk === '') { echo '[END]' . PHP_EOL; - Loop::get()->removeReadStream($stream); + Loop::removeReadStream($stream); fclose($stream); return; } @@ -30,4 +30,4 @@ echo $chunk; }); -Loop::get()->run(); +Loop::run(); diff --git a/examples/14-http-client-async.php b/examples/14-http-client-async.php index 2e3e7e35..ceed3ec7 100644 --- a/examples/14-http-client-async.php +++ b/examples/14-http-client-async.php @@ -23,14 +23,14 @@ // print progress every 10ms echo 'Connecting'; -$timer = Loop::get()->addPeriodicTimer(0.01, function () { +$timer = Loop::addPeriodicTimer(0.01, function () { echo '.'; }); // wait for connection success/error -Loop::get()->addWriteStream($stream, function ($stream) use ($timer) { - Loop::get()->removeWriteStream($stream); - Loop::get()->cancelTimer($timer); +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) { @@ -44,13 +44,13 @@ fwrite($stream, "GET / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n"); // wait for HTTP response - Loop::get()->addReadStream($stream, function ($stream) { + Loop::addReadStream($stream, function ($stream) { $chunk = fread($stream, 64 * 1024); // reading nothing means we reached EOF if ($chunk === '') { echo '[END]' . PHP_EOL; - Loop::get()->removeReadStream($stream); + Loop::removeReadStream($stream); fclose($stream); return; } @@ -59,4 +59,4 @@ }); }); -Loop::get()->run(); +Loop::run(); diff --git a/examples/21-http-server.php b/examples/21-http-server.php index 4e238158..e000eb51 100644 --- a/examples/21-http-server.php +++ b/examples/21-http-server.php @@ -13,24 +13,24 @@ stream_set_blocking($server, false); // wait for incoming connections on server socket -Loop::get()->addReadStream($server, function ($server) { +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::get()->addWriteStream($conn, function ($conn) use (&$data) { + Loop::addWriteStream($conn, function ($conn) use (&$data) { $written = fwrite($conn, $data); if ($written === strlen($data)) { fclose($conn); - Loop::get()->removeWriteStream($conn); + Loop::removeWriteStream($conn); } else { $data = substr($data, $written); } }); }); -Loop::get()->addPeriodicTimer(5, function () { +Loop::addPeriodicTimer(5, function () { $memory = memory_get_usage() / 1024; $formatted = number_format($memory, 3).'K'; echo "Current memory usage: {$formatted}\n"; }); -Loop::get()->run(); +Loop::run(); diff --git a/examples/91-benchmark-ticks.php b/examples/91-benchmark-ticks.php index 8a768568..452abbac 100644 --- a/examples/91-benchmark-ticks.php +++ b/examples/91-benchmark-ticks.php @@ -7,7 +7,7 @@ $n = isset($argv[1]) ? (int)$argv[1] : 1000 * 100; for ($i = 0; $i < $n; ++$i) { - Loop::get()->futureTick(function () { }); + Loop::futureTick(function () { }); } -Loop::get()->run(); +Loop::run(); diff --git a/examples/92-benchmark-timers.php b/examples/92-benchmark-timers.php index 51cb9596..da381f16 100644 --- a/examples/92-benchmark-timers.php +++ b/examples/92-benchmark-timers.php @@ -7,7 +7,7 @@ $n = isset($argv[1]) ? (int)$argv[1] : 1000 * 100; for ($i = 0; $i < $n; ++$i) { - Loop::get()->addTimer(0, function () { }); + Loop::addTimer(0, function () { }); } -Loop::get()->run(); +Loop::run(); diff --git a/examples/93-benchmark-ticks-delay.php b/examples/93-benchmark-ticks-delay.php index b37cfbc2..ac5094f3 100644 --- a/examples/93-benchmark-ticks-delay.php +++ b/examples/93-benchmark-ticks-delay.php @@ -9,7 +9,7 @@ if ($ticks > 0) { --$ticks; //$loop->addTimer(0, $tick); - Loop::get()->futureTick($tick); + Loop::futureTick($tick); } else { echo 'done'; } @@ -17,4 +17,4 @@ $tick(); -Loop::get()->run(); +Loop::run(); diff --git a/examples/94-benchmark-timers-delay.php b/examples/94-benchmark-timers-delay.php index e8e380a2..eb4fc5cb 100644 --- a/examples/94-benchmark-timers-delay.php +++ b/examples/94-benchmark-timers-delay.php @@ -9,7 +9,7 @@ if ($ticks > 0) { --$ticks; //$loop->futureTick($tick); - Loop::get()->addTimer(0, $tick); + Loop::addTimer(0, $tick); } else { echo 'done'; } @@ -17,4 +17,4 @@ $tick(); -Loop::get()->run(); +Loop::run(); diff --git a/examples/95-benchmark-memory.php b/examples/95-benchmark-memory.php index 14d77872..06735bd2 100644 --- a/examples/95-benchmark-memory.php +++ b/examples/95-benchmark-memory.php @@ -7,7 +7,6 @@ * php 95-benchmark-memory.php -t 30 -l StreamSelect -r 10 */ -use React\EventLoop\Factory; use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use React\EventLoop\TimerInterface; @@ -27,21 +26,21 @@ $runs = 0; if (5 < $t) { - Loop::get()->addTimer($t, function () { - Loop::get()->stop(); + Loop::addTimer($t, function () { + Loop::stop(); }); } -Loop::get()->addPeriodicTimer(0.001, function () use (&$runs) { +Loop::addPeriodicTimer(0.001, function () use (&$runs) { $runs++; - Loop::get()->addPeriodicTimer(1, function (TimerInterface $timer) { - Loop::get()->cancelTimer($timer); + Loop::addPeriodicTimer(1, function (TimerInterface $timer) { + Loop::cancelTimer($timer); }); }); -Loop::get()->addPeriodicTimer($r, function () use (&$runs) { +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"; @@ -57,7 +56,7 @@ echo str_repeat('-', 50), "\n"; $beginTime = time(); -Loop::get()->run(); +Loop::run(); $endTime = time(); $timeTaken = $endTime - $beginTime; diff --git a/src/Loop.php b/src/Loop.php index e91291e3..fed27cba 100644 --- a/src/Loop.php +++ b/src/Loop.php @@ -47,4 +47,154 @@ 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::get()->stop(); + } } diff --git a/tests/LoopTest.php b/tests/LoopTest.php index a653035a..f3a13d34 100644 --- a/tests/LoopTest.php +++ b/tests/LoopTest.php @@ -49,6 +49,158 @@ 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 From 75a59f10a3d0a8f64b210fae5624542782b28356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 26 Jun 2021 13:36:36 +0200 Subject: [PATCH 41/85] Improve documentation regarding deprecated Factory --- README.md | 68 +++++++++++++++++++++++++++---------------------- src/Factory.php | 14 +++++++--- 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index b5559f6c..fd33d20c 100644 --- a/README.md +++ b/README.md @@ -13,31 +13,31 @@ single [`run()`](#run) call that is controlled by the user. * [Quickstart example](#quickstart-example) * [Usage](#usage) - * [Loop](#loop) - * [Loop methods](#loop-methods) - * [get()](#get) - * [Factory](#factory) - * [create()](#create) - * [Loop implementations](#loop-implementations) - * [StreamSelectLoop](#streamselectloop) - * [ExtEventLoop](#exteventloop) - * [ExtLibeventLoop](#extlibeventloop) - * [ExtLibevLoop](#extlibevloop) - * [ExtEvLoop](#extevloop) - * [ExtUvLoop](#extuvloop) - * [LoopInterface](#loopinterface) - * [run()](#run) - * [stop()](#stop) - * [addTimer()](#addtimer) - * [addPeriodicTimer()](#addperiodictimer) - * [cancelTimer()](#canceltimer) - * [futureTick()](#futuretick) - * [addSignal()](#addsignal) - * [removeSignal()](#removesignal) - * [addReadStream()](#addreadstream) - * [addWriteStream()](#addwritestream) - * [removeReadStream()](#removereadstream) - * [removeWriteStream()](#removewritestream) + * [Loop](#loop) + * [Loop methods](#loop-methods) + * [get()](#get) + * [~~Factory~~](#factory) + * [~~create()~~](#create) + * [Loop implementations](#loop-implementations) + * [StreamSelectLoop](#streamselectloop) + * [ExtEventLoop](#exteventloop) + * [ExtLibeventLoop](#extlibeventloop) + * [ExtLibevLoop](#extlibevloop) + * [ExtEvLoop](#extevloop) + * [ExtUvLoop](#extuvloop) + * [LoopInterface](#loopinterface) + * [run()](#run) + * [stop()](#stop) + * [addTimer()](#addtimer) + * [addPeriodicTimer()](#addperiodictimer) + * [cancelTimer()](#canceltimer) + * [futureTick()](#futuretick) + * [addSignal()](#addsignal) + * [removeSignal()](#removesignal) + * [addReadStream()](#addreadstream) + * [addWriteStream()](#addwritestream) + * [removeReadStream()](#removereadstream) + * [removeWriteStream()](#removewritestream) * [Install](#install) * [Tests](#tests) * [License](#license) @@ -262,18 +262,26 @@ Loop::run(); See [`LoopInterface`](#loopinterface) for more details about available methods. -### Factory +### ~~Factory~~ -The `Factory` class exists as a convenient way to pick the best available +> Deprecated since v1.2.0, see [`Loop` class](#loop) instead. + +The deprecated `Factory` class exists as a convenient way to pick the best available [event loop implementation](#loop-implementations). -#### create() +#### ~~create()~~ + +> Deprecated since v1.2.0, see [`Loop::get()`](#get) instead. -The `create(): LoopInterface` method can be used to create a new event loop -instance: +The deprecated `create(): LoopInterface` method can be used to +create 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`](#loopinterface), diff --git a/src/Factory.php b/src/Factory.php index 1843d86e..30bbfd7c 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -3,15 +3,22 @@ namespace React\EventLoop; /** - * The `Factory` class exists as a convenient way to pick the best available event loop implementation. + * [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 { /** - * Creates a new event loop instance + * [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`, @@ -19,7 +26,8 @@ final class Factory * * This method should usually only be called once at the beginning of the program. * - * @deprecated Use Loop::get instead + * @deprecated 1.2.0 See Loop::get() instead. + * @see Loop::get() * * @return LoopInterface */ From 86b5f82e743bc34a55c0c3497e038461072609a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 30 Jun 2021 13:12:33 +0200 Subject: [PATCH 42/85] Update usage examples --- README.md | 14 ++++++++++---- examples/02-periodic.php | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fd33d20c..ef5c5ce6 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,12 @@ single [`run()`](#run) call that is controlled by the user. Here is an async HTTP server built with just the event loop. ```php +addPeriodicTimer(0.1, function () { - echo "Tick" . PHP_EOL; + echo 'Tick' . PHP_EOL; }); + $loop->addTimer(1.0, function () use ($loop, $timer) { $loop->cancelTimer($timer); echo 'Done' . PHP_EOL; @@ -163,7 +169,7 @@ like this: use React\EventLoop\Loop; $timer = Loop::addPeriodicTimer(0.1, function () { - echo 'tick!' . PHP_EOL; + echo 'Tick' . PHP_EOL; }); Loop::addTimer(1.0, function () use ($timer) { diff --git a/examples/02-periodic.php b/examples/02-periodic.php index 0604f846..4413870d 100644 --- a/examples/02-periodic.php +++ b/examples/02-periodic.php @@ -5,7 +5,7 @@ require __DIR__ . '/../vendor/autoload.php'; $timer = Loop::addPeriodicTimer(0.1, function () { - echo 'tick!' . PHP_EOL; + echo 'Tick' . PHP_EOL; }); Loop::addTimer(1.0, function () use ($timer) { From f129a5b9b6e69b2f5db7bfba2fc3dd2d17dec15e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 5 Jun 2021 15:54:31 +0200 Subject: [PATCH 43/85] Automatically run Loop at end of program (autorun) --- README.md | 35 +++++++++++++++-------- examples/01-timers.php | 2 -- examples/02-periodic.php | 2 -- examples/03-ticks.php | 2 -- examples/04-signals.php | 2 -- examples/11-consume-stdin.php | 2 -- examples/12-generate-yes.php | 2 -- examples/13-http-client-blocking.php | 2 -- examples/14-http-client-async.php | 2 -- examples/21-http-server.php | 2 -- examples/91-benchmark-ticks.php | 2 -- examples/92-benchmark-timers.php | 2 -- examples/93-benchmark-ticks-delay.php | 2 -- examples/94-benchmark-timers-delay.php | 2 -- src/Loop.php | 18 ++++++++++-- tests/BinTest.php | 39 ++++++++++++++++++++++++++ tests/bin/01-ticks-loop-class.php | 13 +++++++++ tests/bin/02-ticks-loop-instance.php | 19 +++++++++++++ tests/bin/03-ticks-loop-stop.php | 23 +++++++++++++++ 19 files changed, 133 insertions(+), 40 deletions(-) create mode 100644 tests/BinTest.php create mode 100644 tests/bin/01-ticks-loop-class.php create mode 100644 tests/bin/02-ticks-loop-instance.php create mode 100644 tests/bin/03-ticks-loop-stop.php diff --git a/README.md b/README.md index ef5c5ce6..e83ded58 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ single [`run()`](#run) call that is controlled by the user. * [Usage](#usage) * [Loop](#loop) * [Loop methods](#loop-methods) + * [Loop autorun](#loop-autorun) * [get()](#get) * [~~Factory~~](#factory) * [~~create()~~](#create) @@ -76,8 +77,6 @@ Loop::addPeriodicTimer(5, function () { $formatted = number_format($memory, 3).'K'; echo "Current memory usage: {$formatted}\n"; }); - -Loop::run(); ``` See also the [examples](examples). @@ -98,8 +97,6 @@ Loop::addTimer(1.0, function () use ($timer) { Loop::cancelTimer($timer); echo 'Done' . PHP_EOL; }); - -Loop::run(); ``` As an alternative, you can also explicitly create an event loop instance at the @@ -127,12 +124,13 @@ In both cases, the program would perform the exact same steps. 1. The event loop instance is created at the beginning of the program. This is implicitly done the first time you call the [`Loop` class](#loop) or explicitly when using the deprecated [`Factory::create() method`](#create) - (or manually instantiating any of the [loop implementation](#loop-implementations)). + (or manually instantiating any of the [loop implementations](#loop-implementations)). 2. The event loop is used directly or passed as an instance to library and application code. In this example, a periodic timer is registered with the event loop which simply outputs `Tick` every fraction of a second until another timer stops the periodic timer after a second. -3. The event loop is run at the end of the program with a single [`run()`](#run) +3. The event loop is run at the end of the program. This is automatically done + when using [`Loop` class](#loop) or explicitly with a single [`run()`](#run) call at the end of the program. As of `v1.2.0`, we highly recommend using the [`Loop` class](#loop). @@ -176,8 +174,6 @@ Loop::addTimer(1.0, function () use ($timer) { Loop::cancelTimer($timer); echo 'Done' . PHP_EOL; }); - -Loop::run(); ``` On the other hand, if you're familiar with object-oriented programming (OOP) and @@ -208,14 +204,31 @@ class Greeter $greeter = new Greeter(Loop::get()); $greeter->greet('Alice'); $greeter->greet('Bob'); - -Loop::run(); ``` Each static method call will be forwarded as-is to the underlying event loop instance by using the [`Loop::get()`](#get) call internally. See [`LoopInterface`](#loopinterface) for more details about available methods. +#### Loop autorun + +When using the `Loop` class, it will automatically execute the loop at the end of +the program. This means the following example will schedule a timer and will +automatically execute the program until the timer event fires: + +```php +use React\EventLoop\Loop; + +Loop::addTimer(1.0, function () { + echo 'Hello' . PHP_EOL; +}); +``` + +As of `v1.2.0`, we highly recommend using the `Loop` class this way and omitting any +explicit [`run()`](#run) calls. For BC reasons, the explicit [`run()`](#run) +method is still valid and may still be useful in some applications, especially +for a transition period towards the more concise style. + #### get() The `get(): LoopInterface` method can be used to @@ -262,8 +275,6 @@ class Greeter $greeter = new Greeter(Loop::get()); $greeter->greet('Alice'); $greeter->greet('Bob'); - -Loop::run(); ``` See [`LoopInterface`](#loopinterface) for more details about available methods. diff --git a/examples/01-timers.php b/examples/01-timers.php index a7bf3945..5f263b3e 100644 --- a/examples/01-timers.php +++ b/examples/01-timers.php @@ -11,5 +11,3 @@ Loop::addTimer(0.3, function () { echo 'hello '; }); - -Loop::run(); diff --git a/examples/02-periodic.php b/examples/02-periodic.php index 4413870d..68533bd3 100644 --- a/examples/02-periodic.php +++ b/examples/02-periodic.php @@ -12,5 +12,3 @@ Loop::cancelTimer($timer); echo 'Done' . PHP_EOL; }); - -Loop::run(); diff --git a/examples/03-ticks.php b/examples/03-ticks.php index 4b2077da..e32e67af 100644 --- a/examples/03-ticks.php +++ b/examples/03-ticks.php @@ -11,5 +11,3 @@ echo 'c'; }); echo 'a'; - -Loop::run(); diff --git a/examples/04-signals.php b/examples/04-signals.php index ceca3521..e841311b 100644 --- a/examples/04-signals.php +++ b/examples/04-signals.php @@ -15,5 +15,3 @@ }); echo 'Listening for SIGINT. Use "kill -SIGINT ' . getmypid() . '" or CTRL+C' . PHP_EOL; - -Loop::run(); diff --git a/examples/11-consume-stdin.php b/examples/11-consume-stdin.php index f567d84a..dfcb220d 100644 --- a/examples/11-consume-stdin.php +++ b/examples/11-consume-stdin.php @@ -24,5 +24,3 @@ echo strlen($chunk) . ' bytes' . PHP_EOL; }); - -Loop::run(); diff --git a/examples/12-generate-yes.php b/examples/12-generate-yes.php index 4424b8ec..a57e8d6e 100644 --- a/examples/12-generate-yes.php +++ b/examples/12-generate-yes.php @@ -37,5 +37,3 @@ $data = substr($data, $r) . substr($data, 0, $r); } }); - -Loop::run(); diff --git a/examples/13-http-client-blocking.php b/examples/13-http-client-blocking.php index efd8cc86..f0562c90 100644 --- a/examples/13-http-client-blocking.php +++ b/examples/13-http-client-blocking.php @@ -29,5 +29,3 @@ echo $chunk; }); - -Loop::run(); diff --git a/examples/14-http-client-async.php b/examples/14-http-client-async.php index ceed3ec7..074a0eac 100644 --- a/examples/14-http-client-async.php +++ b/examples/14-http-client-async.php @@ -58,5 +58,3 @@ echo $chunk; }); }); - -Loop::run(); diff --git a/examples/21-http-server.php b/examples/21-http-server.php index e000eb51..61529240 100644 --- a/examples/21-http-server.php +++ b/examples/21-http-server.php @@ -32,5 +32,3 @@ $formatted = number_format($memory, 3).'K'; echo "Current memory usage: {$formatted}\n"; }); - -Loop::run(); diff --git a/examples/91-benchmark-ticks.php b/examples/91-benchmark-ticks.php index 452abbac..e3dc2b1c 100644 --- a/examples/91-benchmark-ticks.php +++ b/examples/91-benchmark-ticks.php @@ -9,5 +9,3 @@ for ($i = 0; $i < $n; ++$i) { Loop::futureTick(function () { }); } - -Loop::run(); diff --git a/examples/92-benchmark-timers.php b/examples/92-benchmark-timers.php index da381f16..dd42ec77 100644 --- a/examples/92-benchmark-timers.php +++ b/examples/92-benchmark-timers.php @@ -9,5 +9,3 @@ for ($i = 0; $i < $n; ++$i) { Loop::addTimer(0, function () { }); } - -Loop::run(); diff --git a/examples/93-benchmark-ticks-delay.php b/examples/93-benchmark-ticks-delay.php index ac5094f3..1976124f 100644 --- a/examples/93-benchmark-ticks-delay.php +++ b/examples/93-benchmark-ticks-delay.php @@ -16,5 +16,3 @@ }; $tick(); - -Loop::run(); diff --git a/examples/94-benchmark-timers-delay.php b/examples/94-benchmark-timers-delay.php index eb4fc5cb..dfe6c8c0 100644 --- a/examples/94-benchmark-timers-delay.php +++ b/examples/94-benchmark-timers-delay.php @@ -16,5 +16,3 @@ }; $tick(); - -Loop::run(); diff --git a/src/Loop.php b/src/Loop.php index fed27cba..8cc9dd8f 100644 --- a/src/Loop.php +++ b/src/Loop.php @@ -12,7 +12,6 @@ final class Loop */ private static $instance; - /** * Returns the event loop. * When no loop is set it will it will call the factory to create one. @@ -31,7 +30,22 @@ public static function get() return self::$instance; } - self::$instance = Factory::create(); + self::$instance = $loop = Factory::create(); + + // Automatically run loop at end of program, unless already started explicitly. + // This is tested using child processes, so coverage is actually 100%, see BinTest. + // @codeCoverageIgnoreStart + $hasRun = false; + $loop->futureTick(function () use (&$hasRun) { + $hasRun = true; + }); + + register_shutdown_function(function () use ($loop, &$hasRun) { + if (!$hasRun) { + $loop->run(); + } + }); + // @codeCoverageIgnoreEnd return self::$instance; } diff --git a/tests/BinTest.php b/tests/BinTest.php new file mode 100644 index 00000000..99c05d91 --- /dev/null +++ b/tests/BinTest.php @@ -0,0 +1,39 @@ +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); + } +} diff --git a/tests/bin/01-ticks-loop-class.php b/tests/bin/01-ticks-loop-class.php new file mode 100644 index 00000000..f4fcedf1 --- /dev/null +++ b/tests/bin/01-ticks-loop-class.php @@ -0,0 +1,13 @@ +futureTick(function () { + echo 'b'; +}); + +$loop->futureTick(function () { + echo 'c'; +}); + +echo 'a'; + +$loop->run(); diff --git a/tests/bin/03-ticks-loop-stop.php b/tests/bin/03-ticks-loop-stop.php new file mode 100644 index 00000000..d8b65946 --- /dev/null +++ b/tests/bin/03-ticks-loop-stop.php @@ -0,0 +1,23 @@ +futureTick(function () use ($loop) { + echo 'b'; + + $loop->stop(); + + $loop->futureTick(function () { + echo 'never'; + }); +}); + +echo 'a'; + +$loop->run(); + +echo 'c'; From f0853b115e9f1b922168e6a824ee2ebea7267468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 27 Jun 2021 14:34:39 +0200 Subject: [PATCH 44/85] Don't run loop automatically when an uncaught exceptions occurs --- src/Loop.php | 6 ++++++ tests/BinTest.php | 18 ++++++++++++++++++ tests/bin/11-uncaught.php | 11 +++++++++++ tests/bin/12-undefined.php | 11 +++++++++++ 4 files changed, 46 insertions(+) create mode 100644 tests/bin/11-uncaught.php create mode 100644 tests/bin/12-undefined.php diff --git a/src/Loop.php b/src/Loop.php index 8cc9dd8f..a247579e 100644 --- a/src/Loop.php +++ b/src/Loop.php @@ -41,6 +41,12 @@ public static function get() }); register_shutdown_function(function () use ($loop, &$hasRun) { + // 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) { $loop->run(); } diff --git a/tests/BinTest.php b/tests/BinTest.php index 99c05d91..55b3aaca 100644 --- a/tests/BinTest.php +++ b/tests/BinTest.php @@ -36,4 +36,22 @@ public function testExecuteExampleWithExplicitLoopRunAndStopRunsLoopAndExecutesT $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); + } } diff --git a/tests/bin/11-uncaught.php b/tests/bin/11-uncaught.php new file mode 100644 index 00000000..0655698b --- /dev/null +++ b/tests/bin/11-uncaught.php @@ -0,0 +1,11 @@ +addTimer(10.0, function () { + echo 'never'; +}); + +$undefined->foo('bar'); From 9712eea0263cd0636c801b2ac71b8b8e3987cc47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 30 Jun 2021 14:59:29 +0200 Subject: [PATCH 45/85] Don't run loop automatically when explicitly calling stop() --- README.md | 19 +++++++++++++++++++ src/Loop.php | 11 ++++++++--- tests/BinTest.php | 18 ++++++++++++++++++ tests/bin/21-stop.php | 11 +++++++++++ tests/bin/22-stop-uncaught.php | 16 ++++++++++++++++ 5 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 tests/bin/21-stop.php create mode 100644 tests/bin/22-stop-uncaught.php diff --git a/README.md b/README.md index e83ded58..6b345265 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,25 @@ explicit [`run()`](#run) calls. For BC reasons, the explicit [`run()`](#run) method is still valid and may still be useful in some applications, especially for a transition period towards the more concise style. +If you don't want the `Loop` to run automatically, you can either explicitly +[`run()`](#run) or [`stop()`](#stop) it. This can be useful if you're using +a global exception handler like this: + +```php +use React\EventLoop\Loop; + +Loop::addTimer(10.0, function () { + echo 'Never happens'; +}); + +set_exception_handler(function (Throwable $e) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; + Loop::stop(); +}); + +throw new RuntimeException('Demo'); +``` + #### get() The `get(): LoopInterface` method can be used to diff --git a/src/Loop.php b/src/Loop.php index a247579e..7f1d962c 100644 --- a/src/Loop.php +++ b/src/Loop.php @@ -12,6 +12,9 @@ final class Loop */ private static $instance; + /** @var bool */ + private static $stopped = false; + /** * Returns the event loop. * When no loop is set it will it will call the factory to create one. @@ -32,7 +35,7 @@ public static function get() self::$instance = $loop = Factory::create(); - // Automatically run loop at end of program, unless already started explicitly. + // 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; @@ -40,14 +43,15 @@ public static function get() $hasRun = true; }); - register_shutdown_function(function () use ($loop, &$hasRun) { + $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) { + if (!$hasRun && !$stopped) { $loop->run(); } }); @@ -215,6 +219,7 @@ public static function run() */ public static function stop() { + self::$stopped = true; self::get()->stop(); } } diff --git a/tests/BinTest.php b/tests/BinTest.php index 55b3aaca..6f8231b8 100644 --- a/tests/BinTest.php +++ b/tests/BinTest.php @@ -54,4 +54,22 @@ public function testExecuteExampleWithUndefinedVariableShouldNotRunLoop() $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); + } } diff --git a/tests/bin/21-stop.php b/tests/bin/21-stop.php new file mode 100644 index 00000000..038d9223 --- /dev/null +++ b/tests/bin/21-stop.php @@ -0,0 +1,11 @@ + Date: Mon, 5 Jul 2021 12:47:23 +0200 Subject: [PATCH 46/85] Fix typo in docblock --- src/Loop.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Loop.php b/src/Loop.php index 7f1d962c..9eb6e510 100644 --- a/src/Loop.php +++ b/src/Loop.php @@ -17,7 +17,7 @@ final class Loop /** * Returns the event loop. - * When no loop is set it will it will call the factory to create one. + * 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. From 53d4ba0c06ae6f1352496042eb39567269866a23 Mon Sep 17 00:00:00 2001 From: Nicolas Hedger <649677+nhedger@users.noreply.github.com> Date: Tue, 6 Jul 2021 14:22:37 +0200 Subject: [PATCH 47/85] Little typo --- src/Loop.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Loop.php b/src/Loop.php index 9eb6e510..fd5d81c8 100644 --- a/src/Loop.php +++ b/src/Loop.php @@ -62,7 +62,7 @@ public static function get() /** * Internal undocumented method, behavior might change or throw in the - * future. Use with cation and at your own risk. + * future. Use with caution and at your own risk. * * @internal * @return void From 181941f806412947713753541feee97d5ea3bd3c Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Tue, 6 Jul 2021 12:03:18 +0200 Subject: [PATCH 48/85] Mark extensions deprecated and minor docs improvement --- README.md | 36 +++++++++++++++++++++++------------- src/ExtEvLoop.php | 1 + src/ExtEventLoop.php | 5 +++-- src/ExtLibevLoop.php | 8 +++++--- src/ExtLibeventLoop.php | 6 ++++-- src/ExtUvLoop.php | 1 + 6 files changed, 37 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index ef5c5ce6..30c6a330 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ single [`run()`](#run) call that is controlled by the user. * [Loop implementations](#loop-implementations) * [StreamSelectLoop](#streamselectloop) * [ExtEventLoop](#exteventloop) - * [ExtLibeventLoop](#extlibeventloop) - * [ExtLibevLoop](#extlibevloop) + * [~~ExtLibeventLoop~~](#extlibeventloop) + * [~~ExtLibevLoop~~](#extlibevloop) * [ExtEvLoop](#extevloop) * [ExtUvLoop](#extuvloop) * [LoopInterface](#loopinterface) @@ -362,8 +362,9 @@ See also [`addTimer()`](#addtimer) for more details. An `ext-event` based event loop. -This uses the [`event` PECL extension](https://pecl.php.net/package/event). -It supports the same backends as libevent. +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+. @@ -371,8 +372,10 @@ This loop is known to work with PHP 5.4 through PHP 7+. 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. +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+. @@ -380,16 +383,20 @@ This loop is known to work with PHP 5.4 through PHP 7+. 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. +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+. -#### ExtLibeventLoop +#### ~~ExtLibeventLoop~~ + +> Deprecated since v1.2.0, use [`ExtEventLoop`](#exteventloop) instead. An `ext-libevent` based event loop. -This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent). +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. @@ -408,12 +415,15 @@ 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. -#### ExtLibevLoop +#### ~~ExtLibevLoop~~ + +> Deprecated since v1.2.0, use [`ExtEvLoop`](#extevloop) instead. An `ext-libev` based event loop. -This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev). -It supports the same backends as libevent. +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) diff --git a/src/ExtEvLoop.php b/src/ExtEvLoop.php index fedd5884..7fcc29af 100644 --- a/src/ExtEvLoop.php +++ b/src/ExtEvLoop.php @@ -14,6 +14,7 @@ * * 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+. * diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 1f1b9ea4..7ce50010 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -12,8 +12,9 @@ /** * An `ext-event` based event loop. * - * This uses the [`event` PECL extension](https://pecl.php.net/package/event). - * It supports the same backends as libevent. + * 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+. * diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php index 193c6c0d..2cf1ad54 100644 --- a/src/ExtLibevLoop.php +++ b/src/ExtLibevLoop.php @@ -12,10 +12,11 @@ use SplObjectStorage; /** - * An `ext-libev` based event loop. + * [Deprecated] An `ext-libev` based event loop. * - * This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev). - * It supports the same backends as libevent. + * 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) @@ -23,6 +24,7 @@ * * @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 { diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index 55c2fca0..2ea7ffa5 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -10,9 +10,10 @@ use SplObjectStorage; /** - * An `ext-libevent` based event loop. + * [Deprecated] An `ext-libevent` based event loop. * - * This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent). + * 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. @@ -32,6 +33,7 @@ * 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 { diff --git a/src/ExtUvLoop.php b/src/ExtUvLoop.php index 002d6a2e..4434720d 100644 --- a/src/ExtUvLoop.php +++ b/src/ExtUvLoop.php @@ -11,6 +11,7 @@ * * 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+. * From be6dee480fc4692cec0504e65eb486e3be1aa6f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 11 Jul 2021 14:31:24 +0200 Subject: [PATCH 49/85] Prepare v1.2.0 release --- CHANGELOG.md | 41 +++++++++++++++++++++++++++++++++++++++++ README.md | 6 +++--- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d571cfa..bd9e69d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,46 @@ # Changelog +## 1.2.0 (2021-07-11) + +A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop). + +* Feature: Introduce new concept of default loop with the new `Loop` class. + (#226 by @WyriHaximus, #229, #231 and #232 by @clue) + + The `Loop` class exists as a convenient global accessor for the event loop. + It provides all methods that exist on the `LoopInterface` as static methods and + will automatically execute the loop at the end of the program: + + ```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; + }); + ``` + + The explicit loop instructions are still valid and may still be useful in some applications, + especially for a transition period towards the more concise style. + The `Loop::get()` method can be used to get the currently active event loop instance. + + ```php + // deprecated + $loop = React\EventLoop\Factory::create(); + + // new + $loop = React\EventLoop\Loop::get(); + ``` + +* Minor documentation improvements and mark legacy extensions as deprecated. + (#234 by @SimonFrings, #214 by @WyriHaximus and #233 and #235 by @nhedger) + +* Improve test suite, use GitHub actions for continuous integration (CI), + update PHPUnit config and run tests on PHP 8. + (#212 and #215 by @SimonFrings and #230 by @clue) + ## 1.1.1 (2020-01-01) * Fix: Fix reporting connection refused errors with `ExtUvLoop` on Linux and `StreamSelectLoop` on Windows. diff --git a/README.md b/README.md index 34b07942..b89ad575 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,10 @@ single [`run()`](#run) call that is controlled by the user. * [Loop implementations](#loop-implementations) * [StreamSelectLoop](#streamselectloop) * [ExtEventLoop](#exteventloop) - * [~~ExtLibeventLoop~~](#extlibeventloop) - * [~~ExtLibevLoop~~](#extlibevloop) * [ExtEvLoop](#extevloop) * [ExtUvLoop](#extuvloop) + * [~~ExtLibeventLoop~~](#extlibeventloop) + * [~~ExtLibevLoop~~](#extlibevloop) * [LoopInterface](#loopinterface) * [run()](#run) * [stop()](#stop) @@ -878,7 +878,7 @@ This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -$ composer require react/event-loop:^1.1.1 +$ composer require react/event-loop:^1.2 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 17f8f85b21f50ea82e4b86c62d07e123ea0e0a9a Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Thu, 29 Apr 2021 09:08:14 -0500 Subject: [PATCH 50/85] Remove Duplicate Word in Comment (Loop Interface) Remove a duplicate word ("the") from the comment for `LoopInterface::addPeriodicTimer`. --- README.md | 6 +++--- src/LoopInterface.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b89ad575..f7cecfe1 100644 --- a/README.md +++ b/README.md @@ -590,9 +590,9 @@ 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). +Unlike [`addTimer()`](#addtimer), this method will ensure the callback +will be invoked infinitely after the given interval or until you invoke +[`cancelTimer`](#canceltimer). ```php $timer = $loop->addPeriodicTimer(0.1, function () { diff --git a/src/LoopInterface.php b/src/LoopInterface.php index 8146757a..59a52ad2 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -213,9 +213,9 @@ public function addTimer($interval, $callback); * 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). + * Unlike [`addTimer()`](#addtimer), this method will ensure the callback + * will be invoked infinitely after the given interval or until you invoke + * [`cancelTimer`](#canceltimer). * * ```php * $timer = $loop->addPeriodicTimer(0.1, function () { From ce5ebe1e9c5199ff4ee3e0c1fc9b856ab8a2effc Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 20 Oct 2021 08:14:52 +0200 Subject: [PATCH 51/85] fix references to the deprecated factory the readme should not recommend to use the factory anymore but Loop::create Co-authored-by: Simon Frings --- README.md | 11 ++++++----- src/ExtLibeventLoop.php | 4 ++-- src/StreamSelectLoop.php | 5 +++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b89ad575..e1ccdb29 100644 --- a/README.md +++ b/README.md @@ -339,7 +339,7 @@ All of the event loops support these features: For most consumers of this package, the underlying event loop implementation is an implementation detail. -You should use the [`Factory`](#factory) to automatically create a new instance. +You should use the [`Loop` class](#loop) to automatically create a new instance. Advanced! If you explicitly need a certain event loop implementation, you can manually instantiate one of the following classes. @@ -356,8 +356,9 @@ 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.3 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. +Accordingly, the [`Loop` class](#loop) and the deprecated [`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 @@ -433,8 +434,8 @@ 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. +Accordingly, neither the [`Loop` object](#loop) nor the deprecated +[`Factory`](#factory) will 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 diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index 2ea7ffa5..d5699d8f 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -20,8 +20,8 @@ * 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. + * Accordingly, neither the [`Loop` object](#loop) nor the deprecated + * [`Factory`](#factory) will 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 diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index b89d8000..db3f7706 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -15,8 +15,9 @@ * 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. + * Accordingly, the [`Loop` class](#loop) and the deprecated [`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 From e244758b75e0f1000cd2960a3b1164ef33c39506 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 7 Aug 2021 12:44:02 +0200 Subject: [PATCH 52/85] Support PHP 8.1 PHP 8.1 is soon upon us, let us make sure we're ready for it. --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92f06ab7..65d76f23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: strategy: matrix: php: + - 8.1 - 8.0 - 7.4 - 7.3 @@ -81,6 +82,7 @@ jobs: strategy: matrix: php: + - 8.1 - 8.0 - 7.4 - 7.3 From 4daee2493c5630796ee9ad683399a217bd6102e8 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Wed, 13 Oct 2021 13:16:45 +0200 Subject: [PATCH 53/85] Improve documentation --- README.md | 57 +++++++++++++++++++++++----------------- src/LoopInterface.php | 51 ++++++++++++++++++++--------------- src/StreamSelectLoop.php | 2 +- 3 files changed, 64 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index f7cecfe1..92dfb4d2 100644 --- a/README.md +++ b/README.md @@ -351,7 +351,7 @@ event loop implementation first or they will throw a `BadMethodCallException` on 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. +function and is the only implementation that works out of the box with PHP. This event loop works out of the box on PHP 5.3 through PHP 7+ and HHVM. This means that no installation is required and this library works on all @@ -468,7 +468,7 @@ 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 +As a rule of thumb, it is usually recommended to attach everything to the same loop instance and then run the loop once at the bottom end of the application. @@ -486,7 +486,7 @@ 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 +This method MAY be called more than once after it has explicitly been [`stop()`ped](#stop) or after it automatically stopped because it previously did no longer have anything to do. @@ -515,18 +515,21 @@ on a loop instance that has already been stopped has no effect. The `addTimer(float $interval, callable $callback): TimerInterface` method can be used to 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 second parameter MUST be a timer callback function that accepts +the timer instance as its only parameter. +If you don't use the timer instance inside your timer callback function +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. +This method returns a timer instance. The same timer instance will also be +passed into the timer callback function as described above. +You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer. 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 () { @@ -581,15 +584,18 @@ See also [event loop implementations](#loop-implementations) for more details. The `addPeriodicTimer(float $interval, callable $callback): TimerInterface` method can be used to 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 second parameter MUST be a timer callback function that accepts +the timer instance as its only parameter. +If you don't use the timer instance inside your timer callback function +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. +This method returns a timer instance. The same timer instance will also be +passed into the timer callback function as described above. Unlike [`addTimer()`](#addtimer), this method will ensure the callback will be invoked infinitely after the given interval or until you invoke [`cancelTimer`](#canceltimer). @@ -720,9 +726,10 @@ 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 second parameter MUST be a listener callback function that accepts +the signal as its only parameter. +If you don't use the signal inside your listener callback function +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 @@ -737,14 +744,14 @@ $loop->addSignal(SIGINT, function (int $signal) { See also [example #4](examples). -Signaling is only available on Unix-like platform, Windows isn't +Signaling is only available on Unix-like platforms, 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.** +attempts to add it more than once will be ignored.** #### removeSignal() @@ -775,9 +782,10 @@ 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 second parameter MUST be a listener callback function that accepts +the stream resource as its only parameter. +If you don't use the stream resource inside your listener callback function +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 @@ -827,9 +835,10 @@ 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 second parameter MUST be a listener callback function that accepts +the stream resource as its only parameter. +If you don't use the stream resource inside your listener callback function +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 @@ -871,7 +880,7 @@ to remove a stream that was never added or is invalid has no effect. ## Install -The recommended way to install this library is [through Composer](https://getcomposer.org). +The recommended way to install this library is [through Composer](https://getcomposer.org/). [New to Composer?](https://getcomposer.org/doc/00-intro.md) This project follows [SemVer](https://semver.org/). @@ -894,7 +903,7 @@ See also [event loop implementations](#loop-implementations) for more details. ## Tests To run the test suite, you first need to clone this repo and then install all -dependencies [through Composer](https://getcomposer.org): +dependencies [through Composer](https://getcomposer.org/): ```bash $ composer install @@ -903,7 +912,7 @@ $ composer install To run the test suite, go to the project root and run: ```bash -$ php vendor/bin/phpunit +$ vendor/bin/phpunit ``` ## License diff --git a/src/LoopInterface.php b/src/LoopInterface.php index 59a52ad2..9266f718 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -20,9 +20,10 @@ interface LoopInterface * 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 second parameter MUST be a listener callback function that accepts + * the stream resource as its only parameter. + * If you don't use the stream resource inside your listener callback function + * 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 @@ -69,9 +70,10 @@ public function addReadStream($stream, $listener); * 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 second parameter MUST be a listener callback function that accepts + * the stream resource as its only parameter. + * If you don't use the stream resource inside your listener callback function + * 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 @@ -133,18 +135,21 @@ 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 second parameter MUST be a timer callback function that accepts + * the timer instance as its only parameter. + * If you don't use the timer instance inside your timer callback function + * 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. * + * This method returns a timer instance. The same timer instance will also be + * passed into the timer callback function as described above. + * You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer. * 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 () { @@ -204,16 +209,19 @@ 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 second parameter MUST be a timer callback function that accepts + * the timer instance as its only parameter. + * If you don't use the timer instance inside your timer callback function + * 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 callback + * This method returns a timer instance. The same timer instance will also be + * passed into the timer callback function as described above. + * Unlike [`addTimer()`](#addtimer), this method will ensure the callback * will be invoked infinitely after the given interval or until you invoke * [`cancelTimer`](#canceltimer). * @@ -356,9 +364,10 @@ public function futureTick($listener); * 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 second parameter MUST be a listener callback function that accepts + * the signal as its only parameter. + * If you don't use the signal inside your listener callback function + * 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 @@ -373,14 +382,14 @@ public function futureTick($listener); * * See also [example #4](examples). * - * Signaling is only available on Unix-like platform, Windows isn't + * Signaling is only available on Unix-like platforms, 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.** + * attempts to add it more than once will be ignored.** * * @param int $signal * @param callable $listener @@ -413,7 +422,7 @@ public function removeSignal($signal, $listener); * * 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 + * As a rule of thumb, it is usually recommended to attach everything to the * same loop instance and then run the loop once at the bottom end of the * application. * @@ -431,7 +440,7 @@ public function removeSignal($signal, $listener); * 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 + * This method MAY be called more than once after it has explicitly been * [`stop()`ped](#stop) or after it automatically stopped because it * previously did no longer have anything to do. * diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index b89d8000..fed25e9a 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -10,7 +10,7 @@ * 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. + * function and is the only implementation that 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 From ae621e441170725f010b4e86abc7c64222998cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 4 Feb 2022 09:16:59 +0100 Subject: [PATCH 54/85] Improve bin tests to support running from dependency --- tests/bin/01-ticks-loop-class.php | 3 ++- tests/bin/02-ticks-loop-instance.php | 3 ++- tests/bin/03-ticks-loop-stop.php | 3 ++- tests/bin/11-uncaught.php | 3 ++- tests/bin/12-undefined.php | 3 ++- tests/bin/21-stop.php | 3 ++- tests/bin/22-stop-uncaught.php | 3 ++- 7 files changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/bin/01-ticks-loop-class.php b/tests/bin/01-ticks-loop-class.php index f4fcedf1..5d499f92 100644 --- a/tests/bin/01-ticks-loop-class.php +++ b/tests/bin/01-ticks-loop-class.php @@ -2,7 +2,8 @@ use React\EventLoop\Loop; -require __DIR__ . '/../../vendor/autoload.php'; +// autoload for local project development or project installed as dependency for reactphp/reactphp +(@include __DIR__ . '/../../vendor/autoload.php') || require __DIR__ . '/../../../../autoload.php'; Loop::futureTick(function () { echo 'b'; diff --git a/tests/bin/02-ticks-loop-instance.php b/tests/bin/02-ticks-loop-instance.php index 57e58c92..5aeead84 100644 --- a/tests/bin/02-ticks-loop-instance.php +++ b/tests/bin/02-ticks-loop-instance.php @@ -2,7 +2,8 @@ use React\EventLoop\Loop; -require __DIR__ . '/../../vendor/autoload.php'; +// autoload for local project development or project installed as dependency for reactphp/reactphp +(@include __DIR__ . '/../../vendor/autoload.php') || require __DIR__ . '/../../../../autoload.php'; $loop = Loop::get(); diff --git a/tests/bin/03-ticks-loop-stop.php b/tests/bin/03-ticks-loop-stop.php index d8b65946..87317563 100644 --- a/tests/bin/03-ticks-loop-stop.php +++ b/tests/bin/03-ticks-loop-stop.php @@ -2,7 +2,8 @@ use React\EventLoop\Loop; -require __DIR__ . '/../../vendor/autoload.php'; +// autoload for local project development or project installed as dependency for reactphp/reactphp +(@include __DIR__ . '/../../vendor/autoload.php') || require __DIR__ . '/../../../../autoload.php'; $loop = Loop::get(); diff --git a/tests/bin/11-uncaught.php b/tests/bin/11-uncaught.php index 0655698b..c5bf8c70 100644 --- a/tests/bin/11-uncaught.php +++ b/tests/bin/11-uncaught.php @@ -2,7 +2,8 @@ use React\EventLoop\Loop; -require __DIR__ . '/../../vendor/autoload.php'; +// autoload for local project development or project installed as dependency for reactphp/reactphp +(@include __DIR__ . '/../../vendor/autoload.php') || require __DIR__ . '/../../../../autoload.php'; Loop::addTimer(10.0, function () { echo 'never'; diff --git a/tests/bin/12-undefined.php b/tests/bin/12-undefined.php index 66d0d726..c45cc0f4 100644 --- a/tests/bin/12-undefined.php +++ b/tests/bin/12-undefined.php @@ -2,7 +2,8 @@ use React\EventLoop\Loop; -require __DIR__ . '/../../vendor/autoload.php'; +// autoload for local project development or project installed as dependency for reactphp/reactphp +(@include __DIR__ . '/../../vendor/autoload.php') || require __DIR__ . '/../../../../autoload.php'; Loop::get()->addTimer(10.0, function () { echo 'never'; diff --git a/tests/bin/21-stop.php b/tests/bin/21-stop.php index 038d9223..182ee016 100644 --- a/tests/bin/21-stop.php +++ b/tests/bin/21-stop.php @@ -2,7 +2,8 @@ use React\EventLoop\Loop; -require __DIR__ . '/../../vendor/autoload.php'; +// autoload for local project development or project installed as dependency for reactphp/reactphp +(@include __DIR__ . '/../../vendor/autoload.php') || require __DIR__ . '/../../../../autoload.php'; Loop::addTimer(10.0, function () { echo 'never'; diff --git a/tests/bin/22-stop-uncaught.php b/tests/bin/22-stop-uncaught.php index f0c0ff8d..5b6142ed 100644 --- a/tests/bin/22-stop-uncaught.php +++ b/tests/bin/22-stop-uncaught.php @@ -2,7 +2,8 @@ use React\EventLoop\Loop; -require __DIR__ . '/../../vendor/autoload.php'; +// autoload for local project development or project installed as dependency for reactphp/reactphp +(@include __DIR__ . '/../../vendor/autoload.php') || require __DIR__ . '/../../../../autoload.php'; Loop::addTimer(10.0, function () { echo 'never'; From 4f641e8ca3709d17b7cc608f6e03fc8ff163c4ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 4 Feb 2022 13:13:27 +0100 Subject: [PATCH 55/85] Improve test suite to declare and validate helper variables (PHP 8.1) --- phpunit.xml.dist | 3 ++- tests/AbstractLoopTest.php | 4 ++++ tests/ExtEventLoopTest.php | 14 ++++++++++++++ tests/ExtLibeventLoopTest.php | 4 +++- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index bfdeecf6..19a4b901 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -4,8 +4,9 @@ + convertDeprecationsToExceptions="true"> ./tests/ diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 61790882..5b4da29e 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -12,8 +12,12 @@ abstract class AbstractLoopTest extends TestCase */ protected $loop; + /** @var float */ private $tickTimeout; + /** @var ?string */ + private $received; + const PHP_DEFAULT_CHUNK_SIZE = 8192; /** diff --git a/tests/ExtEventLoopTest.php b/tests/ExtEventLoopTest.php index 2f88d184..af4caa13 100644 --- a/tests/ExtEventLoopTest.php +++ b/tests/ExtEventLoopTest.php @@ -6,6 +6,9 @@ class ExtEventLoopTest extends AbstractLoopTest { + /** @var ?string */ + private $fifoPath; + public function createLoop($readStreamCompatible = false) { if ('Linux' === PHP_OS && !extension_loaded('posix')) { @@ -19,12 +22,23 @@ public function createLoop($readStreamCompatible = false) return new ExtEventLoop(); } + /** + * @after + */ + public function tearDownFile() + { + if ($this->fifoPath !== null && file_exists($this->fifoPath)) { + unlink($this->fifoPath); + } + } + 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-'); + assert(is_string($this->fifoPath)); unlink($this->fifoPath); diff --git a/tests/ExtLibeventLoopTest.php b/tests/ExtLibeventLoopTest.php index 32a852b8..524e0548 100644 --- a/tests/ExtLibeventLoopTest.php +++ b/tests/ExtLibeventLoopTest.php @@ -6,6 +6,7 @@ class ExtLibeventLoopTest extends AbstractLoopTest { + /** @var ?string */ private $fifoPath; public function createLoop() @@ -26,7 +27,7 @@ public function createLoop() */ public function tearDownFile() { - if (file_exists($this->fifoPath)) { + if ($this->fifoPath !== null && file_exists($this->fifoPath)) { unlink($this->fifoPath); } } @@ -38,6 +39,7 @@ public function createStream() } $this->fifoPath = tempnam(sys_get_temp_dir(), 'react-'); + assert(is_string($this->fifoPath)); unlink($this->fifoPath); From 8e6d223553d4a3170695d2859a07b62a1d078139 Mon Sep 17 00:00:00 2001 From: James Lucas Date: Tue, 15 Feb 2022 22:07:59 +1100 Subject: [PATCH 56/85] When initialising a periodic EvTimer, the after and repeat parameters should take the adjusted interval value from Timer. This takes into account any MIN_INTERVAL bounds are applied when initialising the Timer --- src/ExtEvLoop.php | 2 +- src/ExtLibevLoop.php | 2 +- tests/Timer/AbstractTimerTest.php | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/ExtEvLoop.php b/src/ExtEvLoop.php index 7fcc29af..e823c37e 100644 --- a/src/ExtEvLoop.php +++ b/src/ExtEvLoop.php @@ -162,7 +162,7 @@ public function addPeriodicTimer($interval, $callback) \call_user_func($timer->getCallback(), $timer); }; - $event = $this->loop->timer($interval, $interval, $callback); + $event = $this->loop->timer($timer->getInterval(), $timer->getInterval(), $callback); $this->timers->attach($timer, $event); return $timer; diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php index 2cf1ad54..c303fdd5 100644 --- a/src/ExtLibevLoop.php +++ b/src/ExtLibevLoop.php @@ -132,7 +132,7 @@ public function addPeriodicTimer($interval, $callback) \call_user_func($timer->getCallback(), $timer); }; - $event = new TimerEvent($callback, $interval, $interval); + $event = new TimerEvent($callback, $timer->getInterval(), $timer->getInterval()); $this->timerEvents->attach($timer, $event); $this->loop->add($event); diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index c5198385..bbea46f8 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -121,6 +121,26 @@ public function testMinimumIntervalOneMicrosecond() $this->assertEquals(0.000001, $timer->getInterval()); } + public function testAddPeriodicTimerWithZeroIntervalWillExecuteCallbackFunctionAtLeastTwice() + { + $loop = $this->createLoop(); + + $timeout = $loop->addTimer(2, $this->expectCallableNever()); //Timeout the test after two seconds if the periodic timer hasn't fired twice + + $i = 0; + $loop->addPeriodicTimer(0, function ($timer) use (&$i, $loop, $timeout) { + ++$i; + if ($i === 2) { + $loop->cancelTimer($timer); + $loop->cancelTimer($timeout); + } + }); + + $loop->run(); + + $this->assertEquals(2, $i); + } + public function testTimerIntervalBelowZeroRunsImmediately() { $loop = $this->createLoop(); From bc0bca9c6d88320d552fecd6d5e755ca081fb6e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 17 Feb 2022 15:50:48 +0100 Subject: [PATCH 57/85] Do not suppress warnings for invalid streams in `stream_select()` --- src/StreamSelectLoop.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 4b1f81c1..14a2613f 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -287,8 +287,15 @@ private function streamSelect(array &$read, array &$write, $timeout) } } - // suppress warnings that occur, when stream_select is interrupted by a signal - $ret = @\stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); + // suppress warnings that occur when `stream_select()` is interrupted by a signal + \set_error_handler(function ($errno, $errstr) { + $eintr = \defined('SOCKET_EINTR') ? \SOCKET_EINTR : 4; + return ($errno === \E_WARNING && \strpos($errstr, '[' . $eintr .']: ') !== false); + }); + + $ret = \stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); + + \restore_error_handler(); if ($except) { $write = \array_merge($write, $except); From 2c8533c82a18a206ec829d85497957ccd824c0ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 18 Feb 2022 15:38:25 +0100 Subject: [PATCH 58/85] Explicitly forward warnings to any registered error handlers --- src/StreamSelectLoop.php | 25 ++++++++--- tests/StreamSelectLoopTest.php | 79 ++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 14a2613f..9e78dc03 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -287,15 +287,28 @@ private function streamSelect(array &$read, array &$write, $timeout) } } - // suppress warnings that occur when `stream_select()` is interrupted by a signal - \set_error_handler(function ($errno, $errstr) { + /** @var ?callable $previous */ + $previous = \set_error_handler(function ($errno, $errstr) use (&$previous) { + // suppress warnings that occur when `stream_select()` is interrupted by a signal $eintr = \defined('SOCKET_EINTR') ? \SOCKET_EINTR : 4; - return ($errno === \E_WARNING && \strpos($errstr, '[' . $eintr .']: ') !== false); - }); + if ($errno === \E_WARNING && \strpos($errstr, '[' . $eintr .']: ') !== false) { + return; + } - $ret = \stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); + // forward any other error to registered error handler or print warning + return ($previous !== null) ? \call_user_func_array($previous, \func_get_args()) : false; + }); - \restore_error_handler(); + try { + $ret = \stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); + \restore_error_handler(); + } catch (\Throwable $e) { // @codeCoverageIgnoreStart + \restore_error_handler(); + throw $e; + } catch (\Exception $e) { + \restore_error_handler(); + throw $e; + } // @codeCoverageIgnoreEnd if ($except) { $write = \array_merge($write, $except); diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index d6800c5e..80197468 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -40,6 +40,85 @@ public function testStreamSelectTimeoutEmulation() $this->assertGreaterThan(0.04, $interval); } + public function testStreamSelectReportsWarningForStreamWithFilter() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('Not supported on legacy HHVM'); + } + + $stream = tmpfile(); + stream_filter_append($stream, 'string.rot13'); + + $this->loop->addReadStream($stream, $this->expectCallableNever()); + + $loop = $this->loop; + $this->loop->futureTick(function () use ($loop, $stream) { + $loop->futureTick(function () use ($loop, $stream) { + $loop->removeReadStream($stream); + }); + }); + + $error = null; + $previous = set_error_handler(function ($_, $errstr) use (&$error) { + $error = $errstr; + }); + + try { + $this->loop->run(); + } catch (\ValueError $e) { + // ignore ValueError for PHP 8+ due to empty stream array + } + + restore_error_handler(); + + $this->assertNotNull($error); + + $now = set_error_handler(function () { }); + restore_error_handler(); + $this->assertEquals($previous, $now); + } + + public function testStreamSelectThrowsWhenCustomErrorHandlerThrowsForStreamWithFilter() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('Not supported on legacy HHVM'); + } + + $stream = tmpfile(); + stream_filter_append($stream, 'string.rot13'); + + $this->loop->addReadStream($stream, $this->expectCallableNever()); + + $loop = $this->loop; + $this->loop->futureTick(function () use ($loop, $stream) { + $loop->futureTick(function () use ($loop, $stream) { + $loop->removeReadStream($stream); + }); + }); + + $previous = set_error_handler(function ($_, $errstr) { + throw new \RuntimeException($errstr); + }); + + $e = null; + try { + $this->loop->run(); + restore_error_handler(); + $this->fail(); + } catch (\RuntimeException $e) { + restore_error_handler(); + } catch (\ValueError $e) { + restore_error_handler(); // PHP 8+ + $e = $e->getPrevious(); + } + + $this->assertInstanceOf('RuntimeException', $e); + + $now = set_error_handler(function () { }); + restore_error_handler(); + $this->assertEquals($previous, $now); + } + public function signalProvider() { return array( From a89338551f073f5a81a5ae14579e28ff31dd2913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 22 Feb 2022 16:33:38 +0100 Subject: [PATCH 59/85] Improve performance of `StreamSelectLoop` when no timers are scheduled --- src/Timer/Timers.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Timer/Timers.php b/src/Timer/Timers.php index 70adc132..3a33b8ce 100644 --- a/src/Timer/Timers.php +++ b/src/Timer/Timers.php @@ -73,6 +73,11 @@ public function isEmpty() public function tick() { + // hot path: skip timers if nothing is scheduled + if (!$this->schedule) { + return; + } + // ensure timers are sorted so we can execute in order if (!$this->sorted) { $this->sorted = true; From 993bb89b574bd880c9aff99a69b03164bf2cbd0c Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Tue, 15 Mar 2022 13:37:28 +0100 Subject: [PATCH 60/85] Add badge to show number of project installations --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 77703761..d27cc47a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # EventLoop Component [![CI status](https://github.com/reactphp/event-loop/workflows/CI/badge.svg)](https://github.com/reactphp/event-loop/actions) +[![installs on Packagist](https://img.shields.io/packagist/dt/react/event-loop?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/event-loop) [ReactPHP](https://reactphp.org/)'s core reactor event loop that libraries can use for evented I/O. From a35d6d43c9cc582d52d59ccc9fbf55136dc52261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 12 Mar 2022 16:28:39 +0100 Subject: [PATCH 61/85] Minor documentation improvements --- README.md | 12 ++++++------ src/ExtLibeventLoop.php | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 77703761..e9ec62de 100644 --- a/README.md +++ b/README.md @@ -83,8 +83,8 @@ See also the [examples](examples). ## Usage -As of `v1.2.0`, typical applications would use the [`Loop` object](#loop) -to use the currently active event loop like this: +Typical applications would use the [`Loop` class](#loop) to use the default +event loop like this: ```php use React\EventLoop\Loop; @@ -123,14 +123,14 @@ In both cases, the program would perform the exact same steps. 1. The event loop instance is created at the beginning of the program. This is implicitly done the first time you call the [`Loop` class](#loop) or - explicitly when using the deprecated [`Factory::create() method`](#create) + explicitly when using the deprecated [`Factory::create()` method](#create) (or manually instantiating any of the [loop implementations](#loop-implementations)). 2. The event loop is used directly or passed as an instance to library and application code. In this example, a periodic timer is registered with the event loop which simply outputs `Tick` every fraction of a second until another timer stops the periodic timer after a second. 3. The event loop is run at the end of the program. This is automatically done - when using [`Loop` class](#loop) or explicitly with a single [`run()`](#run) + when using the [`Loop` class](#loop) or explicitly with a single [`run()`](#run) call at the end of the program. As of `v1.2.0`, we highly recommend using the [`Loop` class](#loop). @@ -434,8 +434,8 @@ 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, neither the [`Loop` object](#loop) nor the deprecated -[`Factory`](#factory) will try to use this event loop on PHP 7. +Accordingly, neither the [`Loop` class](#loop) nor the deprecated +[`Factory` class](#factory) will 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 diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index d5699d8f..099293a4 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -20,8 +20,8 @@ * 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, neither the [`Loop` object](#loop) nor the deprecated - * [`Factory`](#factory) will try to use this event loop on PHP 7. + * Accordingly, neither the [`Loop` class](#loop) nor the deprecated + * [`Factory` class](#factory) will 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 From 7d10eba87206e4d8ff75fd33ebd89e4fb63eca05 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Wed, 16 Mar 2022 10:02:34 +0100 Subject: [PATCH 62/85] Update supported PHP versions for loop extensions --- .github/workflows/ci.yml | 4 ++-- README.md | 8 ++++---- src/ExtEvLoop.php | 2 +- src/ExtEventLoop.php | 2 +- src/ExtUvLoop.php | 2 +- src/StreamSelectLoop.php | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65d76f23..21d27fcc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,12 +29,12 @@ jobs: php-version: ${{ matrix.php }} coverage: xdebug - run: sudo apt-get update && sudo apt-get install libevent-dev - - name: Install ext-event between PHP 5.4 and PHP 7.x + - name: Install ext-event on PHP >= 5.4 run: | echo "yes" | sudo pecl install event # explicitly enable extensions in php.ini on PHP 5.6+ php -r 'exit((int)(PHP_VERSION_ID >= 50600));' || echo "extension=event.so" | sudo tee -a "$(php -r 'echo php_ini_loaded_file();')" - if: ${{ matrix.php >= 5.4 && matrix.php < 8.0 }} + if: ${{ matrix.php >= 5.4 }} - name: Install ext-ev on PHP >= 5.4 run: | echo "yes" | sudo pecl install ev diff --git a/README.md b/README.md index 77703761..0e1fa8d7 100644 --- a/README.md +++ b/README.md @@ -353,7 +353,7 @@ 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 that works out of the box with PHP. -This event loop works out of the box on PHP 5.3 through PHP 7+ and HHVM. +This event loop works out of the box on PHP 5.3 through PHP 8+ and HHVM. This means that no installation is required and this library works on all platforms and supported PHP versions. Accordingly, the [`Loop` class](#loop) and the deprecated [`Factory`](#factory) @@ -397,7 +397,7 @@ 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+. +This loop is known to work with PHP 5.4 through PHP 8+. #### ExtEvLoop @@ -408,7 +408,7 @@ 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+. +This loop is known to work with PHP 5.4 through PHP 8+. #### ExtUvLoop @@ -418,7 +418,7 @@ 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+. +This loop is known to work with PHP 7.x. #### ~~ExtLibeventLoop~~ diff --git a/src/ExtEvLoop.php b/src/ExtEvLoop.php index e823c37e..a3fcec68 100644 --- a/src/ExtEvLoop.php +++ b/src/ExtEvLoop.php @@ -16,7 +16,7 @@ * 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+. + * This loop is known to work with PHP 5.4 through PHP 8+. * * @see http://php.net/manual/en/book.ev.php * @see https://bitbucket.org/osmanov/pecl-ev/overview diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 7ce50010..b162a402 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -16,7 +16,7 @@ * 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+. + * This loop is known to work with PHP 5.4 through PHP 8+. * * @link https://pecl.php.net/package/event */ diff --git a/src/ExtUvLoop.php b/src/ExtUvLoop.php index 4434720d..631a4593 100644 --- a/src/ExtUvLoop.php +++ b/src/ExtUvLoop.php @@ -13,7 +13,7 @@ * 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+. + * This loop is known to work with PHP 7.x. * * @see https://github.com/bwoebi/php-uv */ diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 9e78dc03..71983da0 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -12,7 +12,7 @@ * This uses the [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php) * function and is the only implementation that 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 event loop works out of the box on PHP 5.4 through PHP 8+ and HHVM. * This means that no installation is required and this library works on all * platforms and supported PHP versions. * Accordingly, the [`Loop` class](#loop) and the deprecated [`Factory`](#factory) From 187fb56f46d424afb6ec4ad089269c72eec2e137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 17 Mar 2022 12:10:22 +0100 Subject: [PATCH 63/85] Prepare v1.3.0 release --- CHANGELOG.md | 17 +++++++++++++++++ README.md | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd9e69d4..b0170399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## 1.3.0 (2022-03-17) + +* Feature: Improve default `StreamSelectLoop` to report any warnings for invalid streams. + (#245 by @clue) + +* Feature: Improve performance of `StreamSelectLoop` when no timers are scheduled. + (#246 by @clue) + +* Fix: Fix periodic timer with zero interval for `ExtEvLoop` and legacy `ExtLibevLoop`. + (#243 by @lucasnetau) + +* Minor documentation improvements, update PHP version references. + (#240, #248 and #250 by @SimonFrings, #241 by @dbu and #249 by @clue) + +* Improve test suite and test against PHP 8.1. + (#238 by @WyriHaximus and #242 by @clue) + ## 1.2.0 (2021-07-11) A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop). diff --git a/README.md b/README.md index 24b14532..46564ae7 100644 --- a/README.md +++ b/README.md @@ -889,7 +889,7 @@ This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -$ composer require react/event-loop:^1.2 +$ composer require react/event-loop:^1.3 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 1bc80cc85665e10034c49eebbf04bd747171820c Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Tue, 12 Apr 2022 10:21:09 +0200 Subject: [PATCH 64/85] Fix legacy HHVM build by downgrading Composer --- .github/workflows/ci.yml | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21d27fcc..f4fd7256 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,5 +110,6 @@ jobs: - uses: azjezz/setup-hhvm@v1 with: version: lts-3.30 + - run: composer self-update --2.2 # downgrade Composer for HHVM - run: hhvm $(which composer) install - run: hhvm vendor/bin/phpunit diff --git a/README.md b/README.md index 46564ae7..08357e2f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # EventLoop Component -[![CI status](https://github.com/reactphp/event-loop/workflows/CI/badge.svg)](https://github.com/reactphp/event-loop/actions) +[![CI status](https://github.com/reactphp/event-loop/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/event-loop/actions) [![installs on Packagist](https://img.shields.io/packagist/dt/react/event-loop?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/event-loop) [ReactPHP](https://reactphp.org/)'s core reactor event loop that libraries can use for evented I/O. From a9a3080d641c3e7878dfae8b127240d4c97e39e6 Mon Sep 17 00:00:00 2001 From: Nicolas Hedger Date: Mon, 20 Jun 2022 16:14:49 +0200 Subject: [PATCH 65/85] chore(docs): remove leading dollar sign --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 08357e2f..f86e268e 100644 --- a/README.md +++ b/README.md @@ -889,7 +889,7 @@ This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -$ composer require react/event-loop:^1.3 +composer require react/event-loop:^1.3 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. @@ -908,13 +908,13 @@ To run the test suite, you first need to clone this repo and then install all dependencies [through Composer](https://getcomposer.org/): ```bash -$ composer install +composer install ``` To run the test suite, go to the project root and run: ```bash -$ vendor/bin/phpunit +vendor/bin/phpunit ``` ## License From 1baee2ac1aa7bc10b2b921f4015ba859f051d764 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Mon, 24 Oct 2022 11:50:55 +0200 Subject: [PATCH 66/85] Add issue template for better orientation --- .github/ISSUE_TEMPLATE/bug.md | 11 +++++++++++ .github/ISSUE_TEMPLATE/config.yml | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 00000000..d26fe152 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,11 @@ +--- +name: Bug report +about: Found a bug in our project? Create a report to help us improve. +labels: bug +--- + + + +```php +// Please add code examples if possible, so we can reproduce your steps +``` diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..4b4a0ea6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Report a security vulnerability + url: https://reactphp.org/#support + about: 'If you discover a security vulnerability, please send us an email. Do not disclose security-related issues publicly.' + - name: Feature request + url: https://github.com/orgs/reactphp/discussions/categories/ideas + about: 'You have ideas to improve our project? Start a new discussion in our "Ideas" category.' + - name: Questions + url: https://github.com/orgs/reactphp/discussions/categories/q-a + about: 'We are happy to answer your questions! Start a new discussion in our "Q&A" category.' From ccdeaccb0445e0c73bec887b59c714075e4f38e2 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Fri, 18 Nov 2022 15:33:35 +0100 Subject: [PATCH 67/85] Revert issue template changes to use organisation issue template --- .github/ISSUE_TEMPLATE/bug.md | 11 ----------- .github/ISSUE_TEMPLATE/config.yml | 11 ----------- 2 files changed, 22 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug.md delete mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md deleted file mode 100644 index d26fe152..00000000 --- a/.github/ISSUE_TEMPLATE/bug.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: Bug report -about: Found a bug in our project? Create a report to help us improve. -labels: bug ---- - - - -```php -// Please add code examples if possible, so we can reproduce your steps -``` diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 4b4a0ea6..00000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,11 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Report a security vulnerability - url: https://reactphp.org/#support - about: 'If you discover a security vulnerability, please send us an email. Do not disclose security-related issues publicly.' - - name: Feature request - url: https://github.com/orgs/reactphp/discussions/categories/ideas - about: 'You have ideas to improve our project? Start a new discussion in our "Ideas" category.' - - name: Questions - url: https://github.com/orgs/reactphp/discussions/categories/q-a - about: 'We are happy to answer your questions! Start a new discussion in our "Q&A" category.' From de07690c7e9c3f8f83a41048749172f627d6a74c Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 13 Aug 2022 15:58:41 +0200 Subject: [PATCH 68/85] Test on PHP 8.2 With PHP 8.2 coming out later this year, we should be reading for it's release to ensure all out code works on it. --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4fd7256..4d257be6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: strategy: matrix: php: + - 8.2 - 8.1 - 8.0 - 7.4 @@ -82,6 +83,7 @@ jobs: strategy: matrix: php: + - 8.2 - 8.1 - 8.0 - 7.4 From d1595b7921f0c347a81d82b840a88037b8b6a3e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 8 Jan 2023 13:34:55 +0100 Subject: [PATCH 69/85] Update test suite and report failed assertions --- .github/workflows/ci.yml | 75 +++++++++++++++++++++++----------- README.md | 6 +-- composer.json | 10 ++--- phpunit.xml.dist | 16 ++++++-- phpunit.xml.legacy | 12 +++++- tests/AbstractLoopTest.php | 18 +++++++- tests/StreamSelectLoopTest.php | 4 ++ 7 files changed, 100 insertions(+), 41 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d257be6..a12e827b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: jobs: PHPUnit: name: PHPUnit (PHP ${{ matrix.php }}) - runs-on: ubuntu-18.04 # legacy Ubuntu 18.04 for legacy libevent + runs-on: ubuntu-22.04 strategy: matrix: php: @@ -24,24 +24,46 @@ jobs: - 5.4 - 5.3 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} coverage: xdebug - - run: sudo apt-get update && sudo apt-get install libevent-dev - - name: Install ext-event on PHP >= 5.4 - run: | - echo "yes" | sudo pecl install event - # explicitly enable extensions in php.ini on PHP 5.6+ - php -r 'exit((int)(PHP_VERSION_ID >= 50600));' || echo "extension=event.so" | sudo tee -a "$(php -r 'echo php_ini_loaded_file();')" - if: ${{ matrix.php >= 5.4 }} - - name: Install ext-ev on PHP >= 5.4 - run: | - echo "yes" | sudo pecl install ev - # explicitly enable extensions in php.ini on PHP 5.6+ - php -r 'exit((int)(PHP_VERSION_ID >= 50600));' || echo "extension=ev.so" | sudo tee -a "$(php -r 'echo php_ini_loaded_file();')" - if: ${{ matrix.php >= 5.4 }} + ini-file: development + ini-values: disable_functions='' # do not disable PCNTL functions on PHP < 8.1 + extensions: sockets, pcntl ${{ matrix.php >= 5.6 && ', event' || '' }} ${{ matrix.php >= 5.4 && ', ev' || '' }} + env: + fail-fast: true # fail step if any extension can not be installed + - run: composer install + - run: vendor/bin/phpunit --coverage-text + if: ${{ matrix.php >= 7.3 }} + - run: vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy + if: ${{ matrix.php < 7.3 }} + + PHPUnit-Unstable: + name: PHPUnit (Unstable PHP ${{ matrix.php }}) + runs-on: ubuntu-20.04 + continue-on-error: true + strategy: + matrix: + php: + - 7.4 + - 7.3 + - 7.2 + - 7.1 + - 7.0 + - 5.6 + - 5.5 + - 5.4 + - 5.3 + steps: + - uses: actions/checkout@v3 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: xdebug + ini-file: development + extensions: sockets, pcntl - name: Install ext-uv on PHP 7.x run: | sudo add-apt-repository ppa:ondrej/php -y && sudo apt-get update -q && sudo apt-get install libuv1-dev @@ -50,6 +72,7 @@ jobs: if: ${{ matrix.php >= 7.0 && matrix.php < 8.0 }} - name: Install legacy ext-libevent on PHP < 7.0 run: | + sudo apt-get update && sudo apt-get install libevent-dev curl http://pecl.php.net/get/libevent-0.1.0.tgz | tar -xz pushd libevent-0.1.0 phpize @@ -78,7 +101,7 @@ jobs: PHPUnit-Windows: name: PHPUnit (PHP ${{ matrix.php }} on Windows) - runs-on: windows-2019 + runs-on: windows-2022 continue-on-error: true strategy: matrix: @@ -91,11 +114,12 @@ jobs: - 7.2 - 7.1 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} coverage: xdebug + ini-file: development extensions: sockets,event # future: add uv-beta (installs, but can not load) - run: composer install - run: vendor/bin/phpunit --coverage-text @@ -105,13 +129,16 @@ jobs: PHPUnit-hhvm: name: PHPUnit (HHVM) - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 continue-on-error: true steps: - - uses: actions/checkout@v2 - - uses: azjezz/setup-hhvm@v1 + - uses: actions/checkout@v3 + - run: cp "$(which composer)" composer.phar && ./composer.phar self-update --2.2 # downgrade Composer for HHVM + - name: Run hhvm composer.phar install + uses: docker://hhvm/hhvm:3.30-lts-latest + with: + args: hhvm composer.phar install + - name: Run hhvm vendor/bin/phpunit + uses: docker://hhvm/hhvm:3.30-lts-latest with: - version: lts-3.30 - - run: composer self-update --2.2 # downgrade Composer for HHVM - - run: hhvm $(which composer) install - - run: hhvm vendor/bin/phpunit + args: hhvm vendor/bin/phpunit diff --git a/README.md b/README.md index f86e268e..7fd01964 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# EventLoop Component +# EventLoop [![CI status](https://github.com/reactphp/event-loop/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/event-loop/actions) [![installs on Packagist](https://img.shields.io/packagist/dt/react/event-loop?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/event-loop) @@ -10,7 +10,7 @@ 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()`](#run) call that is controlled by the user. -**Table of Contents** +**Table of contents** * [Quickstart example](#quickstart-example) * [Usage](#usage) @@ -897,7 +897,7 @@ See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. This project aims to run on any platform and thus does not require any PHP extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM. -It's *highly recommended to use PHP 7+* for this project. +It's *highly recommended to use the latest supported PHP version* for this project. Installing any of the event loop extensions is suggested, but entirely optional. See also [event loop implementations](#loop-implementations) for more details. diff --git a/composer.json b/composer.json index d9b032e1..25a41fe1 100644 --- a/composer.json +++ b/composer.json @@ -29,21 +29,19 @@ "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "suggest": { - "ext-event": "~1.0 for ExtEventLoop", - "ext-pcntl": "For signal handling support when using the StreamSelectLoop", - "ext-uv": "* for ExtUvLoop" + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" }, "autoload": { "psr-4": { - "React\\EventLoop\\": "src" + "React\\EventLoop\\": "src/" } }, "autoload-dev": { "psr-4": { - "React\\Tests\\EventLoop\\": "tests" + "React\\Tests\\EventLoop\\": "tests/" } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 19a4b901..ffc13cd3 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,14 +1,14 @@ - - + - + ./tests/ @@ -17,4 +17,12 @@ ./src/ + + + + + + + + diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy index 632fccd1..03c2fed5 100644 --- a/phpunit.xml.legacy +++ b/phpunit.xml.legacy @@ -1,12 +1,12 @@ - + - + ./tests/ @@ -15,4 +15,12 @@ ./src/ + + + + + + + + diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 5b4da29e..a3511d66 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -657,11 +657,13 @@ public function testRemoveSignalNotRegisteredIsNoOp() /** * @requires extension pcntl + * @requires function posix_kill() + * @requires function posix_getpid() */ 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.'); + if ($this->loop instanceof StreamSelectLoop && !(\function_exists('pcntl_signal') && \function_exists('pcntl_signal_dispatch'))) { + $this->markTestSkipped('Signal handling with StreamSelectLoop requires pcntl_signal() and pcntl_signal_dispatch(), see also disable_functions'); } $called = false; @@ -696,6 +698,10 @@ public function testSignal() */ public function testSignalMultipleUsagesForTheSameListener() { + if ($this->loop instanceof StreamSelectLoop && !(\function_exists('pcntl_signal') && \function_exists('pcntl_signal_dispatch'))) { + $this->markTestSkipped('Signal handling with StreamSelectLoop requires pcntl_signal() and pcntl_signal_dispatch(), see also disable_functions'); + } + $funcCallCount = 0; $func = function () use (&$funcCallCount) { $funcCallCount++; @@ -723,6 +729,10 @@ public function testSignalMultipleUsagesForTheSameListener() */ public function testSignalsKeepTheLoopRunning() { + if ($this->loop instanceof StreamSelectLoop && !(\function_exists('pcntl_signal') && \function_exists('pcntl_signal_dispatch'))) { + $this->markTestSkipped('Signal handling with StreamSelectLoop requires pcntl_signal() and pcntl_signal_dispatch(), see also disable_functions'); + } + $loop = $this->loop; $function = function () {}; $this->loop->addSignal(SIGUSR1, $function); @@ -739,6 +749,10 @@ public function testSignalsKeepTheLoopRunning() */ public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop() { + if ($this->loop instanceof StreamSelectLoop && !(\function_exists('pcntl_signal') && \function_exists('pcntl_signal_dispatch'))) { + $this->markTestSkipped('Signal handling with StreamSelectLoop requires pcntl_signal() and pcntl_signal_dispatch(), see also disable_functions'); + } + $loop = $this->loop; $function = function () {}; $this->loop->addSignal(SIGUSR1, $function); diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index 80197468..7e2435a8 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -132,6 +132,8 @@ public function signalProvider() * Test signal interrupt when no stream is attached to the loop * @dataProvider signalProvider * @requires extension pcntl + * @requires function pcntl_signal() + * @requires function pcntl_signal_dispatch() */ public function testSignalInterruptNoStream($signal) { @@ -160,6 +162,8 @@ public function testSignalInterruptNoStream($signal) * Test signal interrupt when a stream is attached to the loop * @dataProvider signalProvider * @requires extension pcntl + * @requires function pcntl_signal() + * @requires function pcntl_signal_dispatch() */ public function testSignalInterruptWithStream($signal) { From 05225dcb984393a2bacfccf0ffc12c39ee74b640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 16 Mar 2023 12:01:16 +0100 Subject: [PATCH 70/85] Support checking `EINTR` constant from `ext-pcntl` without `ext-sockets` --- src/StreamSelectLoop.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 71983da0..1686fd74 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -290,7 +290,8 @@ private function streamSelect(array &$read, array &$write, $timeout) /** @var ?callable $previous */ $previous = \set_error_handler(function ($errno, $errstr) use (&$previous) { // suppress warnings that occur when `stream_select()` is interrupted by a signal - $eintr = \defined('SOCKET_EINTR') ? \SOCKET_EINTR : 4; + // PHP defines `EINTR` through `ext-sockets` or `ext-pcntl`, otherwise use common default (Linux & Mac) + $eintr = \defined('SOCKET_EINTR') ? \SOCKET_EINTR : (\defined('PCNTL_EINTR') ? \PCNTL_EINTR : 4); if ($errno === \E_WARNING && \strpos($errstr, '[' . $eintr .']: ') !== false) { return; } From e72f13916bab5dc03c3abfb853414d17a72bf695 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 5 Jan 2023 01:02:22 +0100 Subject: [PATCH 71/85] Replace 3rd party domains in examples with example.com While using a big tech 3rd party domain like google.com is guaranteed to work, there are special domains like example.com set up for this since the dawn of the internet. --- examples/13-http-client-blocking.php | 6 +++--- examples/14-http-client-async.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/13-http-client-blocking.php b/examples/13-http-client-blocking.php index f0562c90..ae119b5a 100644 --- a/examples/13-http-client-blocking.php +++ b/examples/13-http-client-blocking.php @@ -4,16 +4,16 @@ require __DIR__ . '/../vendor/autoload.php'; -// connect to www.google.com:80 (blocking call!) +// connect to example.com:80 (blocking call!) // for illustration purposes only, should use react/socket instead -$stream = stream_socket_client('tcp://www.google.com:80'); +$stream = stream_socket_client('tcp://example.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"); +fwrite($stream, "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"); // wait for HTTP response Loop::addReadStream($stream, function ($stream) { diff --git a/examples/14-http-client-async.php b/examples/14-http-client-async.php index 074a0eac..93419c02 100644 --- a/examples/14-http-client-async.php +++ b/examples/14-http-client-async.php @@ -7,7 +7,7 @@ // 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'); +$ip = gethostbyname('example.com'); if (ip2long($ip) === false) { echo 'Unable to resolve hostname' . PHP_EOL; exit(1); @@ -41,7 +41,7 @@ } // send HTTP request - fwrite($stream, "GET / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n"); + fwrite($stream, "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"); // wait for HTTP response Loop::addReadStream($stream, function ($stream) { From 2f6d3cd6c1426fca277892b8b43753c96e5fe317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 28 Feb 2023 15:40:14 +0100 Subject: [PATCH 72/85] Improve performance of `Loop` by avoiding unneeded method calls --- src/Loop.php | 67 +++++++++++++++---- tests/LoopTest.php | 159 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+), 13 deletions(-) diff --git a/src/Loop.php b/src/Loop.php index fd5d81c8..f74b9ef2 100644 --- a/src/Loop.php +++ b/src/Loop.php @@ -8,7 +8,7 @@ final class Loop { /** - * @var LoopInterface + * @var ?LoopInterface */ private static $instance; @@ -83,7 +83,11 @@ public static function set(LoopInterface $loop) */ public static function addReadStream($stream, $listener) { - self::get()->addReadStream($stream, $listener); + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) + if (self::$instance === null) { + self::get(); + } + self::$instance->addReadStream($stream, $listener); } /** @@ -97,7 +101,11 @@ public static function addReadStream($stream, $listener) */ public static function addWriteStream($stream, $listener) { - self::get()->addWriteStream($stream, $listener); + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) + if (self::$instance === null) { + self::get(); + } + self::$instance->addWriteStream($stream, $listener); } /** @@ -109,7 +117,9 @@ public static function addWriteStream($stream, $listener) */ public static function removeReadStream($stream) { - self::get()->removeReadStream($stream); + if (self::$instance !== null) { + self::$instance->removeReadStream($stream); + } } /** @@ -121,7 +131,9 @@ public static function removeReadStream($stream) */ public static function removeWriteStream($stream) { - self::get()->removeWriteStream($stream); + if (self::$instance !== null) { + self::$instance->removeWriteStream($stream); + } } /** @@ -134,7 +146,11 @@ public static function removeWriteStream($stream) */ public static function addTimer($interval, $callback) { - return self::get()->addTimer($interval, $callback); + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) + if (self::$instance === null) { + self::get(); + } + return self::$instance->addTimer($interval, $callback); } /** @@ -147,7 +163,11 @@ public static function addTimer($interval, $callback) */ public static function addPeriodicTimer($interval, $callback) { - return self::get()->addPeriodicTimer($interval, $callback); + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) + if (self::$instance === null) { + self::get(); + } + return self::$instance->addPeriodicTimer($interval, $callback); } /** @@ -159,7 +179,9 @@ public static function addPeriodicTimer($interval, $callback) */ public static function cancelTimer(TimerInterface $timer) { - return self::get()->cancelTimer($timer); + if (self::$instance !== null) { + self::$instance->cancelTimer($timer); + } } /** @@ -171,7 +193,12 @@ public static function cancelTimer(TimerInterface $timer) */ public static function futureTick($listener) { - self::get()->futureTick($listener); + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) + if (self::$instance === null) { + self::get(); + } + + self::$instance->futureTick($listener); } /** @@ -184,7 +211,12 @@ public static function futureTick($listener) */ public static function addSignal($signal, $listener) { - self::get()->addSignal($signal, $listener); + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) + if (self::$instance === null) { + self::get(); + } + + self::$instance->addSignal($signal, $listener); } /** @@ -197,7 +229,9 @@ public static function addSignal($signal, $listener) */ public static function removeSignal($signal, $listener) { - self::get()->removeSignal($signal, $listener); + if (self::$instance !== null) { + self::$instance->removeSignal($signal, $listener); + } } /** @@ -208,7 +242,12 @@ public static function removeSignal($signal, $listener) */ public static function run() { - self::get()->run(); + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) + if (self::$instance === null) { + self::get(); + } + + self::$instance->run(); } /** @@ -220,6 +259,8 @@ public static function run() public static function stop() { self::$stopped = true; - self::get()->stop(); + if (self::$instance !== null) { + self::$instance->stop(); + } } } diff --git a/tests/LoopTest.php b/tests/LoopTest.php index f3a13d34..833539ef 100644 --- a/tests/LoopTest.php +++ b/tests/LoopTest.php @@ -62,6 +62,19 @@ public function testStaticAddReadStreamCallsAddReadStreamOnLoopInstance() Loop::addReadStream($stream, $listener); } + public function testStaticAddReadStreamWithNoDefaultLoopCallsAddReadStreamOnNewLoopInstance() + { + $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref->setAccessible(true); + $ref->setValue(null); + + $stream = stream_socket_server('127.0.0.1:0'); + $listener = function () { }; + Loop::addReadStream($stream, $listener); + + $this->assertInstanceOf('React\EventLoop\LoopInterface', $ref->getValue()); + } + public function testStaticAddWriteStreamCallsAddWriteStreamOnLoopInstance() { $stream = tmpfile(); @@ -75,6 +88,19 @@ public function testStaticAddWriteStreamCallsAddWriteStreamOnLoopInstance() Loop::addWriteStream($stream, $listener); } + public function testStaticAddWriteStreamWithNoDefaultLoopCallsAddWriteStreamOnNewLoopInstance() + { + $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref->setAccessible(true); + $ref->setValue(null); + + $stream = stream_socket_server('127.0.0.1:0'); + $listener = function () { }; + Loop::addWriteStream($stream, $listener); + + $this->assertInstanceOf('React\EventLoop\LoopInterface', $ref->getValue()); + } + public function testStaticRemoveReadStreamCallsRemoveReadStreamOnLoopInstance() { $stream = tmpfile(); @@ -87,6 +113,18 @@ public function testStaticRemoveReadStreamCallsRemoveReadStreamOnLoopInstance() Loop::removeReadStream($stream); } + public function testStaticRemoveReadStreamWithNoDefaultLoopIsNoOp() + { + $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref->setAccessible(true); + $ref->setValue(null); + + $stream = tmpfile(); + Loop::removeReadStream($stream); + + $this->assertNull($ref->getValue()); + } + public function testStaticRemoveWriteStreamCallsRemoveWriteStreamOnLoopInstance() { $stream = tmpfile(); @@ -99,6 +137,18 @@ public function testStaticRemoveWriteStreamCallsRemoveWriteStreamOnLoopInstance( Loop::removeWriteStream($stream); } + public function testStaticRemoveWriteStreamWithNoDefaultLoopIsNoOp() + { + $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref->setAccessible(true); + $ref->setValue(null); + + $stream = tmpfile(); + Loop::removeWriteStream($stream); + + $this->assertNull($ref->getValue()); + } + public function testStaticAddTimerCallsAddTimerOnLoopInstanceAndReturnsTimerInstance() { $interval = 1.0; @@ -115,6 +165,20 @@ public function testStaticAddTimerCallsAddTimerOnLoopInstanceAndReturnsTimerInst $this->assertSame($timer, $ret); } + public function testStaticAddTimerWithNoDefaultLoopCallsAddTimerOnNewLoopInstance() + { + $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref->setAccessible(true); + $ref->setValue(null); + + $interval = 1.0; + $callback = function () { }; + $ret = Loop::addTimer($interval, $callback); + + $this->assertInstanceOf('React\EventLoop\TimerInterface', $ret); + $this->assertInstanceOf('React\EventLoop\LoopInterface', $ref->getValue()); + } + public function testStaticAddPeriodicTimerCallsAddPeriodicTimerOnLoopInstanceAndReturnsTimerInstance() { $interval = 1.0; @@ -131,6 +195,21 @@ public function testStaticAddPeriodicTimerCallsAddPeriodicTimerOnLoopInstanceAnd $this->assertSame($timer, $ret); } + public function testStaticAddPeriodicTimerWithNoDefaultLoopCallsAddPeriodicTimerOnNewLoopInstance() + { + $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref->setAccessible(true); + $ref->setValue(null); + + $interval = 1.0; + $callback = function () { }; + $ret = Loop::addPeriodicTimer($interval, $callback); + + $this->assertInstanceOf('React\EventLoop\TimerInterface', $ret); + $this->assertInstanceOf('React\EventLoop\LoopInterface', $ref->getValue()); + } + + public function testStaticCancelTimerCallsCancelTimerOnLoopInstance() { $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock(); @@ -143,6 +222,18 @@ public function testStaticCancelTimerCallsCancelTimerOnLoopInstance() Loop::cancelTimer($timer); } + public function testStaticCancelTimerWithNoDefaultLoopIsNoOp() + { + $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref->setAccessible(true); + $ref->setValue(null); + + $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock(); + Loop::cancelTimer($timer); + + $this->assertNull($ref->getValue()); + } + public function testStaticFutureTickCallsFutureTickOnLoopInstance() { $listener = function () { }; @@ -155,6 +246,18 @@ public function testStaticFutureTickCallsFutureTickOnLoopInstance() Loop::futureTick($listener); } + public function testStaticFutureTickWithNoDefaultLoopCallsFutureTickOnNewLoopInstance() + { + $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref->setAccessible(true); + $ref->setValue(null); + + $listener = function () { }; + Loop::futureTick($listener); + + $this->assertInstanceOf('React\EventLoop\LoopInterface', $ref->getValue()); + } + public function testStaticAddSignalCallsAddSignalOnLoopInstance() { $signal = 1; @@ -168,6 +271,27 @@ public function testStaticAddSignalCallsAddSignalOnLoopInstance() Loop::addSignal($signal, $listener); } + public function testStaticAddSignalWithNoDefaultLoopCallsAddSignalOnNewLoopInstance() + { + if (DIRECTORY_SEPARATOR === '\\') { + $this->markTestSkipped('Not supported on Windows'); + } + + $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref->setAccessible(true); + $ref->setValue(null); + + $signal = 1; + $listener = function () { }; + try { + Loop::addSignal($signal, $listener); + } catch (\BadMethodCallException $e) { + $this->markTestSkipped('Skipped: ' . $e->getMessage()); + } + + $this->assertInstanceOf('React\EventLoop\LoopInterface', $ref->getValue()); + } + public function testStaticRemoveSignalCallsRemoveSignalOnLoopInstance() { $signal = 1; @@ -181,6 +305,19 @@ public function testStaticRemoveSignalCallsRemoveSignalOnLoopInstance() Loop::removeSignal($signal, $listener); } + public function testStaticRemoveSignalWithNoDefaultLoopIsNoOp() + { + $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref->setAccessible(true); + $ref->setValue(null); + + $signal = 1; + $listener = function () { }; + Loop::removeSignal($signal, $listener); + + $this->assertNull($ref->getValue()); + } + public function testStaticRunCallsRunOnLoopInstance() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); @@ -191,6 +328,17 @@ public function testStaticRunCallsRunOnLoopInstance() Loop::run(); } + public function testStaticRunWithNoDefaultLoopCallsRunsOnNewLoopInstance() + { + $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref->setAccessible(true); + $ref->setValue(null); + + Loop::run(); + + $this->assertInstanceOf('React\EventLoop\LoopInterface', $ref->getValue()); + } + public function testStaticStopCallsStopOnLoopInstance() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); @@ -201,6 +349,17 @@ public function testStaticStopCallsStopOnLoopInstance() Loop::stop(); } + public function testStaticStopCallWithNoDefaultLoopIsNoOp() + { + $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref->setAccessible(true); + $ref->setValue(null); + + Loop::stop(); + + $this->assertNull($ref->getValue()); + } + /** * @after * @before From 6e7e587714fff7a83dcc7025aee42ab3b265ae05 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Fri, 5 May 2023 12:11:24 +0200 Subject: [PATCH 73/85] Prepare v1.4.0 release --- CHANGELOG.md | 14 ++++++++++++++ README.md | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0170399..1647dbe3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 1.4.0 (2023-05-05) + +* Feature: Improve performance of `Loop` by avoiding unneeded method calls. + (#266 by @clue) + +* Feature: Support checking `EINTR` constant from `ext-pcntl` without `ext-sockets`. + (#265 by @clue) + +* Minor documentation improvements. + (#254 by @nhedger) + +* Improve test suite, run tests on PHP 8.2 and report failed assertions. + (#258 by @WyriHaximus, #264 by @clue and #251, #261 and #262 by @SimonFrings) + ## 1.3.0 (2022-03-17) * Feature: Improve default `StreamSelectLoop` to report any warnings for invalid streams. diff --git a/README.md b/README.md index 7fd01964..8ec9621a 100644 --- a/README.md +++ b/README.md @@ -889,7 +889,7 @@ This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -composer require react/event-loop:^1.3 +composer require react/event-loop:^1.4 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 61c0555a475f803b75c4bb586163113d78baeb3b Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Fri, 1 Sep 2023 14:59:46 +0200 Subject: [PATCH 74/85] Install uv-extension in v0.2.4 for PHP 7.x in test matrix --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a12e827b..b64225a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: - name: Install ext-uv on PHP 7.x run: | sudo add-apt-repository ppa:ondrej/php -y && sudo apt-get update -q && sudo apt-get install libuv1-dev - echo "yes" | sudo pecl install uv-beta + echo "yes" | sudo pecl install uv-0.2.4 echo "extension=uv.so" >> "$(php -r 'echo php_ini_loaded_file();')" if: ${{ matrix.php >= 7.0 && matrix.php < 8.0 }} - name: Install legacy ext-libevent on PHP < 7.0 From 4a8f6af5e21afc83324184df0b8eb7caaacd41a3 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Wed, 30 Aug 2023 22:19:02 +0700 Subject: [PATCH 75/85] [Performance] Use spl_object_id() when possible --- src/Timer/Timers.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Timer/Timers.php b/src/Timer/Timers.php index 3a33b8ce..53c46d03 100644 --- a/src/Timer/Timers.php +++ b/src/Timer/Timers.php @@ -38,7 +38,7 @@ public function getTime() public function add(TimerInterface $timer) { - $id = \spl_object_hash($timer); + $id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer); $this->timers[$id] = $timer; $this->schedule[$id] = $timer->getInterval() + $this->updateTime(); $this->sorted = false; @@ -46,12 +46,13 @@ public function add(TimerInterface $timer) public function contains(TimerInterface $timer) { - return isset($this->timers[\spl_object_hash($timer)]); + $id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer); + return isset($this->timers[$id]); } public function cancel(TimerInterface $timer) { - $id = \spl_object_hash($timer); + $id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer); unset($this->timers[$id], $this->schedule[$id]); } From f1f14b8def8af6abfa9872922268ebb1bcf37966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 22 Oct 2023 10:56:22 +0200 Subject: [PATCH 76/85] Test on PHP 8.3 and update test environment --- .github/workflows/ci.yml | 10 ++++++---- tests/LoopTest.php | 33 +++++++++++++++------------------ 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b64225a2..639632a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: strategy: matrix: php: + - 8.3 - 8.2 - 8.1 - 8.0 @@ -24,7 +25,7 @@ jobs: - 5.4 - 5.3 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -57,7 +58,7 @@ jobs: - 5.4 - 5.3 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -106,6 +107,7 @@ jobs: strategy: matrix: php: + - 8.3 - 8.2 - 8.1 - 8.0 @@ -114,7 +116,7 @@ jobs: - 7.2 - 7.1 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -132,7 +134,7 @@ jobs: runs-on: ubuntu-22.04 continue-on-error: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: cp "$(which composer)" composer.phar && ./composer.phar self-update --2.2 # downgrade Composer for HHVM - name: Run hhvm composer.phar install uses: docker://hhvm/hhvm:3.30-lts-latest diff --git a/tests/LoopTest.php b/tests/LoopTest.php index 833539ef..42f85244 100644 --- a/tests/LoopTest.php +++ b/tests/LoopTest.php @@ -4,7 +4,6 @@ use React\EventLoop\Factory; use React\EventLoop\Loop; -use ReflectionClass; final class LoopTest extends TestCase { @@ -66,7 +65,7 @@ public function testStaticAddReadStreamWithNoDefaultLoopCallsAddReadStreamOnNewL { $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); $ref->setAccessible(true); - $ref->setValue(null); + $ref->setValue(null, null); $stream = stream_socket_server('127.0.0.1:0'); $listener = function () { }; @@ -92,7 +91,7 @@ public function testStaticAddWriteStreamWithNoDefaultLoopCallsAddWriteStreamOnNe { $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); $ref->setAccessible(true); - $ref->setValue(null); + $ref->setValue(null, null); $stream = stream_socket_server('127.0.0.1:0'); $listener = function () { }; @@ -117,7 +116,7 @@ public function testStaticRemoveReadStreamWithNoDefaultLoopIsNoOp() { $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); $ref->setAccessible(true); - $ref->setValue(null); + $ref->setValue(null, null); $stream = tmpfile(); Loop::removeReadStream($stream); @@ -141,7 +140,7 @@ public function testStaticRemoveWriteStreamWithNoDefaultLoopIsNoOp() { $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); $ref->setAccessible(true); - $ref->setValue(null); + $ref->setValue(null, null); $stream = tmpfile(); Loop::removeWriteStream($stream); @@ -169,7 +168,7 @@ public function testStaticAddTimerWithNoDefaultLoopCallsAddTimerOnNewLoopInstanc { $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); $ref->setAccessible(true); - $ref->setValue(null); + $ref->setValue(null, null); $interval = 1.0; $callback = function () { }; @@ -199,7 +198,7 @@ public function testStaticAddPeriodicTimerWithNoDefaultLoopCallsAddPeriodicTimer { $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); $ref->setAccessible(true); - $ref->setValue(null); + $ref->setValue(null, null); $interval = 1.0; $callback = function () { }; @@ -226,7 +225,7 @@ public function testStaticCancelTimerWithNoDefaultLoopIsNoOp() { $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); $ref->setAccessible(true); - $ref->setValue(null); + $ref->setValue(null, null); $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock(); Loop::cancelTimer($timer); @@ -250,7 +249,7 @@ public function testStaticFutureTickWithNoDefaultLoopCallsFutureTickOnNewLoopIns { $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); $ref->setAccessible(true); - $ref->setValue(null); + $ref->setValue(null, null); $listener = function () { }; Loop::futureTick($listener); @@ -279,7 +278,7 @@ public function testStaticAddSignalWithNoDefaultLoopCallsAddSignalOnNewLoopInsta $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); $ref->setAccessible(true); - $ref->setValue(null); + $ref->setValue(null, null); $signal = 1; $listener = function () { }; @@ -309,7 +308,7 @@ public function testStaticRemoveSignalWithNoDefaultLoopIsNoOp() { $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); $ref->setAccessible(true); - $ref->setValue(null); + $ref->setValue(null, null); $signal = 1; $listener = function () { }; @@ -332,7 +331,7 @@ public function testStaticRunWithNoDefaultLoopCallsRunsOnNewLoopInstance() { $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); $ref->setAccessible(true); - $ref->setValue(null); + $ref->setValue(null, null); Loop::run(); @@ -353,7 +352,7 @@ public function testStaticStopCallWithNoDefaultLoopIsNoOp() { $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); $ref->setAccessible(true); - $ref->setValue(null); + $ref->setValue(null, null); Loop::stop(); @@ -366,10 +365,8 @@ public function testStaticStopCallWithNoDefaultLoopIsNoOp() */ public function unsetLoopFromLoopAccessor() { - $ref = new ReflectionClass('\React\EventLoop\Loop'); - $prop = $ref->getProperty('instance'); - $prop->setAccessible(true); - $prop->setValue(null); - $prop->setAccessible(false); + $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref->setAccessible(true); + $ref->setValue(null, null); } } From dd4881b64931e69983980e9a2643adcdcc7e0094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 3 Sep 2023 14:14:24 +0200 Subject: [PATCH 77/85] Update tests for `ext-uv` on PHP 8+ --- .github/workflows/ci.yml | 14 +++++++++----- README.md | 2 +- src/ExtUvLoop.php | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 639632a1..0755cde8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,10 @@ jobs: strategy: matrix: php: + - 8.3 + - 8.2 + - 8.1 + - 8.0 - 7.4 - 7.3 - 7.2 @@ -65,12 +69,12 @@ jobs: coverage: xdebug ini-file: development extensions: sockets, pcntl - - name: Install ext-uv on PHP 7.x + - name: Install ext-uv on PHP 7+ run: | - sudo add-apt-repository ppa:ondrej/php -y && sudo apt-get update -q && sudo apt-get install libuv1-dev - echo "yes" | sudo pecl install uv-0.2.4 - echo "extension=uv.so" >> "$(php -r 'echo php_ini_loaded_file();')" - if: ${{ matrix.php >= 7.0 && matrix.php < 8.0 }} + sudo apt-get update -q && sudo apt-get install libuv1-dev + echo "yes" | sudo pecl install ${{ matrix.php >= 8.0 && 'uv-0.3.0' || 'uv-0.2.4' }} + php -m | grep -q uv || echo "extension=uv.so" >> "$(php -r 'echo php_ini_loaded_file();')" + if: ${{ matrix.php >= 7.0 }} - name: Install legacy ext-libevent on PHP < 7.0 run: | sudo apt-get update && sudo apt-get install libevent-dev diff --git a/README.md b/README.md index 8ec9621a..55888cb8 100644 --- a/README.md +++ b/README.md @@ -419,7 +419,7 @@ 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.x. +This loop is known to work with PHP 7+. #### ~~ExtLibeventLoop~~ diff --git a/src/ExtUvLoop.php b/src/ExtUvLoop.php index 631a4593..4434720d 100644 --- a/src/ExtUvLoop.php +++ b/src/ExtUvLoop.php @@ -13,7 +13,7 @@ * 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.x. + * This loop is known to work with PHP 7+. * * @see https://github.com/bwoebi/php-uv */ From bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Mon, 13 Nov 2023 14:48:05 +0100 Subject: [PATCH 78/85] Prepare v1.5.0 release --- CHANGELOG.md | 11 +++++++++++ README.md | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1647dbe3..e634b12e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 1.5.0 (2023-11-13) + +* Feature: Improve performance by using `spl_object_id()` on PHP 7.2+. + (#267 by @samsonasik) + +* Feature: Full PHP 8.3 compatibility. + (#269 by @clue) + +* Update tests for `ext-uv` on PHP 8+ and legacy PHP. + (#270 by @clue and #268 by @SimonFrings) + ## 1.4.0 (2023-05-05) * Feature: Improve performance of `Loop` by avoiding unneeded method calls. diff --git a/README.md b/README.md index 55888cb8..88a3e18b 100644 --- a/README.md +++ b/README.md @@ -889,7 +889,7 @@ This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -composer require react/event-loop:^1.4 +composer require react/event-loop:^1.5 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 18601028dcbd2702b6095ec1022f22d18704cce3 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 23 Jan 2024 17:48:07 +0100 Subject: [PATCH 79/85] Hello `3.x` development branch Once this PR is merged, we can start working on the new [v3.0.0 milestone](https://github.com/reactphp/event-loop/milestone/15). The default branch will be `3.x` and the old `1.x` branch still stay in place at least until `3.0.0` is released. Refs: Road map ticket for event-loop: #271 Plans for ReactPHP v3: https://github.com/orgs/reactphp/discussions/481 PR templated from: https://github.com/friends-of-reactphp/mysql/pull/185 --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 88a3e18b..d0a197dd 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,14 @@ [ReactPHP](https://reactphp.org/)'s core reactor event loop that libraries can use for evented I/O. +> **Development version:** This branch contains the code for the upcoming v3 +> release. For the code of the current stable v1 release, check out the +> [`1.x` branch](https://github.com/reactphp/event-loop/tree/1.x). +> +> The upcoming v3 release will be the way forward for this package. However, +> we will still actively support v1 for those not yet on the latest version. +> See also [installation instructions](#install) for more details. + 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 @@ -885,11 +893,11 @@ to remove a stream that was never added or is invalid has no effect. The recommended way to install this library is [through Composer](https://getcomposer.org/). [New to Composer?](https://getcomposer.org/doc/00-intro.md) -This project follows [SemVer](https://semver.org/). -This will install the latest supported version: +Once released, this project will follow [SemVer](https://semver.org/). +At the moment, this will install the latest development version: ```bash -composer require react/event-loop:^1.5 +composer require react/event-loop:^3@dev ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 8180e67f99a238e4f3e7cfdceef19fc67dcdc27a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 28 Jan 2024 14:08:19 +0100 Subject: [PATCH 80/85] Drop deprecated `ExtLibeventLoop` and `ExtLibevLoop` (PHP 5 only) --- .github/workflows/ci.yml | 30 +-- README.md | 42 ---- src/ExtLibevLoop.php | 201 ------------------- src/ExtLibeventLoop.php | 285 --------------------------- src/Factory.php | 9 - src/StreamSelectLoop.php | 5 +- tests/ExtLibevLoopTest.php | 22 --- tests/ExtLibeventLoopTest.php | 63 ------ tests/Timer/ExtLibevTimerTest.php | 17 -- tests/Timer/ExtLibeventTimerTest.php | 17 -- 10 files changed, 3 insertions(+), 688 deletions(-) delete mode 100644 src/ExtLibevLoop.php delete mode 100644 src/ExtLibeventLoop.php delete mode 100644 tests/ExtLibevLoopTest.php delete mode 100644 tests/ExtLibeventLoopTest.php delete mode 100644 tests/Timer/ExtLibevTimerTest.php delete mode 100644 tests/Timer/ExtLibeventTimerTest.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0755cde8..b4bf2b84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: PHPUnit-Unstable: name: PHPUnit (Unstable PHP ${{ matrix.php }}) - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 continue-on-error: true strategy: matrix: @@ -57,10 +57,6 @@ jobs: - 7.2 - 7.1 - 7.0 - - 5.6 - - 5.5 - - 5.4 - - 5.3 steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 @@ -74,30 +70,6 @@ jobs: sudo apt-get update -q && sudo apt-get install libuv1-dev echo "yes" | sudo pecl install ${{ matrix.php >= 8.0 && 'uv-0.3.0' || 'uv-0.2.4' }} php -m | grep -q uv || echo "extension=uv.so" >> "$(php -r 'echo php_ini_loaded_file();')" - if: ${{ matrix.php >= 7.0 }} - - name: Install legacy ext-libevent on PHP < 7.0 - run: | - sudo apt-get update && sudo apt-get install libevent-dev - curl http://pecl.php.net/get/libevent-0.1.0.tgz | tar -xz - pushd libevent-0.1.0 - phpize - ./configure - make - sudo make install - popd - echo "extension=libevent.so" | sudo tee -a "$(php -r 'echo php_ini_loaded_file();')" - if: ${{ matrix.php < 7.0 }} - - name: Install legacy ext-libev on PHP < 7.0 - run: | - git clone --recursive https://github.com/m4rw3r/php-libev - pushd php-libev - phpize - ./configure --with-libev - make - sudo make install - popd - echo "extension=libev.so" | sudo tee -a "$(php -r 'echo php_ini_loaded_file();')" - if: ${{ matrix.php < 7.0 }} - run: composer install - run: vendor/bin/phpunit --coverage-text if: ${{ matrix.php >= 7.3 }} diff --git a/README.md b/README.md index d0a197dd..93be6a57 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,6 @@ single [`run()`](#run) call that is controlled by the user. * [ExtEventLoop](#exteventloop) * [ExtEvLoop](#extevloop) * [ExtUvLoop](#extuvloop) - * [~~ExtLibeventLoop~~](#extlibeventloop) - * [~~ExtLibevLoop~~](#extlibevloop) * [LoopInterface](#loopinterface) * [run()](#run) * [stop()](#stop) @@ -429,46 +427,6 @@ that provides an interface to `libuv` library. This loop is known to work with PHP 7+. -#### ~~ExtLibeventLoop~~ - -> Deprecated since v1.2.0, use [`ExtEventLoop`](#exteventloop) instead. - -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, neither the [`Loop` class](#loop) nor the deprecated -[`Factory` class](#factory) will 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. - -#### ~~ExtLibevLoop~~ - -> Deprecated since v1.2.0, use [`ExtEvLoop`](#extevloop) instead. - -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. - ### LoopInterface #### run() diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php deleted file mode 100644 index c303fdd5..00000000 --- a/src/ExtLibevLoop.php +++ /dev/null @@ -1,201 +0,0 @@ -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, $timer->getInterval(), $timer->getInterval()); - $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; - } -} diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php deleted file mode 100644 index 099293a4..00000000 --- a/src/ExtLibeventLoop.php +++ /dev/null @@ -1,285 +0,0 @@ -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); - } - }; - } -} diff --git a/src/Factory.php b/src/Factory.php index 30bbfd7c..3d71ab97 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -52,10 +52,6 @@ private static function construct() return new ExtUvLoop(); } - if (\class_exists('libev\EventLoop', false)) { - return new ExtLibevLoop(); - } - if (\class_exists('EvLoop', false)) { return new ExtEvLoop(); } @@ -64,11 +60,6 @@ private static function construct() 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 } diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 1686fd74..943a81aa 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -15,9 +15,8 @@ * This event loop works out of the box on PHP 5.4 through PHP 8+ and HHVM. * This means that no installation is required and this library works on all * platforms and supported PHP versions. - * Accordingly, the [`Loop` class](#loop) and the deprecated [`Factory`](#factory) - * will use this event loop by default if you do not install any of the event loop - * extensions listed below. + * Accordingly, the [`Loop` class](#loop) 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 diff --git a/tests/ExtLibevLoopTest.php b/tests/ExtLibevLoopTest.php deleted file mode 100644 index 19a5e876..00000000 --- a/tests/ExtLibevLoopTest.php +++ /dev/null @@ -1,22 +0,0 @@ -markTestSkipped('libev tests skipped because ext-libev is not installed.'); - } - - return new ExtLibevLoop(); - } - - public function testLibEvConstructor() - { - $loop = new ExtLibevLoop(); - } -} diff --git a/tests/ExtLibeventLoopTest.php b/tests/ExtLibeventLoopTest.php deleted file mode 100644 index 524e0548..00000000 --- a/tests/ExtLibeventLoopTest.php +++ /dev/null @@ -1,63 +0,0 @@ -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 ($this->fifoPath !== null && 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-'); - assert(is_string($this->fifoPath)); - - 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); - } -} diff --git a/tests/Timer/ExtLibevTimerTest.php b/tests/Timer/ExtLibevTimerTest.php deleted file mode 100644 index 65e82bee..00000000 --- a/tests/Timer/ExtLibevTimerTest.php +++ /dev/null @@ -1,17 +0,0 @@ -markTestSkipped('libev tests skipped because ext-libev is not installed.'); - } - - return new ExtLibevLoop(); - } -} diff --git a/tests/Timer/ExtLibeventTimerTest.php b/tests/Timer/ExtLibeventTimerTest.php deleted file mode 100644 index 9089b9a5..00000000 --- a/tests/Timer/ExtLibeventTimerTest.php +++ /dev/null @@ -1,17 +0,0 @@ -markTestSkipped('libevent tests skipped because ext-libevent is not installed.'); - } - - return new ExtLibeventLoop(); - } -} From 10cc239e9b87c0a88ab6368cc94fd73967c34a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 4 Feb 2024 21:53:09 +0100 Subject: [PATCH 81/85] Drop deprecated `Factory`, use `Loop` instead --- README.md | 41 ++++------------------------ src/Factory.php | 66 ---------------------------------------------- src/Loop.php | 24 ++++++++++++++++- tests/LoopTest.php | 24 ----------------- 4 files changed, 28 insertions(+), 127 deletions(-) delete mode 100644 src/Factory.php diff --git a/README.md b/README.md index 93be6a57..ed9f0f45 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,6 @@ single [`run()`](#run) call that is controlled by the user. * [Loop methods](#loop-methods) * [Loop autorun](#loop-autorun) * [get()](#get) - * [~~Factory~~](#factory) - * [~~create()~~](#create) * [Loop implementations](#loop-implementations) * [StreamSelectLoop](#streamselectloop) * [ExtEventLoop](#exteventloop) @@ -111,7 +109,7 @@ beginning, reuse it throughout your program and finally run it at the end of the program like this: ```php -$loop = React\EventLoop\Loop::get(); // or deprecated React\EventLoop\Factory::create(); +$loop = React\EventLoop\Loop::get(); $timer = $loop->addPeriodicTimer(0.1, function () { echo 'Tick' . PHP_EOL; @@ -129,9 +127,8 @@ While the former is more concise, the latter is more explicit. In both cases, the program would perform the exact same steps. 1. The event loop instance is created at the beginning of the program. This is - implicitly done the first time you call the [`Loop` class](#loop) or - explicitly when using the deprecated [`Factory::create()` method](#create) - (or manually instantiating any of the [loop implementations](#loop-implementations)). + implicitly done the first time you call the [`Loop` class](#loop) + (or by manually instantiating any of the [loop implementations](#loop-implementations)). 2. The event loop is used directly or passed as an instance to library and application code. In this example, a periodic timer is registered with the event loop which simply outputs `Tick` every fraction of a second until another @@ -305,33 +302,6 @@ $greeter->greet('Bob'); See [`LoopInterface`](#loopinterface) for more details about available methods. -### ~~Factory~~ - -> Deprecated since v1.2.0, see [`Loop` class](#loop) instead. - -The deprecated `Factory` class exists as a convenient way to pick the best available -[event loop implementation](#loop-implementations). - -#### ~~create()~~ - -> Deprecated since v1.2.0, see [`Loop::get()`](#get) instead. - -The deprecated `create(): LoopInterface` method can be used to -create 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`](#loopinterface), -the actual [event loop implementation](#loop-implementations) is an implementation detail. - -This method should usually only be called once at the beginning of the program. - ### Loop implementations In addition to the [`LoopInterface`](#loopinterface), there are a number of @@ -363,9 +333,8 @@ function and is the only implementation that works out of the box with PHP. This event loop works out of the box on PHP 5.3 through PHP 8+ and HHVM. This means that no installation is required and this library works on all platforms and supported PHP versions. -Accordingly, the [`Loop` class](#loop) and the deprecated [`Factory`](#factory) -will use this event loop by default if you do not install any of the event loop -extensions listed below. +Accordingly, the [`Loop` class](#loop) 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 diff --git a/src/Factory.php b/src/Factory.php deleted file mode 100644 index 3d71ab97..00000000 --- a/src/Factory.php +++ /dev/null @@ -1,66 +0,0 @@ -stop(); } } + + /** + * @return LoopInterface + */ + private static function create() + { + // @codeCoverageIgnoreStart + if (\function_exists('uv_loop_new')) { + return new ExtUvLoop(); + } + + if (\class_exists('EvLoop', false)) { + return new ExtEvLoop(); + } + + if (\class_exists('EventBase', false)) { + return new ExtEventLoop(); + } + + return new StreamSelectLoop(); + // @codeCoverageIgnoreEnd + } } diff --git a/tests/LoopTest.php b/tests/LoopTest.php index 42f85244..80f5cccc 100644 --- a/tests/LoopTest.php +++ b/tests/LoopTest.php @@ -2,34 +2,10 @@ namespace React\Tests\EventLoop; -use React\EventLoop\Factory; use React\EventLoop\Loop; 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 */ From d3c10522025ddd811575af2d258a2bc6ee00c80f Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 24 Feb 2024 14:58:18 +0100 Subject: [PATCH 82/85] Update to require PHP 7.1+ --- .github/workflows/ci.yml | 26 ++------------------------ README.md | 11 +++++------ composer.json | 4 ++-- phpunit.xml.legacy | 2 +- src/ExtEvLoop.php | 2 +- src/ExtEventLoop.php | 10 +++------- src/ExtUvLoop.php | 2 +- src/StreamSelectLoop.php | 9 +++------ tests/AbstractLoopTest.php | 4 ---- tests/BinTest.php | 4 ---- tests/ExtEventLoopTest.php | 25 ------------------------- tests/StreamSelectLoopTest.php | 8 -------- 12 files changed, 18 insertions(+), 89 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4bf2b84..4e69fa12 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,11 +19,6 @@ jobs: - 7.3 - 7.2 - 7.1 - - 7.0 - - 5.6 - - 5.5 - - 5.4 - - 5.3 steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 @@ -32,7 +27,7 @@ jobs: coverage: xdebug ini-file: development ini-values: disable_functions='' # do not disable PCNTL functions on PHP < 8.1 - extensions: sockets, pcntl ${{ matrix.php >= 5.6 && ', event' || '' }} ${{ matrix.php >= 5.4 && ', ev' || '' }} + extensions: sockets, pcntl, event, ev env: fail-fast: true # fail step if any extension can not be installed - run: composer install @@ -56,7 +51,6 @@ jobs: - 7.3 - 7.2 - 7.1 - - 7.0 steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 @@ -65,7 +59,7 @@ jobs: coverage: xdebug ini-file: development extensions: sockets, pcntl - - name: Install ext-uv on PHP 7+ + - name: Install ext-uv run: | sudo apt-get update -q && sudo apt-get install libuv1-dev echo "yes" | sudo pecl install ${{ matrix.php >= 8.0 && 'uv-0.3.0' || 'uv-0.2.4' }} @@ -104,19 +98,3 @@ jobs: if: ${{ matrix.php >= 7.3 }} - run: vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy if: ${{ matrix.php < 7.3 }} - - PHPUnit-hhvm: - name: PHPUnit (HHVM) - runs-on: ubuntu-22.04 - continue-on-error: true - steps: - - uses: actions/checkout@v4 - - run: cp "$(which composer)" composer.phar && ./composer.phar self-update --2.2 # downgrade Composer for HHVM - - name: Run hhvm composer.phar install - uses: docker://hhvm/hhvm:3.30-lts-latest - with: - args: hhvm composer.phar install - - name: Run hhvm vendor/bin/phpunit - uses: docker://hhvm/hhvm:3.30-lts-latest - with: - args: hhvm vendor/bin/phpunit diff --git a/README.md b/README.md index ed9f0f45..8394784f 100644 --- a/README.md +++ b/README.md @@ -330,7 +330,7 @@ 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 that works out of the box with PHP. -This event loop works out of the box on PHP 5.3 through PHP 8+ and HHVM. +This event loop works out of the box on any PHP version. This means that no installation is required and this library works on all platforms and supported PHP versions. Accordingly, the [`Loop` class](#loop) will use this event loop by default if @@ -373,7 +373,7 @@ 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 8+. +This loop is known to work with PHP 7.1 through PHP 8+. #### ExtEvLoop @@ -384,7 +384,7 @@ 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 8+. +This loop is known to work with PHP 7.1 through PHP 8+. #### ExtUvLoop @@ -394,7 +394,7 @@ 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+. +This loop is known to work with PHP 7.1 through PHP 8+. ### LoopInterface @@ -830,8 +830,7 @@ composer require react/event-loop:^3@dev See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. This project aims to run on any platform and thus does not require any PHP -extensions and supports running on legacy PHP 5.3 through current PHP 8+ and -HHVM. +extensions and supports running on PHP 7.1 through current PHP 8+. It's *highly recommended to use the latest supported PHP version* for this project. Installing any of the event loop extensions is suggested, but entirely optional. diff --git a/composer.json b/composer.json index 25a41fe1..522bd8ca 100644 --- a/composer.json +++ b/composer.json @@ -26,10 +26,10 @@ } ], "require": { - "php": ">=5.3.0" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + "phpunit/phpunit": "^9.6 || ^5.7" }, "suggest": { "ext-pcntl": "For signal handling support when using the StreamSelectLoop" diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy index 03c2fed5..37671595 100644 --- a/phpunit.xml.legacy +++ b/phpunit.xml.legacy @@ -2,7 +2,7 @@ diff --git a/src/ExtEvLoop.php b/src/ExtEvLoop.php index a3fcec68..1cfc4b41 100644 --- a/src/ExtEvLoop.php +++ b/src/ExtEvLoop.php @@ -16,7 +16,7 @@ * 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 8+. + * This loop is known to work with PHP 7.1 through PHP 8+. * * @see http://php.net/manual/en/book.ev.php * @see https://bitbucket.org/osmanov/pecl-ev/overview diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index b162a402..8f5ce58b 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -16,7 +16,7 @@ * 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 8+. + * This loop is known to work with PHP 7.1 through PHP 8+. * * @link https://pecl.php.net/package/event */ @@ -85,9 +85,7 @@ public function addReadStream($stream, $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; - } + $this->readRefs[$key] = $stream; } public function addWriteStream($stream, $listener) @@ -104,9 +102,7 @@ public function addWriteStream($stream, $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; - } + $this->writeRefs[$key] = $stream; } public function removeReadStream($stream) diff --git a/src/ExtUvLoop.php b/src/ExtUvLoop.php index 4434720d..fc4cb3ab 100644 --- a/src/ExtUvLoop.php +++ b/src/ExtUvLoop.php @@ -13,7 +13,7 @@ * 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+. + * This loop is known to work with PHP 7.1 through PHP 8+. * * @see https://github.com/bwoebi/php-uv */ diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 943a81aa..4ff5cca8 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -12,7 +12,7 @@ * This uses the [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php) * function and is the only implementation that works out of the box with PHP. * - * This event loop works out of the box on PHP 5.4 through PHP 8+ and HHVM. + * This event loop works out of the box on any PHP version. * This means that no installation is required and this library works on all * platforms and supported PHP versions. * Accordingly, the [`Loop` class](#loop) will use this event loop by default if @@ -302,13 +302,10 @@ private function streamSelect(array &$read, array &$write, $timeout) try { $ret = \stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); \restore_error_handler(); - } catch (\Throwable $e) { // @codeCoverageIgnoreStart + } catch (\Throwable $e) { \restore_error_handler(); throw $e; - } catch (\Exception $e) { - \restore_error_handler(); - throw $e; - } // @codeCoverageIgnoreEnd + } if ($except) { $write = \array_merge($write, $except); diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index a3511d66..eb9591ba 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -118,10 +118,6 @@ public function testAddWriteStreamTriggersWhenSocketConnectionSucceeds() 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 diff --git a/tests/BinTest.php b/tests/BinTest.php index 6f8231b8..ebbad5e5 100644 --- a/tests/BinTest.php +++ b/tests/BinTest.php @@ -9,10 +9,6 @@ class BinTest extends TestCase */ 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/'); } diff --git a/tests/ExtEventLoopTest.php b/tests/ExtEventLoopTest.php index af4caa13..ce40ba58 100644 --- a/tests/ExtEventLoopTest.php +++ b/tests/ExtEventLoopTest.php @@ -70,29 +70,4 @@ public function 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); - } } diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index 7e2435a8..578bbe4b 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -42,10 +42,6 @@ public function testStreamSelectTimeoutEmulation() public function testStreamSelectReportsWarningForStreamWithFilter() { - if (defined('HHVM_VERSION')) { - $this->markTestSkipped('Not supported on legacy HHVM'); - } - $stream = tmpfile(); stream_filter_append($stream, 'string.rot13'); @@ -80,10 +76,6 @@ public function testStreamSelectReportsWarningForStreamWithFilter() public function testStreamSelectThrowsWhenCustomErrorHandlerThrowsForStreamWithFilter() { - if (defined('HHVM_VERSION')) { - $this->markTestSkipped('Not supported on legacy HHVM'); - } - $stream = tmpfile(); stream_filter_append($stream, 'string.rot13'); From e95a017499aad09ebabb71d75269728ef90ffc74 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 24 Feb 2024 15:31:33 +0100 Subject: [PATCH 83/85] Update PHP language syntax and remove legacy workarounds --- examples/12-generate-yes.php | 2 +- examples/91-benchmark-ticks.php | 2 +- examples/92-benchmark-timers.php | 2 +- examples/93-benchmark-ticks-delay.php | 2 +- examples/94-benchmark-timers-delay.php | 2 +- examples/95-benchmark-memory.php | 4 +- src/ExtEvLoop.php | 14 +-- src/ExtEventLoop.php | 37 +++--- src/ExtUvLoop.php | 21 ++-- src/Loop.php | 52 ++------ src/SignalsHandler.php | 4 +- src/StreamSelectLoop.php | 12 +- src/Timer/Timers.php | 4 +- tests/AbstractLoopTest.php | 167 +++++++++++-------------- tests/ExtUvLoopTest.php | 48 +++---- tests/LoopTest.php | 2 +- tests/StreamSelectLoopTest.php | 34 +++-- tests/TestCase.php | 6 +- 18 files changed, 173 insertions(+), 242 deletions(-) diff --git a/examples/12-generate-yes.php b/examples/12-generate-yes.php index a57e8d6e..91442016 100644 --- a/examples/12-generate-yes.php +++ b/examples/12-generate-yes.php @@ -5,7 +5,7 @@ require __DIR__ . '/../vendor/autoload.php'; // data can be given as first argument or defaults to "y" -$data = (isset($argv[1]) ? $argv[1] : 'y') . "\n"; +$data = ($argv[1] ?? 'y') . "\n"; // repeat data X times in order to fill around 200 KB $data = str_repeat($data, round(200000 / strlen($data))); diff --git a/examples/91-benchmark-ticks.php b/examples/91-benchmark-ticks.php index e3dc2b1c..7a38f424 100644 --- a/examples/91-benchmark-ticks.php +++ b/examples/91-benchmark-ticks.php @@ -4,7 +4,7 @@ require __DIR__ . '/../vendor/autoload.php'; -$n = isset($argv[1]) ? (int)$argv[1] : 1000 * 100; +$n = (int) ($argv[1] ?? 1000 * 100); for ($i = 0; $i < $n; ++$i) { Loop::futureTick(function () { }); diff --git a/examples/92-benchmark-timers.php b/examples/92-benchmark-timers.php index dd42ec77..ef838e60 100644 --- a/examples/92-benchmark-timers.php +++ b/examples/92-benchmark-timers.php @@ -4,7 +4,7 @@ require __DIR__ . '/../vendor/autoload.php'; -$n = isset($argv[1]) ? (int)$argv[1] : 1000 * 100; +$n = (int) ($argv[1] ?? 1000 * 100); for ($i = 0; $i < $n; ++$i) { Loop::addTimer(0, function () { }); diff --git a/examples/93-benchmark-ticks-delay.php b/examples/93-benchmark-ticks-delay.php index 1976124f..28a99070 100644 --- a/examples/93-benchmark-ticks-delay.php +++ b/examples/93-benchmark-ticks-delay.php @@ -4,7 +4,7 @@ require __DIR__ . '/../vendor/autoload.php'; -$ticks = isset($argv[1]) ? (int)$argv[1] : 1000 * 100; +$ticks = (int) ($argv[1] ?? 1000 * 100); $tick = function () use (&$tick, &$ticks) { if ($ticks > 0) { --$ticks; diff --git a/examples/94-benchmark-timers-delay.php b/examples/94-benchmark-timers-delay.php index dfe6c8c0..df237264 100644 --- a/examples/94-benchmark-timers-delay.php +++ b/examples/94-benchmark-timers-delay.php @@ -4,7 +4,7 @@ require __DIR__ . '/../vendor/autoload.php'; -$ticks = isset($argv[1]) ? (int)$argv[1] : 1000 * 100; +$ticks = (int) ($argv[1] ?? 1000 * 100); $tick = function () use (&$tick, &$ticks) { if ($ticks > 0) { --$ticks; diff --git a/examples/95-benchmark-memory.php b/examples/95-benchmark-memory.php index 06735bd2..efd8c82d 100644 --- a/examples/95-benchmark-memory.php +++ b/examples/95-benchmark-memory.php @@ -14,14 +14,14 @@ require __DIR__ . '/../vendor/autoload.php'; $args = getopt('t:l:r:'); -$t = isset($args['t']) ? (int)$args['t'] : 0; +$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; +$r = (int) ($args['r'] ?? 2); $runs = 0; diff --git a/src/ExtEvLoop.php b/src/ExtEvLoop.php index 1cfc4b41..363ad0c4 100644 --- a/src/ExtEvLoop.php +++ b/src/ExtEvLoop.php @@ -41,12 +41,12 @@ class ExtEvLoop implements LoopInterface /** * @var EvIo[] */ - private $readStreams = array(); + private $readStreams = []; /** * @var EvIo[] */ - private $writeStreams = array(); + private $writeStreams = []; /** * @var bool @@ -61,7 +61,7 @@ class ExtEvLoop implements LoopInterface /** * @var \EvSignal[] */ - private $signalEvents = array(); + private $signalEvents = []; public function __construct() { @@ -138,13 +138,11 @@ public function addTimer($interval, $callback) { $timer = new Timer($interval, $callback, false); - $that = $this; - $timers = $this->timers; - $callback = function () use ($timer, $timers, $that) { + $callback = function () use ($timer) { \call_user_func($timer->getCallback(), $timer); - if ($timers->contains($timer)) { - $that->cancelTimer($timer); + if ($this->timers->contains($timer)) { + $this->cancelTimer($timer); } }; diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 8f5ce58b..d6f24b2a 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -27,15 +27,15 @@ final class ExtEventLoop implements LoopInterface 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 $readEvents = []; + private $writeEvents = []; + private $readListeners = []; + private $writeListeners = []; + private $readRefs = []; + private $writeRefs = []; private $running; private $signals; - private $signalEvents = array(); + private $signalEvents = []; public function __construct() { @@ -67,8 +67,8 @@ public function __destruct() $this->timerEvents->detach($timer); } - $this->readEvents = array(); - $this->writeEvents = array(); + $this->readEvents = []; + $this->writeEvents = []; } public function addReadStream($stream, $listener) @@ -169,7 +169,7 @@ 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] = Event::signal($this->eventBase, $signal, [$this->signals, 'call']); $this->signalEvents[$signal]->add(); } } @@ -235,11 +235,10 @@ private function scheduleTimer(TimerInterface $timer) */ private function createTimerCallback() { - $timers = $this->timerEvents; - $this->timerCallback = function ($_, $__, $timer) use ($timers) { + $this->timerCallback = function ($_, $__, $timer) { \call_user_func($timer->getCallback(), $timer); - if (!$timer->isPeriodic() && $timers->contains($timer)) { + if (!$timer->isPeriodic() && $this->timerEvents->contains($timer)) { $this->cancelTimer($timer); } }; @@ -254,17 +253,15 @@ private function createTimerCallback() */ private function createStreamCallback() { - $read =& $this->readListeners; - $write =& $this->writeListeners; - $this->streamCallback = function ($stream, $flags) use (&$read, &$write) { + $this->streamCallback = function ($stream, $flags) { $key = (int) $stream; - if (Event::READ === (Event::READ & $flags) && isset($read[$key])) { - \call_user_func($read[$key], $stream); + if (Event::READ === (Event::READ & $flags) && isset($this->readListeners[$key])) { + \call_user_func($this->readListeners[$key], $stream); } - if (Event::WRITE === (Event::WRITE & $flags) && isset($write[$key])) { - \call_user_func($write[$key], $stream); + if (Event::WRITE === (Event::WRITE & $flags) && isset($this->writeListeners[$key])) { + \call_user_func($this->writeListeners[$key], $stream); } }; } diff --git a/src/ExtUvLoop.php b/src/ExtUvLoop.php index fc4cb3ab..e9e79524 100644 --- a/src/ExtUvLoop.php +++ b/src/ExtUvLoop.php @@ -22,12 +22,12 @@ final class ExtUvLoop implements LoopInterface private $uv; private $futureTickQueue; private $timers; - private $streamEvents = array(); - private $readStreams = array(); - private $writeStreams = array(); + private $streamEvents = []; + private $readStreams = []; + private $writeStreams = []; private $running; private $signals; - private $signalEvents = array(); + private $signalEvents = []; private $streamListener; public function __construct() @@ -114,13 +114,11 @@ public function addTimer($interval, $callback) { $timer = new Timer($interval, $callback, false); - $that = $this; - $timers = $this->timers; - $callback = function () use ($timer, $timers, $that) { + $callback = function () use ($timer) { \call_user_func($timer->getCallback(), $timer); - if ($timers->contains($timer)) { - $that->cancelTimer($timer); + if ($this->timers->contains($timer)) { + $this->cancelTimer($timer); } }; @@ -184,10 +182,9 @@ 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); + \uv_signal_start($this->signalEvents[$signal], function () use ($signal) { + $this->signals->call($signal); }, $signal); } } diff --git a/src/Loop.php b/src/Loop.php index 10976eea..732c5d5e 100644 --- a/src/Loop.php +++ b/src/Loop.php @@ -43,15 +43,14 @@ public static function get() $hasRun = true; }); - $stopped =& self::$stopped; - register_shutdown_function(function () use ($loop, &$hasRun, &$stopped) { + register_shutdown_function(function () use ($loop, &$hasRun) { // 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)) { + if (($error['type'] ?? 0) & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR)) { return; } - if (!$hasRun && !$stopped) { + if (!$hasRun && !self::$stopped) { $loop->run(); } }); @@ -83,11 +82,7 @@ public static function set(LoopInterface $loop) */ public static function addReadStream($stream, $listener) { - // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) - if (self::$instance === null) { - self::get(); - } - self::$instance->addReadStream($stream, $listener); + (self::$instance ?? self::get())->addReadStream($stream, $listener); } /** @@ -101,11 +96,7 @@ public static function addReadStream($stream, $listener) */ public static function addWriteStream($stream, $listener) { - // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) - if (self::$instance === null) { - self::get(); - } - self::$instance->addWriteStream($stream, $listener); + (self::$instance ?? self::get())->addWriteStream($stream, $listener); } /** @@ -146,11 +137,7 @@ public static function removeWriteStream($stream) */ public static function addTimer($interval, $callback) { - // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) - if (self::$instance === null) { - self::get(); - } - return self::$instance->addTimer($interval, $callback); + return (self::$instance ?? self::get())->addTimer($interval, $callback); } /** @@ -163,11 +150,7 @@ public static function addTimer($interval, $callback) */ public static function addPeriodicTimer($interval, $callback) { - // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) - if (self::$instance === null) { - self::get(); - } - return self::$instance->addPeriodicTimer($interval, $callback); + return (self::$instance ?? self::get())->addPeriodicTimer($interval, $callback); } /** @@ -193,12 +176,7 @@ public static function cancelTimer(TimerInterface $timer) */ public static function futureTick($listener) { - // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) - if (self::$instance === null) { - self::get(); - } - - self::$instance->futureTick($listener); + (self::$instance ?? self::get())->futureTick($listener); } /** @@ -211,12 +189,7 @@ public static function futureTick($listener) */ public static function addSignal($signal, $listener) { - // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) - if (self::$instance === null) { - self::get(); - } - - self::$instance->addSignal($signal, $listener); + (self::$instance ?? self::get())->addSignal($signal, $listener); } /** @@ -242,12 +215,7 @@ public static function removeSignal($signal, $listener) */ public static function run() { - // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) - if (self::$instance === null) { - self::get(); - } - - self::$instance->run(); + (self::$instance ?? self::get())->run(); } /** diff --git a/src/SignalsHandler.php b/src/SignalsHandler.php index 10d125df..e9b245ea 100644 --- a/src/SignalsHandler.php +++ b/src/SignalsHandler.php @@ -7,12 +7,12 @@ */ final class SignalsHandler { - private $signals = array(); + private $signals = []; public function add($signal, $listener) { if (!isset($this->signals[$signal])) { - $this->signals[$signal] = array(); + $this->signals[$signal] = []; } if (\in_array($listener, $this->signals[$signal])) { diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 4ff5cca8..41dd2cb3 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -56,10 +56,10 @@ final class StreamSelectLoop implements LoopInterface private $futureTickQueue; private $timers; - private $readStreams = array(); - private $readListeners = array(); - private $writeStreams = array(); - private $writeListeners = array(); + private $readStreams = []; + private $readListeners = []; + private $writeStreams = []; + private $writeListeners = []; private $running; private $pcntl = false; private $pcntlPoll = false; @@ -157,7 +157,7 @@ public function addSignal($signal, $listener) $this->signals->add($signal, $listener); if ($first) { - \pcntl_signal($signal, array($this->signals, 'call')); + \pcntl_signal($signal, [$this->signals, 'call']); } } @@ -278,7 +278,7 @@ private function streamSelect(array &$read, array &$write, $timeout) // @link https://docs.microsoft.com/de-de/windows/win32/api/winsock2/nf-winsock2-select $except = null; if (\DIRECTORY_SEPARATOR === '\\') { - $except = array(); + $except = []; foreach ($write as $key => $socket) { if (!isset($read[$key]) && @\ftell($socket) === 0) { $except[$key] = $socket; diff --git a/src/Timer/Timers.php b/src/Timer/Timers.php index 53c46d03..c9ae5ed8 100644 --- a/src/Timer/Timers.php +++ b/src/Timer/Timers.php @@ -15,8 +15,8 @@ final class Timers { private $time; - private $timers = array(); - private $schedule = array(); + private $timers = []; + private $schedule = []; private $sorted = true; private $useHighResolution; diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index eb9591ba..e89a8371 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -50,16 +50,15 @@ public function testAddReadStreamTriggersWhenSocketReceivesData() { list ($input, $output) = $this->createSocketPair(); - $loop = $this->loop; - $timeout = $loop->addTimer(0.1, function () use ($input, $loop) { - $loop->removeReadStream($input); + $timeout = $this->loop->addTimer(0.1, function () use ($input) { + $this->loop->removeReadStream($input); }); $called = 0; - $this->loop->addReadStream($input, function () use (&$called, $loop, $input, $timeout) { + $this->loop->addReadStream($input, function () use (&$called, $input, $timeout) { ++$called; - $loop->removeReadStream($input); - $loop->cancelTimer($timeout); + $this->loop->removeReadStream($input); + $this->loop->cancelTimer($timeout); }); fwrite($output, "foo\n"); @@ -73,16 +72,15 @@ public function testAddReadStreamTriggersWhenSocketCloses() { list ($input, $output) = $this->createSocketPair(); - $loop = $this->loop; - $timeout = $loop->addTimer(0.1, function () use ($input, $loop) { - $loop->removeReadStream($input); + $timeout = $this->loop->addTimer(0.1, function () use ($input) { + $this->loop->removeReadStream($input); }); $called = 0; - $this->loop->addReadStream($input, function () use (&$called, $loop, $input, $timeout) { + $this->loop->addReadStream($input, function () use (&$called, $input, $timeout) { ++$called; - $loop->removeReadStream($input); - $loop->cancelTimer($timeout); + $this->loop->removeReadStream($input); + $this->loop->cancelTimer($timeout); }); fclose($output); @@ -99,16 +97,15 @@ public function testAddWriteStreamTriggersWhenSocketConnectionSucceeds() $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); + $timeout = $this->loop->addTimer(0.1, function () use ($connecting) { + $this->loop->removeWriteStream($connecting); }); $called = 0; - $this->loop->addWriteStream($connecting, function () use (&$called, $loop, $connecting, $timeout) { + $this->loop->addWriteStream($connecting, function () use (&$called, $connecting, $timeout) { ++$called; - $loop->removeWriteStream($connecting); - $loop->cancelTimer($timeout); + $this->loop->removeWriteStream($connecting); + $this->loop->cancelTimer($timeout); }); $this->loop->run(); @@ -128,16 +125,15 @@ public function testAddWriteStreamTriggersWhenSocketConnectionRefused() $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); + $timeout = $this->loop->addTimer(10.0, function () use ($connecting) { + $this->loop->removeWriteStream($connecting); }); $called = 0; - $this->loop->addWriteStream($connecting, function () use (&$called, $loop, $connecting, $timeout) { + $this->loop->addWriteStream($connecting, function () use (&$called, $connecting, $timeout) { ++$called; - $loop->removeWriteStream($connecting); - $loop->cancelTimer($timeout); + $this->loop->removeWriteStream($connecting); + $this->loop->cancelTimer($timeout); }); $this->loop->run(); @@ -201,16 +197,14 @@ private function subAddReadStreamReceivesDataFromStreamReference() fwrite($input, 'hello'); fclose($input); - $loop = $this->loop; - $received =& $this->received; - $loop->addReadStream($output, function ($output) use ($loop, &$received) { + $this->loop->addReadStream($output, function ($output) { $chunk = fread($output, 1024); if ($chunk === '') { - $received .= 'X'; - $loop->removeReadStream($output); + $this->received .= 'X'; + $this->loop->removeReadStream($output); fclose($output); } else { - $received .= '[' . $chunk . ']'; + $this->received .= '[' . $chunk . ']'; } }); } @@ -347,10 +341,9 @@ public function testRemoveReadAndWriteStreamFromLoopOnceResourceClosesEndsLoop() $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); + $this->loop->addReadStream($stream, function ($stream) { + $this->loop->removeReadStream($stream); + $this->loop->removeWriteStream($stream); fclose($stream); }); @@ -370,15 +363,14 @@ public function testRemoveReadAndWriteStreamFromLoopOnceResourceClosesOnEndOfFil $this->loop->addWriteStream($stream, function () { }); // remove stream when the stream is readable (closes) - $loop = $this->loop; - $loop->addReadStream($stream, function ($stream) use ($loop) { + $this->loop->addReadStream($stream, function ($stream) { $data = fread($stream, 1024); if ($data !== '') { return; } - $loop->removeReadStream($stream); - $loop->removeWriteStream($stream); + $this->loop->removeReadStream($stream); + $this->loop->removeWriteStream($stream); fclose($stream); }); @@ -401,10 +393,9 @@ public function testRemoveReadAndWriteStreamFromLoopWithClosingResourceEndsLoop( $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); + $this->loop->addReadStream($stream, function ($stream) { + $this->loop->removeReadStream($stream); + $this->loop->removeWriteStream($stream); fclose($stream); }); @@ -433,9 +424,8 @@ public function runShouldReturnWhenNoMoreFds() { list ($input, $output) = $this->createSocketPair(); - $loop = $this->loop; - $this->loop->addReadStream($input, function ($stream) use ($loop) { - $loop->removeReadStream($stream); + $this->loop->addReadStream($input, function ($stream) { + $this->loop->removeReadStream($stream); }); fwrite($output, "foo\n"); @@ -448,9 +438,8 @@ public function stopShouldStopRunningLoop() { list ($input, $output) = $this->createSocketPair(); - $loop = $this->loop; - $this->loop->addReadStream($input, function ($stream) use ($loop) { - $loop->stop(); + $this->loop->addReadStream($input, function ($stream) { + $this->loop->stop(); }); fwrite($output, "foo\n"); @@ -460,18 +449,16 @@ public function stopShouldStopRunningLoop() public function testStopShouldPreventRunFromBlocking() { - $that = $this; $this->loop->addTimer( 1, - function () use ($that) { - $that->fail('Timer was executed.'); + function () { + $this->fail('Timer was executed.'); } ); - $loop = $this->loop; $this->loop->futureTick( - function () use ($loop) { - $loop->stop(); + function () { + $this->loop->stop(); } ); @@ -485,38 +472,34 @@ public function testIgnoreRemovedCallback() list ($input2, $output2) = $this->createSocketPair(); $called = false; - - $loop = $this->loop; - $loop->addReadStream($input1, function ($stream) use (& $called, $loop, $input2) { + $this->loop->addReadStream($input1, function ($stream) use (&$called, $input2) { // stream1 is readable, remove stream2 as well => this will invalidate its callback - $loop->removeReadStream($stream); - $loop->removeReadStream($input2); + $this->loop->removeReadStream($stream); + $this->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) { + $this->loop->addReadStream($input2, function () use (&$called) { if ($called) { - $that->fail('Callback 2 must not be called after callback 1 was 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->loop->run(); $this->assertTrue($called); } public function testFutureTickEventGeneratedByFutureTick() { - $loop = $this->loop; $this->loop->futureTick( - function () use ($loop) { - $loop->futureTick( + function () { + $this->loop->futureTick( function () { echo 'future-tick' . PHP_EOL; } @@ -579,19 +562,18 @@ public function testRecursiveFutureTick() { list ($stream) = $this->createSocketPair(); - $loop = $this->loop; $this->loop->addWriteStream( $stream, - function () use ($stream, $loop) { + function () use ($stream) { echo 'stream' . PHP_EOL; - $loop->removeWriteStream($stream); + $this->loop->removeWriteStream($stream); } ); $this->loop->futureTick( - function () use ($loop) { + function () { echo 'future-tick-1' . PHP_EOL; - $loop->futureTick( + $this->loop->futureTick( function () { echo 'future-tick-2' . PHP_EOL; } @@ -608,12 +590,11 @@ public function testRunWaitsForFutureTickEvents() { list ($stream) = $this->createSocketPair(); - $loop = $this->loop; $this->loop->addWriteStream( $stream, - function () use ($stream, $loop) { - $loop->removeWriteStream($stream); - $loop->futureTick( + function () use ($stream) { + $this->loop->removeWriteStream($stream); + $this->loop->futureTick( function () { echo 'future-tick' . PHP_EOL; } @@ -628,11 +609,10 @@ function () { public function testFutureTickEventGeneratedByTimer() { - $loop = $this->loop; $this->loop->addTimer( 0.001, - function () use ($loop) { - $loop->futureTick( + function () { + $this->loop->futureTick( function () { echo 'future-tick' . PHP_EOL; } @@ -671,12 +651,11 @@ public function testSignal() $calledShouldNot = false; }); - $loop = $this->loop; - $this->loop->addSignal(SIGUSR1, $func1 = function () use (&$func1, &$func2, &$called, $timer, $loop) { + $this->loop->addSignal(SIGUSR1, $func1 = function () use (&$func1, &$func2, &$called, $timer) { $called = true; - $loop->removeSignal(SIGUSR1, $func1); - $loop->removeSignal(SIGUSR2, $func2); - $loop->cancelTimer($timer); + $this->loop->removeSignal(SIGUSR1, $func1); + $this->loop->removeSignal(SIGUSR2, $func2); + $this->loop->cancelTimer($timer); }); $this->loop->futureTick(function () { @@ -710,9 +689,8 @@ public function testSignalMultipleUsagesForTheSameListener() $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->addTimer(0.9, function () use (&$func) { + $this->loop->removeSignal(SIGUSR1, $func); }); $this->loop->run(); @@ -729,12 +707,11 @@ public function testSignalsKeepTheLoopRunning() $this->markTestSkipped('Signal handling with StreamSelectLoop requires pcntl_signal() and pcntl_signal_dispatch(), see also disable_functions'); } - $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->loop->addTimer(1.5, function () use ($function) { + $this->loop->removeSignal(SIGUSR1, $function); + $this->loop->stop(); }); $this->assertRunSlowerThan(1.4); @@ -749,11 +726,10 @@ public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop() $this->markTestSkipped('Signal handling with StreamSelectLoop requires pcntl_signal() and pcntl_signal_dispatch(), see also disable_functions'); } - $loop = $this->loop; $function = function () {}; $this->loop->addSignal(SIGUSR1, $function); - $this->loop->addTimer(1.5, function () use ($function, $loop) { - $loop->removeSignal(SIGUSR1, $function); + $this->loop->addTimer(1.5, function () use ($function) { + $this->loop->removeSignal(SIGUSR1, $function); }); $this->assertRunFasterThan(1.6); @@ -763,12 +739,11 @@ 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->loop->futureTick(function () use ($timer) { + $this->loop->cancelTimer($timer); }); $this->assertRunFasterThan($this->tickTimeout); diff --git a/tests/ExtUvLoopTest.php b/tests/ExtUvLoopTest.php index 267eddf1..da07d767 100644 --- a/tests/ExtUvLoopTest.php +++ b/tests/ExtUvLoopTest.php @@ -45,51 +45,51 @@ public function intervalProvider() $tenMillionsIntMax = PHP_INT_MAX + 10000000; $tenThousandsTimesIntMax = PHP_INT_MAX * 1000; - return array( - array( + return [ + [ $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.", - ), - ); + ], + ]; } } diff --git a/tests/LoopTest.php b/tests/LoopTest.php index 80f5cccc..1a56404b 100644 --- a/tests/LoopTest.php +++ b/tests/LoopTest.php @@ -21,7 +21,7 @@ public function testCallingLoopGetShouldAlwaysReturnTheSameEventLoop() */ public function numberOfTests() { - return array(array(), array(), array()); + return [[], [], []]; } public function testStaticAddReadStreamCallsAddReadStreamOnLoopInstance() diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index 578bbe4b..e402266e 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -47,10 +47,9 @@ public function testStreamSelectReportsWarningForStreamWithFilter() $this->loop->addReadStream($stream, $this->expectCallableNever()); - $loop = $this->loop; - $this->loop->futureTick(function () use ($loop, $stream) { - $loop->futureTick(function () use ($loop, $stream) { - $loop->removeReadStream($stream); + $this->loop->futureTick(function () use ($stream) { + $this->loop->futureTick(function () use ($stream) { + $this->loop->removeReadStream($stream); }); }); @@ -81,10 +80,9 @@ public function testStreamSelectThrowsWhenCustomErrorHandlerThrowsForStreamWithF $this->loop->addReadStream($stream, $this->expectCallableNever()); - $loop = $this->loop; - $this->loop->futureTick(function () use ($loop, $stream) { - $loop->futureTick(function () use ($loop, $stream) { - $loop->removeReadStream($stream); + $this->loop->futureTick(function () use ($stream) { + $this->loop->futureTick(function () use ($stream) { + $this->loop->removeReadStream($stream); }); }); @@ -113,11 +111,11 @@ public function testStreamSelectThrowsWhenCustomErrorHandlerThrowsForStreamWithF public function signalProvider() { - return array( - array('SIGUSR1'), - array('SIGHUP'), - array('SIGTERM'), - ); + return [ + ['SIGUSR1'], + ['SIGHUP'], + ['SIGTERM'], + ]; } /** @@ -133,9 +131,8 @@ public function testSignalInterruptNoStream($signal) $check = $this->loop->addPeriodicTimer(0.01, function() { pcntl_signal_dispatch(); }); - $loop = $this->loop; - $loop->addTimer(0.1, function () use ($check, $loop) { - $loop->cancelTimer($check); + $this->loop->addTimer(0.1, function () use ($check) { + $this->loop->cancelTimer($check); }); $handled = false; @@ -165,13 +162,12 @@ public function testSignalInterruptWithStream($signal) }); // add stream to the loop - $loop = $this->loop; list($writeStream, $readStream) = $this->createSocketPair(); - $loop->addReadStream($readStream, function ($stream) use ($loop) { + $this->loop->addReadStream($readStream, function ($stream) { /** @var $loop LoopInterface */ $read = fgets($stream); if ($read === "end loop\n") { - $loop->stop(); + $this->loop->stop(); } }); $this->loop->addTimer(0.1, function() use ($writeStream) { diff --git a/tests/TestCase.php b/tests/TestCase.php index 69b3b227..8b998e0b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -41,10 +41,10 @@ protected function createCallableMock() { if (method_exists('PHPUnit\Framework\MockObject\MockBuilder', 'addMethods')) { // PHPUnit 9+ - return $this->getMockBuilder('stdClass')->addMethods(array('__invoke'))->getMock(); + return $this->getMockBuilder('stdClass')->addMethods(['__invoke'])->getMock(); } else { - // legacy PHPUnit 4 - PHPUnit 9 - return $this->getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock(); + // legacy PHPUnit + return $this->getMockBuilder('stdClass')->setMethods(['__invoke'])->getMock(); } } From cde40177b179a6741bf4fcb8371776652e77d78e Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 24 Feb 2024 16:10:58 +0100 Subject: [PATCH 84/85] Update test suite and remove legacy PHPUnit workarounds --- composer.json | 2 +- phpunit.xml.legacy | 2 +- tests/ExtUvLoopTest.php | 88 +++++++++++++++---------------- tests/LoopTest.php | 78 ++++++++++++++------------- tests/StreamSelectLoopTest.php | 10 ++-- tests/TestCase.php | 7 +-- tests/Timer/AbstractTimerTest.php | 5 +- 7 files changed, 96 insertions(+), 96 deletions(-) diff --git a/composer.json b/composer.json index 522bd8ca..6d31e81d 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7" + "phpunit/phpunit": "^9.6 || ^7.5" }, "suggest": { "ext-pcntl": "For signal handling support when using the StreamSelectLoop" diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy index 37671595..7c148001 100644 --- a/phpunit.xml.legacy +++ b/phpunit.xml.legacy @@ -2,7 +2,7 @@ diff --git a/tests/ExtUvLoopTest.php b/tests/ExtUvLoopTest.php index da07d767..45b251ef 100644 --- a/tests/ExtUvLoopTest.php +++ b/tests/ExtUvLoopTest.php @@ -45,51 +45,49 @@ public function intervalProvider() $tenMillionsIntMax = PHP_INT_MAX + 10000000; $tenThousandsTimesIntMax = PHP_INT_MAX * 1000; - return [ - [ - $oversizeInterval, - "Interval overflow, value must be lower than '{$maxValue}', but '{$oversizeInterval}' passed." - ], - [ - $oneMaxValue, - "Interval overflow, value must be lower than '{$maxValue}', but '{$oneMaxValue}' passed.", - ], - [ - $tenMaxValue, - "Interval overflow, value must be lower than '{$maxValue}', but '{$tenMaxValue}' passed.", - ], - [ - $tenMillionsMaxValue, - "Interval overflow, value must be lower than '{$maxValue}', but '{$tenMillionsMaxValue}' passed.", - ], - [ - $intMax, - "Interval overflow, value must be lower than '{$maxValue}', but '{$intMax}' passed.", - ], - [ - $oneIntMax, - "Interval overflow, value must be lower than '{$maxValue}', but '{$oneIntMax}' passed.", - ], - [ - $tenIntMax, - "Interval overflow, value must be lower than '{$maxValue}', but '{$tenIntMax}' passed.", - ], - [ - $oneHundredIntMax, - "Interval overflow, value must be lower than '{$maxValue}', but '{$oneHundredIntMax}' passed.", - ], - [ - $oneThousandIntMax, - "Interval overflow, value must be lower than '{$maxValue}', but '{$oneThousandIntMax}' passed.", - ], - [ - $tenMillionsIntMax, - "Interval overflow, value must be lower than '{$maxValue}', but '{$tenMillionsIntMax}' passed.", - ], - [ - $tenThousandsTimesIntMax, - "Interval overflow, value must be lower than '{$maxValue}', but '{$tenThousandsTimesIntMax}' passed.", - ], + yield [ + $oversizeInterval, + "Interval overflow, value must be lower than '{$maxValue}', but '{$oversizeInterval}' passed." + ]; + yield [ + $oneMaxValue, + "Interval overflow, value must be lower than '{$maxValue}', but '{$oneMaxValue}' passed.", + ]; + yield [ + $tenMaxValue, + "Interval overflow, value must be lower than '{$maxValue}', but '{$tenMaxValue}' passed.", + ]; + yield [ + $tenMillionsMaxValue, + "Interval overflow, value must be lower than '{$maxValue}', but '{$tenMillionsMaxValue}' passed.", + ]; + yield [ + $intMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$intMax}' passed.", + ]; + yield [ + $oneIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$oneIntMax}' passed.", + ]; + yield [ + $tenIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$tenIntMax}' passed.", + ]; + yield [ + $oneHundredIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$oneHundredIntMax}' passed.", + ]; + yield [ + $oneThousandIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$oneThousandIntMax}' passed.", + ]; + yield [ + $tenMillionsIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$tenMillionsIntMax}' passed.", + ]; + yield [ + $tenThousandsTimesIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$tenThousandsTimesIntMax}' passed.", ]; } } diff --git a/tests/LoopTest.php b/tests/LoopTest.php index 1a56404b..08e2107e 100644 --- a/tests/LoopTest.php +++ b/tests/LoopTest.php @@ -3,6 +3,8 @@ namespace React\Tests\EventLoop; use React\EventLoop\Loop; +use React\EventLoop\LoopInterface; +use React\EventLoop\TimerInterface; final class LoopTest extends TestCase { @@ -29,7 +31,7 @@ public function testStaticAddReadStreamCallsAddReadStreamOnLoopInstance() $stream = tmpfile(); $listener = function () { }; - $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addReadStream')->with($stream, $listener); Loop::set($loop); @@ -39,7 +41,7 @@ public function testStaticAddReadStreamCallsAddReadStreamOnLoopInstance() public function testStaticAddReadStreamWithNoDefaultLoopCallsAddReadStreamOnNewLoopInstance() { - $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref = new \ReflectionProperty(Loop::class, 'instance'); $ref->setAccessible(true); $ref->setValue(null, null); @@ -47,7 +49,7 @@ public function testStaticAddReadStreamWithNoDefaultLoopCallsAddReadStreamOnNewL $listener = function () { }; Loop::addReadStream($stream, $listener); - $this->assertInstanceOf('React\EventLoop\LoopInterface', $ref->getValue()); + $this->assertInstanceOf(LoopInterface::class, $ref->getValue()); } public function testStaticAddWriteStreamCallsAddWriteStreamOnLoopInstance() @@ -55,7 +57,7 @@ public function testStaticAddWriteStreamCallsAddWriteStreamOnLoopInstance() $stream = tmpfile(); $listener = function () { }; - $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addWriteStream')->with($stream, $listener); Loop::set($loop); @@ -65,7 +67,7 @@ public function testStaticAddWriteStreamCallsAddWriteStreamOnLoopInstance() public function testStaticAddWriteStreamWithNoDefaultLoopCallsAddWriteStreamOnNewLoopInstance() { - $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref = new \ReflectionProperty(Loop::class, 'instance'); $ref->setAccessible(true); $ref->setValue(null, null); @@ -73,14 +75,14 @@ public function testStaticAddWriteStreamWithNoDefaultLoopCallsAddWriteStreamOnNe $listener = function () { }; Loop::addWriteStream($stream, $listener); - $this->assertInstanceOf('React\EventLoop\LoopInterface', $ref->getValue()); + $this->assertInstanceOf(LoopInterface::class, $ref->getValue()); } public function testStaticRemoveReadStreamCallsRemoveReadStreamOnLoopInstance() { $stream = tmpfile(); - $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('removeReadStream')->with($stream); Loop::set($loop); @@ -90,7 +92,7 @@ public function testStaticRemoveReadStreamCallsRemoveReadStreamOnLoopInstance() public function testStaticRemoveReadStreamWithNoDefaultLoopIsNoOp() { - $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref = new \ReflectionProperty(Loop::class, 'instance'); $ref->setAccessible(true); $ref->setValue(null, null); @@ -104,7 +106,7 @@ public function testStaticRemoveWriteStreamCallsRemoveWriteStreamOnLoopInstance( { $stream = tmpfile(); - $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('removeWriteStream')->with($stream); Loop::set($loop); @@ -114,7 +116,7 @@ public function testStaticRemoveWriteStreamCallsRemoveWriteStreamOnLoopInstance( public function testStaticRemoveWriteStreamWithNoDefaultLoopIsNoOp() { - $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref = new \ReflectionProperty(Loop::class, 'instance'); $ref->setAccessible(true); $ref->setValue(null, null); @@ -128,9 +130,9 @@ public function testStaticAddTimerCallsAddTimerOnLoopInstanceAndReturnsTimerInst { $interval = 1.0; $callback = function () { }; - $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock(); + $timer = $this->createMock(TimerInterface::class); - $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addTimer')->with($interval, $callback)->willReturn($timer); Loop::set($loop); @@ -142,7 +144,7 @@ public function testStaticAddTimerCallsAddTimerOnLoopInstanceAndReturnsTimerInst public function testStaticAddTimerWithNoDefaultLoopCallsAddTimerOnNewLoopInstance() { - $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref = new \ReflectionProperty(Loop::class, 'instance'); $ref->setAccessible(true); $ref->setValue(null, null); @@ -150,17 +152,17 @@ public function testStaticAddTimerWithNoDefaultLoopCallsAddTimerOnNewLoopInstanc $callback = function () { }; $ret = Loop::addTimer($interval, $callback); - $this->assertInstanceOf('React\EventLoop\TimerInterface', $ret); - $this->assertInstanceOf('React\EventLoop\LoopInterface', $ref->getValue()); + $this->assertInstanceOf(TimerInterface::class, $ret); + $this->assertInstanceOf(LoopInterface::class, $ref->getValue()); } public function testStaticAddPeriodicTimerCallsAddPeriodicTimerOnLoopInstanceAndReturnsTimerInstance() { $interval = 1.0; $callback = function () { }; - $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock(); + $timer = $this->createMock(TimerInterface::class); - $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addPeriodicTimer')->with($interval, $callback)->willReturn($timer); Loop::set($loop); @@ -172,7 +174,7 @@ public function testStaticAddPeriodicTimerCallsAddPeriodicTimerOnLoopInstanceAnd public function testStaticAddPeriodicTimerWithNoDefaultLoopCallsAddPeriodicTimerOnNewLoopInstance() { - $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref = new \ReflectionProperty(Loop::class, 'instance'); $ref->setAccessible(true); $ref->setValue(null, null); @@ -180,16 +182,16 @@ public function testStaticAddPeriodicTimerWithNoDefaultLoopCallsAddPeriodicTimer $callback = function () { }; $ret = Loop::addPeriodicTimer($interval, $callback); - $this->assertInstanceOf('React\EventLoop\TimerInterface', $ret); - $this->assertInstanceOf('React\EventLoop\LoopInterface', $ref->getValue()); + $this->assertInstanceOf(TimerInterface::class, $ret); + $this->assertInstanceOf(LoopInterface::class, $ref->getValue()); } public function testStaticCancelTimerCallsCancelTimerOnLoopInstance() { - $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock(); + $timer = $this->createMock(TimerInterface::class); - $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('cancelTimer')->with($timer); Loop::set($loop); @@ -199,11 +201,11 @@ public function testStaticCancelTimerCallsCancelTimerOnLoopInstance() public function testStaticCancelTimerWithNoDefaultLoopIsNoOp() { - $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref = new \ReflectionProperty(Loop::class, 'instance'); $ref->setAccessible(true); $ref->setValue(null, null); - $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock(); + $timer = $this->createMock(TimerInterface::class); Loop::cancelTimer($timer); $this->assertNull($ref->getValue()); @@ -213,7 +215,7 @@ public function testStaticFutureTickCallsFutureTickOnLoopInstance() { $listener = function () { }; - $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('futureTick')->with($listener); Loop::set($loop); @@ -223,14 +225,14 @@ public function testStaticFutureTickCallsFutureTickOnLoopInstance() public function testStaticFutureTickWithNoDefaultLoopCallsFutureTickOnNewLoopInstance() { - $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref = new \ReflectionProperty(Loop::class, 'instance'); $ref->setAccessible(true); $ref->setValue(null, null); $listener = function () { }; Loop::futureTick($listener); - $this->assertInstanceOf('React\EventLoop\LoopInterface', $ref->getValue()); + $this->assertInstanceOf(LoopInterface::class, $ref->getValue()); } public function testStaticAddSignalCallsAddSignalOnLoopInstance() @@ -238,7 +240,7 @@ public function testStaticAddSignalCallsAddSignalOnLoopInstance() $signal = 1; $listener = function () { }; - $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('addSignal')->with($signal, $listener); Loop::set($loop); @@ -252,7 +254,7 @@ public function testStaticAddSignalWithNoDefaultLoopCallsAddSignalOnNewLoopInsta $this->markTestSkipped('Not supported on Windows'); } - $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref = new \ReflectionProperty(Loop::class, 'instance'); $ref->setAccessible(true); $ref->setValue(null, null); @@ -264,7 +266,7 @@ public function testStaticAddSignalWithNoDefaultLoopCallsAddSignalOnNewLoopInsta $this->markTestSkipped('Skipped: ' . $e->getMessage()); } - $this->assertInstanceOf('React\EventLoop\LoopInterface', $ref->getValue()); + $this->assertInstanceOf(LoopInterface::class, $ref->getValue()); } public function testStaticRemoveSignalCallsRemoveSignalOnLoopInstance() @@ -272,7 +274,7 @@ public function testStaticRemoveSignalCallsRemoveSignalOnLoopInstance() $signal = 1; $listener = function () { }; - $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('removeSignal')->with($signal, $listener); Loop::set($loop); @@ -282,7 +284,7 @@ public function testStaticRemoveSignalCallsRemoveSignalOnLoopInstance() public function testStaticRemoveSignalWithNoDefaultLoopIsNoOp() { - $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref = new \ReflectionProperty(Loop::class, 'instance'); $ref->setAccessible(true); $ref->setValue(null, null); @@ -295,7 +297,7 @@ public function testStaticRemoveSignalWithNoDefaultLoopIsNoOp() public function testStaticRunCallsRunOnLoopInstance() { - $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('run')->with(); Loop::set($loop); @@ -305,18 +307,18 @@ public function testStaticRunCallsRunOnLoopInstance() public function testStaticRunWithNoDefaultLoopCallsRunsOnNewLoopInstance() { - $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref = new \ReflectionProperty(Loop::class, 'instance'); $ref->setAccessible(true); $ref->setValue(null, null); Loop::run(); - $this->assertInstanceOf('React\EventLoop\LoopInterface', $ref->getValue()); + $this->assertInstanceOf(LoopInterface::class, $ref->getValue()); } public function testStaticStopCallsStopOnLoopInstance() { - $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop = $this->createMock(LoopInterface::class); $loop->expects($this->once())->method('stop')->with(); Loop::set($loop); @@ -326,7 +328,7 @@ public function testStaticStopCallsStopOnLoopInstance() public function testStaticStopCallWithNoDefaultLoopIsNoOp() { - $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref = new \ReflectionProperty(Loop::class, 'instance'); $ref->setAccessible(true); $ref->setValue(null, null); @@ -341,7 +343,7 @@ public function testStaticStopCallWithNoDefaultLoopIsNoOp() */ public function unsetLoopFromLoopAccessor() { - $ref = new \ReflectionProperty('React\EventLoop\Loop', 'instance'); + $ref = new \ReflectionProperty(Loop::class, 'instance'); $ref->setAccessible(true); $ref->setValue(null, null); } diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index e402266e..b2672d4e 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -102,7 +102,7 @@ public function testStreamSelectThrowsWhenCustomErrorHandlerThrowsForStreamWithF $e = $e->getPrevious(); } - $this->assertInstanceOf('RuntimeException', $e); + $this->assertInstanceOf(\RuntimeException::class, $e); $now = set_error_handler(function () { }); restore_error_handler(); @@ -111,11 +111,9 @@ public function testStreamSelectThrowsWhenCustomErrorHandlerThrowsForStreamWithF public function signalProvider() { - return [ - ['SIGUSR1'], - ['SIGHUP'], - ['SIGTERM'], - ]; + yield ['SIGUSR1']; + yield ['SIGHUP']; + yield ['SIGTERM']; } /** diff --git a/tests/TestCase.php b/tests/TestCase.php index 8b998e0b..55f04cf3 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -39,12 +39,13 @@ protected function expectCallableNever() protected function createCallableMock() { - if (method_exists('PHPUnit\Framework\MockObject\MockBuilder', 'addMethods')) { + $builder = $this->getMockBuilder(\stdClass::class); + if (method_exists($builder, 'addMethods')) { // PHPUnit 9+ - return $this->getMockBuilder('stdClass')->addMethods(['__invoke'])->getMock(); + return $builder->addMethods(['__invoke'])->getMock(); } else { // legacy PHPUnit - return $this->getMockBuilder('stdClass')->setMethods(['__invoke'])->getMock(); + return $builder->setMethods(['__invoke'])->getMock(); } } diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index bbea46f8..81099ff9 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -3,6 +3,7 @@ namespace React\Tests\EventLoop\Timer; use React\EventLoop\LoopInterface; +use React\EventLoop\TimerInterface; use React\Tests\EventLoop\TestCase; abstract class AbstractTimerTest extends TestCase @@ -18,7 +19,7 @@ public function testAddTimerReturnsNonPeriodicTimerInstance() $timer = $loop->addTimer(0.001, $this->expectCallableNever()); - $this->assertInstanceOf('React\EventLoop\TimerInterface', $timer); + $this->assertInstanceOf(TimerInterface::class, $timer); $this->assertFalse($timer->isPeriodic()); } @@ -45,7 +46,7 @@ public function testAddPeriodicTimerReturnsPeriodicTimerInstance() $periodic = $loop->addPeriodicTimer(0.1, $this->expectCallableNever()); - $this->assertInstanceOf('React\EventLoop\TimerInterface', $periodic); + $this->assertInstanceOf(TimerInterface::class, $periodic); $this->assertTrue($periodic->isPeriodic()); } From 0b2a3c3caaf8f669821ede8d5a186def525c997e Mon Sep 17 00:00:00 2001 From: Paul Rotmann Date: Mon, 24 Mar 2025 17:42:18 +0100 Subject: [PATCH 85/85] Run tests on PHP 8.4 and update test environment --- .github/workflows/ci.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e69fa12..871d3f4e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,10 +7,11 @@ on: jobs: PHPUnit: name: PHPUnit (PHP ${{ matrix.php }}) - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: php: + - 8.4 - 8.3 - 8.2 - 8.1 @@ -24,10 +25,10 @@ jobs: - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - coverage: xdebug + coverage: ${{ matrix.php < 8.0 && 'xdebug' || 'pcov' }} ini-file: development ini-values: disable_functions='' # do not disable PCNTL functions on PHP < 8.1 - extensions: sockets, pcntl, event, ev + extensions: sockets, pcntl, event, ${{ matrix.php < 8.0 && 'ev-1.1.5' || 'ev' }} env: fail-fast: true # fail step if any extension can not be installed - run: composer install @@ -38,7 +39,7 @@ jobs: PHPUnit-Unstable: name: PHPUnit (Unstable PHP ${{ matrix.php }}) - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 continue-on-error: true strategy: matrix: @@ -56,7 +57,7 @@ jobs: - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - coverage: xdebug + coverage: ${{ matrix.php < 8.0 && 'xdebug' || 'pcov' }} ini-file: development extensions: sockets, pcntl - name: Install ext-uv @@ -77,6 +78,7 @@ jobs: strategy: matrix: php: + - 8.4 - 8.3 - 8.2 - 8.1 @@ -90,7 +92,7 @@ jobs: - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - coverage: xdebug + coverage: ${{ matrix.php < 8.0 && 'xdebug' || 'pcov' }} ini-file: development extensions: sockets,event # future: add uv-beta (installs, but can not load) - run: composer install pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy