From 1a4c7ea6ff692432491d303b3ce005a51a3dcbfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 8 Jun 2015 00:15:29 +0200 Subject: [PATCH 001/203] =?UTF-8?q?Remove=20NextTickQueue=20=E2=80=93=20us?= =?UTF-8?q?e=20FutureTickQueue=20instead?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ExtEventLoop.php | 17 +---- src/LibEvLoop.php | 17 +---- src/LibEventLoop.php | 17 +---- src/LoopInterface.php | 10 --- src/StreamSelectLoop.php | 19 +----- src/Tick/NextTickQueue.php | 57 ---------------- tests/AbstractLoopTest.php | 136 ++----------------------------------- 7 files changed, 10 insertions(+), 263 deletions(-) delete mode 100644 src/Tick/NextTickQueue.php diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 48657f96..a39bc1b8 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -6,7 +6,6 @@ use EventBase; use EventConfig as EventBaseConfig; use React\EventLoop\Tick\FutureTickQueue; -use React\EventLoop\Tick\NextTickQueue; use React\EventLoop\Timer\Timer; use React\EventLoop\Timer\TimerInterface; use SplObjectStorage; @@ -17,7 +16,6 @@ class ExtEventLoop implements LoopInterface { private $eventBase; - private $nextTickQueue; private $futureTickQueue; private $timerCallback; private $timerEvents; @@ -31,7 +29,6 @@ class ExtEventLoop implements LoopInterface public function __construct(EventBaseConfig $config = null) { $this->eventBase = new EventBase($config); - $this->nextTickQueue = new NextTickQueue($this); $this->futureTickQueue = new FutureTickQueue($this); $this->timerEvents = new SplObjectStorage(); @@ -153,14 +150,6 @@ public function isTimerActive(TimerInterface $timer) return $this->timerEvents->contains($timer); } - /** - * {@inheritdoc} - */ - public function nextTick(callable $listener) - { - $this->nextTickQueue->add($listener); - } - /** * {@inheritdoc} */ @@ -174,8 +163,6 @@ public function futureTick(callable $listener) */ public function tick() { - $this->nextTickQueue->tick(); - $this->futureTickQueue->tick(); // @-suppression: https://github.com/reactphp/react/pull/234#discussion-diff-7759616R226 @@ -190,12 +177,10 @@ public function run() $this->running = true; while ($this->running) { - $this->nextTickQueue->tick(); - $this->futureTickQueue->tick(); $flags = EventBase::LOOP_ONCE; - if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) { + if (!$this->running || !$this->futureTickQueue->isEmpty()) { $flags |= EventBase::LOOP_NONBLOCK; } elseif (!$this->streamEvents && !$this->timerEvents->count()) { break; diff --git a/src/LibEvLoop.php b/src/LibEvLoop.php index 38e4ec2c..d1a7093a 100644 --- a/src/LibEvLoop.php +++ b/src/LibEvLoop.php @@ -6,7 +6,6 @@ use libev\IOEvent; use libev\TimerEvent; use React\EventLoop\Tick\FutureTickQueue; -use React\EventLoop\Tick\NextTickQueue; use React\EventLoop\Timer\Timer; use React\EventLoop\Timer\TimerInterface; use SplObjectStorage; @@ -18,7 +17,6 @@ class LibEvLoop implements LoopInterface { private $loop; - private $nextTickQueue; private $futureTickQueue; private $timerEvents; private $readEvents = []; @@ -28,7 +26,6 @@ class LibEvLoop implements LoopInterface public function __construct() { $this->loop = new EventLoop(); - $this->nextTickQueue = new NextTickQueue($this); $this->futureTickQueue = new FutureTickQueue($this); $this->timerEvents = new SplObjectStorage(); } @@ -157,14 +154,6 @@ public function isTimerActive(TimerInterface $timer) return $this->timerEvents->contains($timer); } - /** - * {@inheritdoc} - */ - public function nextTick(callable $listener) - { - $this->nextTickQueue->add($listener); - } - /** * {@inheritdoc} */ @@ -178,8 +167,6 @@ public function futureTick(callable $listener) */ public function tick() { - $this->nextTickQueue->tick(); - $this->futureTickQueue->tick(); $this->loop->run(EventLoop::RUN_ONCE | EventLoop::RUN_NOWAIT); @@ -193,12 +180,10 @@ public function run() $this->running = true; while ($this->running) { - $this->nextTickQueue->tick(); - $this->futureTickQueue->tick(); $flags = EventLoop::RUN_ONCE; - if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) { + if (!$this->running || !$this->futureTickQueue->isEmpty()) { $flags |= EventLoop::RUN_NOWAIT; } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count()) { break; diff --git a/src/LibEventLoop.php b/src/LibEventLoop.php index 6fbc8269..0ab2d3e7 100644 --- a/src/LibEventLoop.php +++ b/src/LibEventLoop.php @@ -5,7 +5,6 @@ use Event; use EventBase; use React\EventLoop\Tick\FutureTickQueue; -use React\EventLoop\Tick\NextTickQueue; use React\EventLoop\Timer\Timer; use React\EventLoop\Timer\TimerInterface; use SplObjectStorage; @@ -18,7 +17,6 @@ class LibEventLoop implements LoopInterface const MICROSECONDS_PER_SECOND = 1000000; private $eventBase; - private $nextTickQueue; private $futureTickQueue; private $timerCallback; private $timerEvents; @@ -32,7 +30,6 @@ class LibEventLoop implements LoopInterface public function __construct() { $this->eventBase = event_base_new(); - $this->nextTickQueue = new NextTickQueue($this); $this->futureTickQueue = new FutureTickQueue($this); $this->timerEvents = new SplObjectStorage(); @@ -161,14 +158,6 @@ public function isTimerActive(TimerInterface $timer) return $this->timerEvents->contains($timer); } - /** - * {@inheritdoc} - */ - public function nextTick(callable $listener) - { - $this->nextTickQueue->add($listener); - } - /** * {@inheritdoc} */ @@ -182,8 +171,6 @@ public function futureTick(callable $listener) */ public function tick() { - $this->nextTickQueue->tick(); - $this->futureTickQueue->tick(); event_base_loop($this->eventBase, EVLOOP_ONCE | EVLOOP_NONBLOCK); @@ -197,12 +184,10 @@ public function run() $this->running = true; while ($this->running) { - $this->nextTickQueue->tick(); - $this->futureTickQueue->tick(); $flags = EVLOOP_ONCE; - if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) { + if (!$this->running || !$this->futureTickQueue->isEmpty()) { $flags |= EVLOOP_NONBLOCK; } elseif (!$this->streamEvents && !$this->timerEvents->count()) { break; diff --git a/src/LoopInterface.php b/src/LoopInterface.php index d046526c..d15a376c 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -85,16 +85,6 @@ public function cancelTimer(TimerInterface $timer); */ public function isTimerActive(TimerInterface $timer); - /** - * Schedule a callback to be invoked on the next tick of the event loop. - * - * Callbacks are guaranteed to be executed in the order they are enqueued, - * before any timer or stream events. - * - * @param callable $listener The callback to invoke. - */ - public function nextTick(callable $listener); - /** * Schedule a callback to be invoked on a future tick of the event loop. * diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 7d455048..0d5af090 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -3,7 +3,6 @@ namespace React\EventLoop; use React\EventLoop\Tick\FutureTickQueue; -use React\EventLoop\Tick\NextTickQueue; use React\EventLoop\Timer\Timer; use React\EventLoop\Timer\TimerInterface; use React\EventLoop\Timer\Timers; @@ -15,7 +14,6 @@ class StreamSelectLoop implements LoopInterface { const MICROSECONDS_PER_SECOND = 1000000; - private $nextTickQueue; private $futureTickQueue; private $timers; private $readStreams = []; @@ -26,7 +24,6 @@ class StreamSelectLoop implements LoopInterface public function __construct() { - $this->nextTickQueue = new NextTickQueue($this); $this->futureTickQueue = new FutureTickQueue($this); $this->timers = new Timers(); } @@ -132,14 +129,6 @@ public function isTimerActive(TimerInterface $timer) return $this->timers->contains($timer); } - /** - * {@inheritdoc} - */ - public function nextTick(callable $listener) - { - $this->nextTickQueue->add($listener); - } - /** * {@inheritdoc} */ @@ -153,8 +142,6 @@ public function futureTick(callable $listener) */ public function tick() { - $this->nextTickQueue->tick(); - $this->futureTickQueue->tick(); $this->timers->tick(); @@ -170,14 +157,12 @@ public function run() $this->running = true; while ($this->running) { - $this->nextTickQueue->tick(); - $this->futureTickQueue->tick(); $this->timers->tick(); - // Next-tick or future-tick queues have pending callbacks ... - if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) { + // Future-tick queue has pending callbacks ... + if (!$this->running || !$this->futureTickQueue->isEmpty()) { $timeout = 0; // There is a pending timer, only block until it is due ... diff --git a/src/Tick/NextTickQueue.php b/src/Tick/NextTickQueue.php deleted file mode 100644 index 5b8e1de8..00000000 --- a/src/Tick/NextTickQueue.php +++ /dev/null @@ -1,57 +0,0 @@ -eventLoop = $eventLoop; - $this->queue = new SplQueue(); - } - - /** - * Add a callback to be invoked on the next tick of the event loop. - * - * Callbacks are guaranteed to be executed in the order they are enqueued, - * before any timer or stream events. - * - * @param callable $listener The callback to invoke. - */ - public function add(callable $listener) - { - $this->queue->enqueue($listener); - } - - /** - * Flush the callback queue. - */ - public function tick() - { - while (!$this->queue->isEmpty()) { - call_user_func( - $this->queue->dequeue(), - $this->eventLoop - ); - } - } - - /** - * Check if the next tick queue is empty. - * - * @return boolean - */ - public function isEmpty() - { - return $this->queue->isEmpty(); - } -} diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index b4095379..7c2eb0b4 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -200,7 +200,7 @@ function () { } ); - $this->loop->nextTick( + $this->loop->futureTick( function () { $this->loop->stop(); } @@ -231,126 +231,19 @@ public function testIgnoreRemovedCallback() $loop->run(); } - public function testNextTick() - { - $called = false; - - $callback = function ($loop) use (&$called) { - $this->assertSame($this->loop, $loop); - $called = true; - }; - - $this->loop->nextTick($callback); - - $this->assertFalse($called); - - $this->loop->tick(); - - $this->assertTrue($called); - } - - public function testNextTickFiresBeforeIO() + public function testFutureTickEventGeneratedByFutureTick() { - $stream = $this->createStream(); - - $this->loop->addWriteStream( - $stream, - function () { - echo 'stream' . PHP_EOL; - } - ); - - $this->loop->nextTick( - function () { - echo 'next-tick' . PHP_EOL; - } - ); - - $this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL); - - $this->loop->tick(); - } - - public function testRecursiveNextTick() - { - $stream = $this->createStream(); - - $this->loop->addWriteStream( - $stream, - function () { - echo 'stream' . PHP_EOL; - } - ); - - $this->loop->nextTick( - function () { - $this->loop->nextTick( - function () { - echo 'next-tick' . PHP_EOL; - } - ); - } - ); - - $this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL); - - $this->loop->tick(); - } - - public function testRunWaitsForNextTickEvents() - { - $stream = $this->createStream(); - - $this->loop->addWriteStream( - $stream, - function () use ($stream) { - $this->loop->removeStream($stream); - $this->loop->nextTick( - function () { - echo 'next-tick' . PHP_EOL; - } - ); - } - ); - - $this->expectOutputString('next-tick' . PHP_EOL); - - $this->loop->run(); - } - - public function testNextTickEventGeneratedByFutureTick() - { - $stream = $this->createStream(); - $this->loop->futureTick( function () { - $this->loop->nextTick( - function () { - echo 'next-tick' . PHP_EOL; - } - ); - } - ); - - $this->expectOutputString('next-tick' . PHP_EOL); - - $this->loop->run(); - } - - public function testNextTickEventGeneratedByTimer() - { - $this->loop->addTimer( - 0.001, - function () { - $this->loop->nextTick( + $this->loop->futureTick( function () { - echo 'next-tick' . PHP_EOL; + echo 'future-tick' . PHP_EOL; } ); } ); - $this->expectOutputString('next-tick' . PHP_EOL); + $this->expectOutputString('future-tick' . PHP_EOL); $this->loop->run(); } @@ -444,25 +337,6 @@ function () { $this->loop->run(); } - public function testFutureTickEventGeneratedByNextTick() - { - $stream = $this->createStream(); - - $this->loop->nextTick( - function () { - $this->loop->futureTick( - function () { - echo 'future-tick' . PHP_EOL; - } - ); - } - ); - - $this->expectOutputString('future-tick' . PHP_EOL); - - $this->loop->run(); - } - public function testFutureTickEventGeneratedByTimer() { $this->loop->addTimer( From 480228fe16462a0b66d9e18ca5f66a5838c0ccc6 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sun, 13 Nov 2016 22:19:21 +0100 Subject: [PATCH 002/203] Added enhanced test-memory.php, originally from react/react --- examples/test-memory.php | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 examples/test-memory.php diff --git a/examples/test-memory.php b/examples/test-memory.php new file mode 100644 index 00000000..6d95a336 --- /dev/null +++ b/examples/test-memory.php @@ -0,0 +1,54 @@ +addTimer((int)$argv[1], function (TimerInterface $timer) { + $timer->getLoop()->stop(); + }); + +} + +$loop->addPeriodicTimer(0.001, function () use (&$i, $loop) { + $i++; + + $loop->addPeriodicTimer(1, function (TimerInterface $timer) { + $timer->cancel(); + }); +}); + +$loop->addPeriodicTimer(2, function () use (&$i) { + $kmem = round(memory_get_usage() / 1024); + $kmemReal = round(memory_get_usage(true) / 1024); + echo "Runs:\t\t\t$i\n"; + echo "Memory (internal):\t$kmem KiB\n"; + echo "Memory (real):\t\t$kmemReal KiB\n"; + echo str_repeat('-', 50), "\n"; +}); + +echo "Loop\t\t\t", get_class($loop), "\n"; +echo "Time\t\t\t", date('r'), "\n"; + +echo str_repeat('-', 50), "\n"; + +$beginTime = time(); +$loop->run(); +$endTime = time(); +$timeTaken = $endTime - $beginTime; + +echo "Loop\t\t\t", get_class($loop), "\n"; +echo "Time\t\t\t", date('r'), "\n"; +echo "Time taken\t\t", $timeTaken, " seconds\n"; +echo "Runs per second\t\t", round($i / $timeTaken), "\n"; From 2cacaa3701ae1d1f3aca63f8c9e36e007a720961 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sun, 13 Nov 2016 22:25:30 +0100 Subject: [PATCH 003/203] Added usage --- examples/test-memory.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/test-memory.php b/examples/test-memory.php index 6d95a336..3c36b33a 100644 --- a/examples/test-memory.php +++ b/examples/test-memory.php @@ -1,5 +1,11 @@ Date: Mon, 14 Nov 2016 13:29:12 +0100 Subject: [PATCH 004/203] Applied argument suggestions by @clue from https://github.com/reactphp/event-loop/pull/59#issuecomment-260259139 --- examples/test-memory.php | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/examples/test-memory.php b/examples/test-memory.php index 3c36b33a..36cf5ba2 100644 --- a/examples/test-memory.php +++ b/examples/test-memory.php @@ -1,49 +1,55 @@ addTimer((int)$argv[1], function (TimerInterface $timer) { +if (5 < $t) { + $loop->addTimer($t, function (TimerInterface $timer) { $timer->getLoop()->stop(); }); } -$loop->addPeriodicTimer(0.001, function () use (&$i, $loop) { - $i++; +$loop->addPeriodicTimer(0.001, function () use (&$runs, $loop) { + $runs++; $loop->addPeriodicTimer(1, function (TimerInterface $timer) { $timer->cancel(); }); }); -$loop->addPeriodicTimer(2, function () use (&$i) { +$loop->addPeriodicTimer($r, function () use (&$runs) { $kmem = round(memory_get_usage() / 1024); $kmemReal = round(memory_get_usage(true) / 1024); - echo "Runs:\t\t\t$i\n"; + echo "Runs:\t\t\t$runs\n"; echo "Memory (internal):\t$kmem KiB\n"; echo "Memory (real):\t\t$kmemReal KiB\n"; echo str_repeat('-', 50), "\n"; }); +echo "PHP Version:\t\t", phpversion(), "\n"; echo "Loop\t\t\t", get_class($loop), "\n"; echo "Time\t\t\t", date('r'), "\n"; @@ -54,7 +60,8 @@ $endTime = time(); $timeTaken = $endTime - $beginTime; +echo "PHP Version:\t\t", phpversion(), "\n"; echo "Loop\t\t\t", get_class($loop), "\n"; echo "Time\t\t\t", date('r'), "\n"; echo "Time taken\t\t", $timeTaken, " seconds\n"; -echo "Runs per second\t\t", round($i / $timeTaken), "\n"; +echo "Runs per second\t\t", round($runs / $timeTaken), "\n"; From 8fa040756c3a62de2e5e160c2a4d63595c0c658e Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Fri, 3 Feb 2017 10:01:27 +0100 Subject: [PATCH 005/203] Drop tick() from LoopInterface --- src/ExtEventLoop.php | 13 ---------- src/LibEvLoop.php | 12 ---------- src/LibEventLoop.php | 12 ---------- src/LoopInterface.php | 5 ---- src/StreamSelectLoop.php | 14 ----------- tests/AbstractLoopTest.php | 40 +++++++++++++++---------------- tests/ExtEventLoopTest.php | 4 ++-- tests/TestCase.php | 11 +++++++++ tests/Timer/AbstractTimerTest.php | 20 ++++++++-------- 9 files changed, 43 insertions(+), 88 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 7160c908..593c2c62 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -169,19 +169,6 @@ public function futureTick(callable $listener) $this->futureTickQueue->add($listener); } - /** - * {@inheritdoc} - */ - public function tick() - { - $this->nextTickQueue->tick(); - - $this->futureTickQueue->tick(); - - // @-suppression: https://github.com/reactphp/react/pull/234#discussion-diff-7759616R226 - @$this->eventBase->loop(EventBase::LOOP_ONCE | EventBase::LOOP_NONBLOCK); - } - /** * {@inheritdoc} */ diff --git a/src/LibEvLoop.php b/src/LibEvLoop.php index 38e4ec2c..ca3426a9 100644 --- a/src/LibEvLoop.php +++ b/src/LibEvLoop.php @@ -173,18 +173,6 @@ public function futureTick(callable $listener) $this->futureTickQueue->add($listener); } - /** - * {@inheritdoc} - */ - public function tick() - { - $this->nextTickQueue->tick(); - - $this->futureTickQueue->tick(); - - $this->loop->run(EventLoop::RUN_ONCE | EventLoop::RUN_NOWAIT); - } - /** * {@inheritdoc} */ diff --git a/src/LibEventLoop.php b/src/LibEventLoop.php index 99417a12..f6a4aba0 100644 --- a/src/LibEventLoop.php +++ b/src/LibEventLoop.php @@ -177,18 +177,6 @@ public function futureTick(callable $listener) $this->futureTickQueue->add($listener); } - /** - * {@inheritdoc} - */ - public function tick() - { - $this->nextTickQueue->tick(); - - $this->futureTickQueue->tick(); - - event_base_loop($this->eventBase, EVLOOP_ONCE | EVLOOP_NONBLOCK); - } - /** * {@inheritdoc} */ diff --git a/src/LoopInterface.php b/src/LoopInterface.php index d046526c..bb01d720 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -104,11 +104,6 @@ public function nextTick(callable $listener); */ public function futureTick(callable $listener); - /** - * Perform a single iteration of the event loop. - */ - public function tick(); - /** * Run the event loop until there are no more tasks to perform. */ diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index e51a27f8..f0d68e1a 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -148,20 +148,6 @@ public function futureTick(callable $listener) $this->futureTickQueue->add($listener); } - /** - * {@inheritdoc} - */ - public function tick() - { - $this->nextTickQueue->tick(); - - $this->futureTickQueue->tick(); - - $this->timers->tick(); - - $this->waitForStreamActivity(0); - } - /** * {@inheritdoc} */ diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 50b3e432..c45c4d7a 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -38,10 +38,10 @@ public function testAddReadStream() $this->loop->addReadStream($input, $this->expectCallableExactly(2)); $this->writeToStream($input, "foo\n"); - $this->loop->tick(); + $this->tickLoop($this->loop); $this->writeToStream($input, "bar\n"); - $this->loop->tick(); + $this->tickLoop($this->loop); } public function testAddWriteStream() @@ -49,8 +49,8 @@ public function testAddWriteStream() $input = $this->createStream(); $this->loop->addWriteStream($input, $this->expectCallableExactly(2)); - $this->loop->tick(); - $this->loop->tick(); + $this->tickLoop($this->loop); + $this->tickLoop($this->loop); } public function testRemoveReadStreamInstantly() @@ -61,7 +61,7 @@ public function testRemoveReadStreamInstantly() $this->loop->removeReadStream($input); $this->writeToStream($input, "bar\n"); - $this->loop->tick(); + $this->tickLoop($this->loop); } public function testRemoveReadStreamAfterReading() @@ -71,12 +71,12 @@ public function testRemoveReadStreamAfterReading() $this->loop->addReadStream($input, $this->expectCallableOnce()); $this->writeToStream($input, "foo\n"); - $this->loop->tick(); + $this->tickLoop($this->loop); $this->loop->removeReadStream($input); $this->writeToStream($input, "bar\n"); - $this->loop->tick(); + $this->tickLoop($this->loop); } public function testRemoveWriteStreamInstantly() @@ -85,7 +85,7 @@ public function testRemoveWriteStreamInstantly() $this->loop->addWriteStream($input, $this->expectCallableNever()); $this->loop->removeWriteStream($input); - $this->loop->tick(); + $this->tickLoop($this->loop); } public function testRemoveWriteStreamAfterWriting() @@ -93,10 +93,10 @@ public function testRemoveWriteStreamAfterWriting() $input = $this->createStream(); $this->loop->addWriteStream($input, $this->expectCallableOnce()); - $this->loop->tick(); + $this->tickLoop($this->loop); $this->loop->removeWriteStream($input); - $this->loop->tick(); + $this->tickLoop($this->loop); } public function testRemoveStreamInstantly() @@ -108,7 +108,7 @@ public function testRemoveStreamInstantly() $this->loop->removeStream($input); $this->writeToStream($input, "bar\n"); - $this->loop->tick(); + $this->tickLoop($this->loop); } public function testRemoveStreamForReadOnly() @@ -120,7 +120,7 @@ public function testRemoveStreamForReadOnly() $this->loop->removeReadStream($input); $this->writeToStream($input, "foo\n"); - $this->loop->tick(); + $this->tickLoop($this->loop); } public function testRemoveStreamForWriteOnly() @@ -133,7 +133,7 @@ public function testRemoveStreamForWriteOnly() $this->loop->addWriteStream($input, $this->expectCallableNever()); $this->loop->removeWriteStream($input); - $this->loop->tick(); + $this->tickLoop($this->loop); } public function testRemoveStream() @@ -144,12 +144,12 @@ public function testRemoveStream() $this->loop->addWriteStream($input, $this->expectCallableOnce()); $this->writeToStream($input, "bar\n"); - $this->loop->tick(); + $this->tickLoop($this->loop); $this->loop->removeStream($input); $this->writeToStream($input, "bar\n"); - $this->loop->tick(); + $this->tickLoop($this->loop); } public function testRemoveInvalid() @@ -251,7 +251,7 @@ public function testNextTick() $this->assertFalse($called); - $this->loop->tick(); + $this->tickLoop($this->loop); $this->assertTrue($called); } @@ -275,7 +275,7 @@ function () { $this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL); - $this->loop->tick(); + $this->tickLoop($this->loop); } public function testRecursiveNextTick() @@ -301,7 +301,7 @@ function () { $this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL); - $this->loop->tick(); + $this->tickLoop($this->loop); } public function testRunWaitsForNextTickEvents() @@ -375,7 +375,7 @@ public function testFutureTick() $this->assertFalse($called); - $this->loop->tick(); + $this->tickLoop($this->loop); $this->assertTrue($called); } @@ -399,7 +399,7 @@ function () { $this->expectOutputString('future-tick' . PHP_EOL . 'stream' . PHP_EOL); - $this->loop->tick(); + $this->tickLoop($this->loop); } public function testRecursiveFutureTick() diff --git a/tests/ExtEventLoopTest.php b/tests/ExtEventLoopTest.php index 71f798c1..761ff962 100644 --- a/tests/ExtEventLoopTest.php +++ b/tests/ExtEventLoopTest.php @@ -82,9 +82,9 @@ public function testCanUseReadableStreamWithFeatureFds() $this->loop->addReadStream($input, $this->expectCallableExactly(2)); $this->writeToStream($input, "foo\n"); - $this->loop->tick(); + $this->tickLoop($this->loop); $this->writeToStream($input, "bar\n"); - $this->loop->tick(); + $this->tickLoop($this->loop); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 5114f4e0..0bc26a40 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,6 +2,8 @@ namespace React\Tests\EventLoop; +use React\EventLoop\LoopInterface; + class TestCase extends \PHPUnit_Framework_TestCase { protected function expectCallableExactly($amount) @@ -38,4 +40,13 @@ protected function createCallableMock() { return $this->getMockBuilder('React\Tests\EventLoop\CallableStub')->getMock(); } + + protected function tickLoop(LoopInterface $loop) + { + $loop->futureTick(function () use ($loop) { + $loop->stop(); + }); + + $loop->run(); + } } diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index 57689658..e930ad37 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -16,7 +16,7 @@ public function testAddTimer() $loop->addTimer(0.001, $this->expectCallableOnce()); usleep(1000); - $loop->tick(); + $this->tickLoop($loop); } public function testAddPeriodicTimer() @@ -25,11 +25,11 @@ public function testAddPeriodicTimer() $loop->addPeriodicTimer(0.001, $this->expectCallableExactly(3)); usleep(1000); - $loop->tick(); + $this->tickLoop($loop); usleep(1000); - $loop->tick(); + $this->tickLoop($loop); usleep(1000); - $loop->tick(); + $this->tickLoop($loop); } public function testAddPeriodicTimerWithCancel() @@ -39,14 +39,14 @@ public function testAddPeriodicTimerWithCancel() $timer = $loop->addPeriodicTimer(0.001, $this->expectCallableExactly(2)); usleep(1000); - $loop->tick(); + $this->tickLoop($loop); usleep(1000); - $loop->tick(); + $this->tickLoop($loop); $timer->cancel(); usleep(1000); - $loop->tick(); + $this->tickLoop($loop); } public function testAddPeriodicTimerCancelsItself() @@ -64,11 +64,11 @@ public function testAddPeriodicTimerCancelsItself() }); usleep(1000); - $loop->tick(); + $this->tickLoop($loop); usleep(1000); - $loop->tick(); + $this->tickLoop($loop); usleep(1000); - $loop->tick(); + $this->tickLoop($loop); $this->assertSame(2, $i); } From 665fde6cb0834624e0d227c9146f0e5d656d2bc3 Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Fri, 3 Feb 2017 11:03:13 +0100 Subject: [PATCH 006/203] Add changelog entry for removing LoopInterface::tick() --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30787694..79bf001a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.5.0 (xxxx-xx-xx) + +* BC break: Remove `LoopInterface::tick()` (@jsor, #72) + ## 0.4.2 (2016-03-07) * Bug fix: No longer error when signals sent to StreamSelectLoop From d70ec6eabc077c8f33420bb0f5b5e8c7ae9902b8 Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Fri, 3 Feb 2017 11:54:17 +0100 Subject: [PATCH 007/203] Remove deprecated --dev option from composer install --- travis-init.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/travis-init.sh b/travis-init.sh index 07b1d2a8..f3f3b228 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -37,4 +37,4 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && fi -composer install --dev --prefer-source +composer install --prefer-source From 95b459ce5f4d614a5bdd0805c44d11f5bfdc77ff Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Fri, 3 Feb 2017 11:54:52 +0100 Subject: [PATCH 008/203] Remove --prefer-source from composer install --- travis-init.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/travis-init.sh b/travis-init.sh index f3f3b228..5b21086c 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -37,4 +37,4 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && fi -composer install --prefer-source +composer install From 54bf45dd6f09d7c228d2c5bfc98df64a2f1f94b9 Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Fri, 3 Feb 2017 11:57:04 +0100 Subject: [PATCH 009/203] Move composer install to .travis.yml --- .travis.yml | 4 +++- travis-init.sh | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index a29daf2b..85944a34 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,9 @@ php: - 7.0 - hhvm -install: ./travis-init.sh +install: + - ./travis-init.sh + - composer install script: - ./vendor/bin/phpunit --coverage-text diff --git a/travis-init.sh b/travis-init.sh index 5b21086c..424d3e0a 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -36,5 +36,3 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && fi fi - -composer install From bec5e5a8df3e9043b80559f1d64e86c7bdb48c6d Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Fri, 3 Feb 2017 12:16:45 +0100 Subject: [PATCH 010/203] Enable container based infrastructure on travis --- .travis.yml | 7 +++++++ travis-init.sh | 3 --- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 85944a34..d5f0760c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,13 @@ php: - 7.0 - hhvm +sudo: false + +addons: + apt: + packages: + - libevent-dev # Used by 'event' and 'libevent' PHP extensions + install: - ./travis-init.sh - composer install diff --git a/travis-init.sh b/travis-init.sh index 424d3e0a..f8990ab5 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -5,9 +5,6 @@ set -o pipefail if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]]; then - # install "libevent" (used by 'event' and 'libevent' PHP extensions) - sudo apt-get install -y libevent-dev - # install 'event' PHP extension echo "yes" | pecl install event From 5f9c697d990d71e13e38c4c5d412d66fac719594 Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Fri, 3 Feb 2017 12:17:54 +0100 Subject: [PATCH 011/203] Enable travis cache --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index d5f0760c..8bdf688d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,10 @@ addons: packages: - libevent-dev # Used by 'event' and 'libevent' PHP extensions +cache: + directories: + - $HOME/.composer/cache + install: - ./travis-init.sh - composer install From 91ffeb1c52c608f4c12ef088be2e217b82a19a77 Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Fri, 3 Feb 2017 12:23:55 +0100 Subject: [PATCH 012/203] Add PHP 7.1 to test matrix --- .travis.yml | 1 + travis-init.sh | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8bdf688d..f317c3f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ php: - 5.5 - 5.6 - 7.0 + - 7.1 - hhvm sudo: false diff --git a/travis-init.sh b/travis-init.sh index f8990ab5..87456016 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -9,7 +9,8 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && echo "yes" | pecl install event # install 'libevent' PHP extension (does not support php 7) - if [[ "$TRAVIS_PHP_VERSION" != "7.0" ]]; then + if [[ "$TRAVIS_PHP_VERSION" != "7.0" && + "$TRAVIS_PHP_VERSION" != "7.1" ]]; then curl http://pecl.php.net/get/libevent-0.1.0.tgz | tar -xz pushd libevent-0.1.0 phpize @@ -21,7 +22,8 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && fi # install 'libev' PHP extension (does not support php 7) - if [[ "$TRAVIS_PHP_VERSION" != "7.0" ]]; then + if [[ "$TRAVIS_PHP_VERSION" != "7.0" && + "$TRAVIS_PHP_VERSION" != "7.1" ]]; then git clone --recursive https://github.com/m4rw3r/php-libev pushd php-libev phpize From f986c5693aa7fcd1a2ee641fe6f418deba2e4318 Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Mon, 6 Feb 2017 19:44:54 +0100 Subject: [PATCH 013/203] Adjust composer cache directory to only cache package files --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f317c3f0..0b7ce2cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ addons: cache: directories: - - $HOME/.composer/cache + - $HOME/.composer/cache/files install: - ./travis-init.sh From 160181b2aa4f43ea7f3e8f5d1845cafbd92a92d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 8 Feb 2017 19:04:03 +0100 Subject: [PATCH 014/203] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index d6f7da51..cb027228 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ same event loop. This component provides a common `LoopInterface` that any library can target. This allows them to be used in the same loop, with one single `run` call that is controlled by the user. +> The master branch contains the code for the upcoming 0.5 release. +For the code of the current stable 0.4.x release, checkout the +[0.4 branch](https://github.com/reactphp/event-loop/tree/0.4). + In addition to the interface there are some implementations provided: * `StreamSelectLoop`: This is the only implementation which works out of the From 378efec0573f150e6b4032025308e0f8cd1a0cf2 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Wed, 8 Feb 2017 21:50:42 +0100 Subject: [PATCH 015/203] AppVeyor configuration --- appveyor.yml | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..ad64e60a --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,46 @@ +build: false +platform: + - x64 + - x86 +clone_folder: c:\projects\php-project-workspace + + +## Build matrix with the different PHP versions we test against +environment: + matrix: + - php_ver_target: 5.6 + - php_ver_target: 7.0 + - php_ver_target: 7.1 + +## Cache composer bits +cache: + - '%LOCALAPPDATA%\Composer\files -> composer.lock' + +## Set up environment varriables +init: + - SET PATH=C:\Program Files\OpenSSL;c:\tools\php;%PATH% + - SET COMPOSER_NO_INTERACTION=1 + - SET PHP=1 + - SET ANSICON=121x90 (121x90) + +## Install PHP and composer, and run the appropriate composer command +install: + - IF EXIST c:\tools\php (SET PHP=0) + - ps: appveyor-retry cinst --ignore-checksums -y php --version ((choco search php --exact --all-versions -r | select-string -pattern $Env:php_ver_target | Select-Object -first 1) -replace '[php|]','') + - cd c:\tools\php + - IF %PHP%==1 copy php.ini-production php.ini /Y + - IF %PHP%==1 echo date.timezone="UTC" >> php.ini + - IF %PHP%==1 echo extension_dir=ext >> php.ini + - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini + - IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini + - IF %PHP%==1 echo extension=php_fileinfo.dll >> php.ini + - IF %PHP%==1 echo @php %%~dp0composer.phar %%* > composer.bat + - appveyor-retry appveyor DownloadFile https://getcomposer.org/composer.phar + - cd c:\projects\php-project-workspace + - appveyor-retry composer install --no-progress --profile + - composer show + +## Run the actual test +test_script: + - cd c:\projects\php-project-workspace + - vendor/bin/phpunit -c phpunit.xml.dist From c2434cf79f292c23d36fb6918e1b6da3ea87cef8 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 9 Feb 2017 12:39:09 +0100 Subject: [PATCH 016/203] Removed extra white space --- appveyor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index ad64e60a..decd0750 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,6 @@ platform: - x86 clone_folder: c:\projects\php-project-workspace - ## Build matrix with the different PHP versions we test against environment: matrix: From 912fe2b418b4f326a8b3e60fbaeeb7f13a47289e Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 13 Feb 2017 16:37:08 +0100 Subject: [PATCH 017/203] Revert "Run tests on windows using AppVeyor" --- appveyor.yml | 45 --------------------------------------------- 1 file changed, 45 deletions(-) delete mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index decd0750..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,45 +0,0 @@ -build: false -platform: - - x64 - - x86 -clone_folder: c:\projects\php-project-workspace - -## Build matrix with the different PHP versions we test against -environment: - matrix: - - php_ver_target: 5.6 - - php_ver_target: 7.0 - - php_ver_target: 7.1 - -## Cache composer bits -cache: - - '%LOCALAPPDATA%\Composer\files -> composer.lock' - -## Set up environment varriables -init: - - SET PATH=C:\Program Files\OpenSSL;c:\tools\php;%PATH% - - SET COMPOSER_NO_INTERACTION=1 - - SET PHP=1 - - SET ANSICON=121x90 (121x90) - -## Install PHP and composer, and run the appropriate composer command -install: - - IF EXIST c:\tools\php (SET PHP=0) - - ps: appveyor-retry cinst --ignore-checksums -y php --version ((choco search php --exact --all-versions -r | select-string -pattern $Env:php_ver_target | Select-Object -first 1) -replace '[php|]','') - - cd c:\tools\php - - IF %PHP%==1 copy php.ini-production php.ini /Y - - IF %PHP%==1 echo date.timezone="UTC" >> php.ini - - IF %PHP%==1 echo extension_dir=ext >> php.ini - - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini - - IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini - - IF %PHP%==1 echo extension=php_fileinfo.dll >> php.ini - - IF %PHP%==1 echo @php %%~dp0composer.phar %%* > composer.bat - - appveyor-retry appveyor DownloadFile https://getcomposer.org/composer.phar - - cd c:\projects\php-project-workspace - - appveyor-retry composer install --no-progress --profile - - composer show - -## Run the actual test -test_script: - - cd c:\projects\php-project-workspace - - vendor/bin/phpunit -c phpunit.xml.dist From e7bc391611778d7a1ee31d9959f9df86761cba89 Mon Sep 17 00:00:00 2001 From: martinschroeder Date: Mon, 9 Jan 2017 12:06:19 +0100 Subject: [PATCH 018/203] Switched to socket pairs in test cases. Removed deprecated code. --- tests/AbstractLoopTest.php | 103 +++++++++++++++++++-------------- tests/ExtEventLoopTest.php | 4 +- tests/StreamSelectLoopTest.php | 2 +- 3 files changed, 61 insertions(+), 48 deletions(-) diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 8c175a67..d054648a 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -20,33 +20,36 @@ public function setUp() abstract public function createLoop(); - public function createStream() + public function createSocketPair() { - return fopen('php://temp', 'r+'); - } + $domain = (DIRECTORY_SEPARATOR === '\\') ? STREAM_PF_INET : STREAM_PF_UNIX; + $sockets = stream_socket_pair($domain, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); - public function writeToStream($stream, $content) - { - fwrite($stream, $content); - rewind($stream); + foreach ($sockets as $socket) { + if (function_exists('stream_set_read_buffer')) { + stream_set_read_buffer($socket, 0); + } + } + + return $sockets; } public function testAddReadStream() { - $input = $this->createStream(); + list ($input, $output) = $this->createSocketPair(); $this->loop->addReadStream($input, $this->expectCallableExactly(2)); - $this->writeToStream($input, "foo\n"); + fwrite($output, "foo\n"); $this->tickLoop($this->loop); - $this->writeToStream($input, "bar\n"); + fwrite($output, "bar\n"); $this->tickLoop($this->loop); } public function testAddWriteStream() { - $input = $this->createStream(); + list ($input) = $this->createSocketPair(); $this->loop->addWriteStream($input, $this->expectCallableExactly(2)); $this->tickLoop($this->loop); @@ -55,33 +58,33 @@ public function testAddWriteStream() public function testRemoveReadStreamInstantly() { - $input = $this->createStream(); + list ($input, $output) = $this->createSocketPair(); $this->loop->addReadStream($input, $this->expectCallableNever()); $this->loop->removeReadStream($input); - $this->writeToStream($input, "bar\n"); + fwrite($output, "bar\n"); $this->tickLoop($this->loop); } public function testRemoveReadStreamAfterReading() { - $input = $this->createStream(); + list ($input, $output) = $this->createSocketPair(); $this->loop->addReadStream($input, $this->expectCallableOnce()); - $this->writeToStream($input, "foo\n"); + fwrite($output, "foo\n"); $this->tickLoop($this->loop); $this->loop->removeReadStream($input); - $this->writeToStream($input, "bar\n"); + fwrite($output, "bar\n"); $this->tickLoop($this->loop); } public function testRemoveWriteStreamInstantly() { - $input = $this->createStream(); + list ($input) = $this->createSocketPair(); $this->loop->addWriteStream($input, $this->expectCallableNever()); $this->loop->removeWriteStream($input); @@ -90,7 +93,7 @@ public function testRemoveWriteStreamInstantly() public function testRemoveWriteStreamAfterWriting() { - $input = $this->createStream(); + list ($input) = $this->createSocketPair(); $this->loop->addWriteStream($input, $this->expectCallableOnce()); $this->tickLoop($this->loop); @@ -101,60 +104,60 @@ public function testRemoveWriteStreamAfterWriting() public function testRemoveStreamInstantly() { - $input = $this->createStream(); + list ($input, $output) = $this->createSocketPair(); $this->loop->addReadStream($input, $this->expectCallableNever()); $this->loop->addWriteStream($input, $this->expectCallableNever()); $this->loop->removeStream($input); - $this->writeToStream($input, "bar\n"); + fwrite($output, "bar\n"); $this->tickLoop($this->loop); } public function testRemoveStreamForReadOnly() { - $input = $this->createStream(); + list ($input, $output) = $this->createSocketPair(); $this->loop->addReadStream($input, $this->expectCallableNever()); - $this->loop->addWriteStream($input, $this->expectCallableOnce()); + $this->loop->addWriteStream($output, $this->expectCallableOnce()); $this->loop->removeReadStream($input); - $this->writeToStream($input, "foo\n"); + fwrite($output, "foo\n"); $this->tickLoop($this->loop); } public function testRemoveStreamForWriteOnly() { - $input = $this->createStream(); + list ($input, $output) = $this->createSocketPair(); - $this->writeToStream($input, "foo\n"); + fwrite($output, "foo\n"); $this->loop->addReadStream($input, $this->expectCallableOnce()); - $this->loop->addWriteStream($input, $this->expectCallableNever()); - $this->loop->removeWriteStream($input); + $this->loop->addWriteStream($output, $this->expectCallableNever()); + $this->loop->removeWriteStream($output); $this->tickLoop($this->loop); } public function testRemoveStream() { - $input = $this->createStream(); + list ($input, $output) = $this->createSocketPair(); $this->loop->addReadStream($input, $this->expectCallableOnce()); $this->loop->addWriteStream($input, $this->expectCallableOnce()); - $this->writeToStream($input, "bar\n"); + fwrite($output, "bar\n"); $this->tickLoop($this->loop); $this->loop->removeStream($input); - $this->writeToStream($input, "bar\n"); + fwrite($output, "bar\n"); $this->tickLoop($this->loop); } public function testRemoveInvalid() { - $stream = $this->createStream(); + list ($stream) = $this->createSocketPair(); // remove a valid stream from the event loop that was never added in the first place $this->loop->removeReadStream($stream); @@ -171,14 +174,14 @@ public function emptyRunShouldSimplyReturn() /** @test */ public function runShouldReturnWhenNoMoreFds() { - $input = $this->createStream(); + list ($input, $output) = $this->createSocketPair(); $loop = $this->loop; $this->loop->addReadStream($input, function ($stream) use ($loop) { $loop->removeStream($stream); }); - $this->writeToStream($input, "foo\n"); + fwrite($output, "foo\n"); $this->assertRunFasterThan($this->tickTimeout * 2); } @@ -186,14 +189,14 @@ public function runShouldReturnWhenNoMoreFds() /** @test */ public function stopShouldStopRunningLoop() { - $input = $this->createStream(); + list ($input, $output) = $this->createSocketPair(); $loop = $this->loop; $this->loop->addReadStream($input, function ($stream) use ($loop) { $loop->stop(); }); - $this->writeToStream($input, "foo\n"); + fwrite($output, "foo\n"); $this->assertRunFasterThan($this->tickTimeout * 2); } @@ -219,23 +222,33 @@ function () { public function testIgnoreRemovedCallback() { // two independent streams, both should be readable right away - $stream1 = $this->createStream(); - $stream2 = $this->createStream(); + list ($input1, $output1) = $this->createSocketPair(); + list ($input2, $output2) = $this->createSocketPair(); + + $called = false; $loop = $this->loop; - $loop->addReadStream($stream1, function ($stream) use ($loop, $stream2) { + $loop->addReadStream($input1, function ($stream) use (& $called, $loop, $input2) { // stream1 is readable, remove stream2 as well => this will invalidate its callback $loop->removeReadStream($stream); - $loop->removeReadStream($stream2); + $loop->removeReadStream($input2); + + $called = true; }); // this callback would have to be called as well, but the first stream already removed us - $loop->addReadStream($stream2, $this->expectCallableNever()); + $loop->addReadStream($input2, function () use (& $called) { + if ($called) { + $this->fail('Callback 2 must not be called after callback 1 was called'); + } + }); - $this->writeToStream($stream1, "foo\n"); - $this->writeToStream($stream2, "foo\n"); + fwrite($output1, "foo\n"); + fwrite($output2, "foo\n"); $loop->run(); + + $this->assertTrue($called); } public function testFutureTickEventGeneratedByFutureTick() @@ -275,7 +288,7 @@ public function testFutureTick() public function testFutureTickFiresBeforeIO() { - $stream = $this->createStream(); + list ($stream) = $this->createSocketPair(); $this->loop->addWriteStream( $stream, @@ -297,7 +310,7 @@ function () { public function testRecursiveFutureTick() { - $stream = $this->createStream(); + list ($stream) = $this->createSocketPair(); $this->loop->addWriteStream( $stream, @@ -325,7 +338,7 @@ function () { public function testRunWaitsForFutureTickEvents() { - $stream = $this->createStream(); + list ($stream) = $this->createSocketPair(); $this->loop->addWriteStream( $stream, diff --git a/tests/ExtEventLoopTest.php b/tests/ExtEventLoopTest.php index 761ff962..52c65309 100644 --- a/tests/ExtEventLoopTest.php +++ b/tests/ExtEventLoopTest.php @@ -81,10 +81,10 @@ public function testCanUseReadableStreamWithFeatureFds() $this->loop->addReadStream($input, $this->expectCallableExactly(2)); - $this->writeToStream($input, "foo\n"); + fwrite($input, "foo\n"); $this->tickLoop($this->loop); - $this->writeToStream($input, "bar\n"); + fwrite($input, "bar\n"); $this->tickLoop($this->loop); } } diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index 61b059c1..1457b789 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -88,7 +88,7 @@ public function testSignalInterruptWithStream($sigName, $signal) $this->loop->addPeriodicTimer(0.01, function() { pcntl_signal_dispatch(); }); // add stream to the loop - list($writeStream, $readStream) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + list($writeStream, $readStream) = $this->createSocketPair(); $this->loop->addReadStream($readStream, function($stream, $loop) { /** @var $loop LoopInterface */ $read = fgets($stream); From 94f984ffe6386dc60637e79d6a17424c11298add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Schr=C3=B6der?= Date: Wed, 11 Jan 2017 09:16:27 +0100 Subject: [PATCH 019/203] Provide only signal constant names in data provider. --- tests/StreamSelectLoopTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index 1457b789..5cb957b7 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -40,9 +40,9 @@ public function testStreamSelectTimeoutEmulation() public function signalProvider() { return [ - ['SIGUSR1', SIGUSR1], - ['SIGHUP', SIGHUP], - ['SIGTERM', SIGTERM], + ['SIGUSR1'], + ['SIGHUP'], + ['SIGTERM'], ]; } @@ -52,7 +52,7 @@ public function signalProvider() * Test signal interrupt when no stream is attached to the loop * @dataProvider signalProvider */ - public function testSignalInterruptNoStream($sigName, $signal) + public function testSignalInterruptNoStream($signal) { if (!extension_loaded('pcntl')) { $this->markTestSkipped('"pcntl" extension is required to run this test.'); @@ -78,7 +78,7 @@ public function testSignalInterruptNoStream($sigName, $signal) * Test signal interrupt when a stream is attached to the loop * @dataProvider signalProvider */ - public function testSignalInterruptWithStream($sigName, $signal) + public function testSignalInterruptWithStream($signal) { if (!extension_loaded('pcntl')) { $this->markTestSkipped('"pcntl" extension is required to run this test.'); @@ -116,7 +116,7 @@ public function testSignalInterruptWithStream($sigName, $signal) protected function setUpSignalHandler($signal) { $this->_signalHandled = false; - $this->assertTrue(pcntl_signal($signal, function() { $this->_signalHandled = true; })); + $this->assertTrue(pcntl_signal(constant($signal), function() { $this->_signalHandled = true; })); } /** @@ -125,7 +125,7 @@ protected function setUpSignalHandler($signal) protected function resetSignalHandlers() { foreach($this->signalProvider() as $signal) { - pcntl_signal($signal[1], SIG_DFL); + pcntl_signal(constant($signal[0]), SIG_DFL); } } @@ -141,7 +141,7 @@ protected function forkSendSignal($signal) } else if ($childPid === 0) { // this is executed in the child process usleep(20000); - posix_kill($currentPid, $signal); + posix_kill($currentPid, constant($signal)); die(); } } From 473c25a3d1329638980fdb890e4a2ea49508ebf7 Mon Sep 17 00:00:00 2001 From: Maciej Mazur Date: Mon, 9 Jan 2017 10:52:07 +0100 Subject: [PATCH 020/203] ExtEventLoop: No longer suppress all errors --- src/ExtEventLoop.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 51704475..d94e65db 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -175,8 +175,7 @@ public function run() break; } - // @-suppression: https://github.com/reactphp/react/pull/234#discussion-diff-7759616R226 - @$this->eventBase->loop($flags); + $this->eventBase->loop($flags); } } From f184905e4ec0d29dc679c15de4cfcdb45db91048 Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Sat, 25 Mar 2017 12:57:14 +0100 Subject: [PATCH 021/203] Fix code indention and add some linebreaks --- README.md | 49 ++++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index cb027228..99b72eb7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # EventLoop Component -[![Build Status](https://secure.travis-ci.org/reactphp/event-loop.png?branch=master)](http://travis-ci.org/reactphp/event-loop) [![Code Climate](https://codeclimate.com/github/reactphp/event-loop/badges/gpa.svg)](https://codeclimate.com/github/reactphp/event-loop) +[![Build Status](https://secure.travis-ci.org/reactphp/event-loop.png?branch=master)](http://travis-ci.org/reactphp/event-loop) +[![Code Climate](https://codeclimate.com/github/reactphp/event-loop/badges/gpa.svg)](https://codeclimate.com/github/reactphp/event-loop) Event loop abstraction layer that libraries can use for evented I/O. @@ -39,33 +40,35 @@ All of the loops support these features: ## Usage Here is an async HTTP server built with just the event loop. + ```php - $loop = React\EventLoop\Factory::create(); - - $server = stream_socket_server('tcp://127.0.0.1:8080'); - stream_set_blocking($server, 0); - $loop->addReadStream($server, function ($server) use ($loop) { - $conn = stream_socket_accept($server); - $data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n"; - $loop->addWriteStream($conn, function ($conn) use (&$data, $loop) { - $written = fwrite($conn, $data); - if ($written === strlen($data)) { - fclose($conn); - $loop->removeStream($conn); - } else { - $data = substr($data, $written); - } - }); +$loop = React\EventLoop\Factory::create(); + +$server = stream_socket_server('tcp://127.0.0.1:8080'); +stream_set_blocking($server, 0); +$loop->addReadStream($server, function ($server) use ($loop) { + $conn = stream_socket_accept($server); + $data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n"; + $loop->addWriteStream($conn, function ($conn) use (&$data, $loop) { + $written = fwrite($conn, $data); + if ($written === strlen($data)) { + fclose($conn); + $loop->removeStream($conn); + } else { + $data = substr($data, $written); + } }); +}); - $loop->addPeriodicTimer(5, function () { - $memory = memory_get_usage() / 1024; - $formatted = number_format($memory, 3).'K'; - echo "Current memory usage: {$formatted}\n"; - }); +$loop->addPeriodicTimer(5, function () { + $memory = memory_get_usage() / 1024; + $formatted = number_format($memory, 3).'K'; + echo "Current memory usage: {$formatted}\n"; +}); - $loop->run(); +$loop->run(); ``` + **Note:** The factory is just for convenience. It tries to pick the best available implementation. Libraries `SHOULD` allow the user to inject an instance of the loop. They `MAY` use the factory when the user did not supply From 025cd88cb5c2031420f16688fa5b1f96c59b7b6e Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Sat, 25 Mar 2017 12:58:58 +0100 Subject: [PATCH 022/203] Use svg travis badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 99b72eb7..ab1f462f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # EventLoop Component -[![Build Status](https://secure.travis-ci.org/reactphp/event-loop.png?branch=master)](http://travis-ci.org/reactphp/event-loop) +[![Build Status](https://travis-ci.org/reactphp/event-loop.svg?branch=master)](https://travis-ci.org/reactphp/event-loop) [![Code Climate](https://codeclimate.com/github/reactphp/event-loop/badges/gpa.svg)](https://codeclimate.com/github/reactphp/event-loop) Event loop abstraction layer that libraries can use for evented I/O. From a86fae236e521df585fc76a981b15b67c1b665e9 Mon Sep 17 00:00:00 2001 From: Niklas Keller Date: Sat, 25 Mar 2017 13:41:33 +0100 Subject: [PATCH 023/203] Add tests for double watchers to be ignored --- tests/AbstractLoopTest.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index d054648a..3351a8aa 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -47,6 +47,20 @@ public function testAddReadStream() $this->tickLoop($this->loop); } + public function testAddReadStreamIgnoresSecondCallable() + { + list ($input, $output) = $this->createSocketPair(); + + $this->loop->addReadStream($input, $this->expectCallableExactly(2)); + $this->loop->addReadStream($input, $this->expectCallableNever()); + + fwrite($output, "foo\n"); + $this->tickLoop($this->loop); + + fwrite($output, "bar\n"); + $this->tickLoop($this->loop); + } + public function testAddWriteStream() { list ($input) = $this->createSocketPair(); @@ -56,6 +70,16 @@ public function testAddWriteStream() $this->tickLoop($this->loop); } + public function testAddWriteStreamIgnoresSecondCallable() + { + list ($input) = $this->createSocketPair(); + + $this->loop->addWriteStream($input, $this->expectCallableExactly(2)); + $this->loop->addWriteStream($input, $this->expectCallableNever()); + $this->tickLoop($this->loop); + $this->tickLoop($this->loop); + } + public function testRemoveReadStreamInstantly() { list ($input, $output) = $this->createSocketPair(); From 94ef2499e97697d29618650f1d029513bdaedca0 Mon Sep 17 00:00:00 2001 From: Niklas Keller Date: Sat, 25 Mar 2017 13:59:58 +0100 Subject: [PATCH 024/203] Fix LibEvLoop to ignore double IO streams --- src/LibEvLoop.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/LibEvLoop.php b/src/LibEvLoop.php index 1f0124ae..929d2eb6 100644 --- a/src/LibEvLoop.php +++ b/src/LibEvLoop.php @@ -35,6 +35,10 @@ public function __construct() */ public function addReadStream($stream, callable $listener) { + if (isset($this->readEvents[(int) $stream])) { + return; + } + $callback = function () use ($stream, $listener) { call_user_func($listener, $stream, $this); }; @@ -50,6 +54,10 @@ public function addReadStream($stream, callable $listener) */ public function addWriteStream($stream, callable $listener) { + if (isset($this->writeEvents[(int) $stream])) { + return; + } + $callback = function () use ($stream, $listener) { call_user_func($listener, $stream, $this); }; From 2090e918b10ab9b587949b1db44f0cbf23285955 Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Mon, 27 Mar 2017 08:47:38 +0200 Subject: [PATCH 025/203] Restructure and improve README * Separate into sections and add a TOC at the beginning * Add new sections for Install, Tests and License * Add paragraph to Usage emphasizing on recommended program structuring using a single loop instance --- README.md | 131 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 100 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index ab1f462f..6967450c 100644 --- a/README.md +++ b/README.md @@ -5,39 +5,20 @@ Event loop abstraction layer that libraries can use for evented I/O. -In order for async based libraries to be interoperable, they need to use the -same event loop. This component provides a common `LoopInterface` that any -library can target. This allows them to be used in the same loop, with one -single `run` call that is controlled by the user. - > The master branch contains the code for the upcoming 0.5 release. For the code of the current stable 0.4.x release, checkout the [0.4 branch](https://github.com/reactphp/event-loop/tree/0.4). -In addition to the interface there are some implementations provided: - -* `StreamSelectLoop`: This is the only implementation which works out of the - box with PHP. It does a simple `select` system call. It's not the most - performant of loops, but still does the job quite well. - -* `LibEventLoop`: This uses the `libevent` pecl extension. `libevent` itself - supports a number of system-specific backends (epoll, kqueue). - -* `LibEvLoop`: This uses the `libev` pecl extension - ([github](https://github.com/m4rw3r/php-libev)). It supports the same - backends as libevent. +**Table of Contents** -* `ExtEventLoop`: This uses the `event` pecl extension. It supports the same - backends as libevent. +* [Quickstart example](#quickstart-example) +* [Usage](#usage) +* [Loop implementations](#loop-implementations) +* [Install](#install) +* [Tests](#tests) +* [License](#license) -All of the loops support these features: - -* File descriptor polling -* One-off timers -* Periodic timers -* Deferred execution of callbacks - -## Usage +## Quickstart example Here is an async HTTP server built with just the event loop. @@ -46,6 +27,7 @@ $loop = React\EventLoop\Factory::create(); $server = stream_socket_server('tcp://127.0.0.1:8080'); stream_set_blocking($server, 0); + $loop->addReadStream($server, function ($server) use ($loop) { $conn = stream_socket_accept($server); $data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n"; @@ -69,7 +51,94 @@ $loop->addPeriodicTimer(5, function () { $loop->run(); ``` -**Note:** The factory is just for convenience. It tries to pick the best -available implementation. Libraries `SHOULD` allow the user to inject an -instance of the loop. They `MAY` use the factory when the user did not supply -a loop. +## Usage + +In order for async based libraries to be interoperable, they need to use the +same event loop. This component provides a common `LoopInterface` that any +library can target. This allows them to be used in the same loop, with one +single `run()` call that is controlled by the user. + +```php +// [1] +$loop = React\EventLoop\Factory::create(); + +// [2] +$loop->addPeriodicTimer(1, function () { + echo "Tick\n"; +}); + +$stream = new React\Stream\ReadableResourceStream( + fopen('file.txt', 'r'), + $loop +); + +// [3] +$loop->run(); +``` + +1. The loop instance is created at the beginning of the program. A convenience + factory `React\EventLoop\Factory::create()` is provided by this library which + picks the best available [loop implementation](#loop-implementations). +2. The loop instance is used directly or passed to library and application code. + In this example, a periodic timer is registered with the event loop which + simply outputs `Tick` every second and a + [readable stream](https://github.com/reactphp/stream#readableresourcestream) + is created by using ReactPHP's + [stream component](https://github.com/reactphp/stream) for demonstration + purposes. +3. The loop is run with a single `$loop->run()` call at the end of the program. + +## Loop implementations + +In addition to the interface there are the following implementations provided: + +* `StreamSelectLoop`: This is the only implementation which works out of the + box with PHP. It does a simple `select` system call. It's not the most + performant of loops, but still does the job quite well. + +* `LibEventLoop`: This uses the `libevent` pecl extension. `libevent` itself + supports a number of system-specific backends (epoll, kqueue). + +* `LibEvLoop`: This uses the `libev` pecl extension + ([github](https://github.com/m4rw3r/php-libev)). It supports the same + backends as libevent. + +* `ExtEventLoop`: This uses the `event` pecl extension. It supports the same + backends as libevent. + +All of the loops support these features: + +* File descriptor polling +* One-off timers +* Periodic timers +* Deferred execution of callbacks + +## Install + +The recommended way to install this library is [through Composer](http://getcomposer.org). +[New to Composer?](http://getcomposer.org/doc/00-intro.md) + +This will install the latest supported version: + +```bash +$ composer require react/event-loop +``` + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](http://getcomposer.org): + +```bash +$ composer install +``` + +To run the test suite, go to the project root and run: + +```bash +$ php vendor/bin/phpunit +``` + +## License + +MIT, see [LICENSE file](LICENSE). From 49491cb4272de2a12afbb32224d4ee83aebb9ae2 Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Mon, 27 Mar 2017 14:41:57 +0200 Subject: [PATCH 026/203] Move general description back to top and use short introduction for usage --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6967450c..4e4ee3d0 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,11 @@ Event loop abstraction layer that libraries can use for evented I/O. +In order for async based libraries to be interoperable, they need to use the +same event loop. This component provides a common `LoopInterface` that any +library can target. This allows them to be used in the same loop, with one +single `run()` call that is controlled by the user. + > The master branch contains the code for the upcoming 0.5 release. For the code of the current stable 0.4.x release, checkout the [0.4 branch](https://github.com/reactphp/event-loop/tree/0.4). @@ -53,10 +58,8 @@ $loop->run(); ## Usage -In order for async based libraries to be interoperable, they need to use the -same event loop. This component provides a common `LoopInterface` that any -library can target. This allows them to be used in the same loop, with one -single `run()` call that is controlled by the user. +Typical applications use a single event loop which is created at the beginning +and run at the end of the program. ```php // [1] From 08be7c1d9ac57b857fa921b3b8589decc7e4772f Mon Sep 17 00:00:00 2001 From: Niklas Keller Date: Wed, 29 Mar 2017 12:43:22 +0200 Subject: [PATCH 027/203] Increase test timeout, because Travis is slow --- tests/AbstractLoopTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 3351a8aa..465fee22 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -13,8 +13,8 @@ abstract class AbstractLoopTest extends TestCase public function setUp() { - // HHVM is a bit slow, so give it more time - $this->tickTimeout = defined('HHVM_VERSION') ? 0.02 : 0.005; + // It's a timeout, don't set it too low. Travis and other CI systems are slow. + $this->tickTimeout = 0.02; $this->loop = $this->createLoop(); } From bdc6191f89dfd0b0f3b7847b8448451af66d7982 Mon Sep 17 00:00:00 2001 From: Ivan Kalita Date: Mon, 3 Apr 2017 22:47:39 +0300 Subject: [PATCH 028/203] Add StreamSelectLoop test for timer with small interval. --- tests/StreamSelectLoopTest.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index 5cb957b7..d2e3e078 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -4,6 +4,7 @@ use React\EventLoop\LoopInterface; use React\EventLoop\StreamSelectLoop; +use React\EventLoop\Timer\Timer; class StreamSelectLoopTest extends AbstractLoopTest { @@ -145,4 +146,34 @@ protected function forkSendSignal($signal) die(); } } + + /** + * https://github.com/reactphp/event-loop/issues/48 + * + * Tests that timer with very small interval uses at least 1 microsecond + * timeout. + */ + public function testSmallTimerInterval() + { + /** @var StreamSelectLoop|\PHPUnit_Framework_MockObject_MockObject $loop */ + $loop = $this->getMock('React\EventLoop\StreamSelectLoop', ['streamSelect']); + $loop + ->expects($this->at(0)) + ->method('streamSelect') + ->with([], [], 1); + $loop + ->expects($this->at(1)) + ->method('streamSelect') + ->with([], [], 0); + + $callsCount = 0; + $loop->addPeriodicTimer(Timer::MIN_INTERVAL, function() use (&$loop, &$callsCount) { + $callsCount++; + if ($callsCount == 2) { + $loop->stop(); + } + }); + + $loop->run(); + } } From 09cd0bcb4038dea3e84283984a011fc2c32285f0 Mon Sep 17 00:00:00 2001 From: Ivan Kalita Date: Mon, 3 Apr 2017 23:10:33 +0300 Subject: [PATCH 029/203] Add StreamSelectLoop timeout rounding. --- src/StreamSelectLoop.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index da5ddaa4..92fc5787 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -159,7 +159,11 @@ public function run() if ($timeout < 0) { $timeout = 0; } else { - $timeout *= self::MICROSECONDS_PER_SECOND; + /* + * round() needed to correct float error: + * https://github.com/reactphp/event-loop/issues/48 + */ + $timeout = round($timeout * self::MICROSECONDS_PER_SECOND); } // The only possible event is stream activity, so wait forever ... From d4892c6bb2acae94e857e00e5348839d2a481fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 25 Apr 2017 13:16:11 +0200 Subject: [PATCH 030/203] Add examples to ease getting started --- README.md | 2 ++ examples/01-timers.php | 15 ++++++++++++++ examples/02-periodic.php | 16 ++++++++++++++ examples/11-consume-stdin.php | 26 +++++++++++++++++++++++ examples/12-generate-yes.php | 39 +++++++++++++++++++++++++++++++++++ examples/21-http-server.php | 30 +++++++++++++++++++++++++++ 6 files changed, 128 insertions(+) create mode 100644 examples/01-timers.php create mode 100644 examples/02-periodic.php create mode 100644 examples/11-consume-stdin.php create mode 100644 examples/12-generate-yes.php create mode 100644 examples/21-http-server.php diff --git a/README.md b/README.md index 4e4ee3d0..96ee01f9 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ $loop->addPeriodicTimer(5, function () { $loop->run(); ``` +See also the [examples](examples). + ## Usage Typical applications use a single event loop which is created at the beginning diff --git a/examples/01-timers.php b/examples/01-timers.php new file mode 100644 index 00000000..e6107e46 --- /dev/null +++ b/examples/01-timers.php @@ -0,0 +1,15 @@ +addTimer(0.8, function () { + echo 'world!' . PHP_EOL; +}); + +$loop->addTimer(0.3, function () { + echo 'hello '; +}); + +$loop->run(); diff --git a/examples/02-periodic.php b/examples/02-periodic.php new file mode 100644 index 00000000..5e138a62 --- /dev/null +++ b/examples/02-periodic.php @@ -0,0 +1,16 @@ +addPeriodicTimer(0.1, function () { + echo 'tick!' . PHP_EOL; +}); + +$loop->addTimer(1.0, function () use ($loop, $timer) { + $loop->cancelTimer($timer); + echo 'Done' . PHP_EOL; +}); + +$loop->run(); diff --git a/examples/11-consume-stdin.php b/examples/11-consume-stdin.php new file mode 100644 index 00000000..c1874dd3 --- /dev/null +++ b/examples/11-consume-stdin.php @@ -0,0 +1,26 @@ +addReadStream(STDIN, function ($stream) use ($loop) { + $chunk = fread($stream, 64 * 1024); + + // reading nothing means we reached EOF + if ($chunk === '') { + $loop->removeReadStream($stream); + return; + } + + echo strlen($chunk) . ' bytes' . PHP_EOL; +}); + +$loop->run(); diff --git a/examples/12-generate-yes.php b/examples/12-generate-yes.php new file mode 100644 index 00000000..bb78e88f --- /dev/null +++ b/examples/12-generate-yes.php @@ -0,0 +1,39 @@ +addWriteStream($stdout, function () use ($loop, $stdout, &$data) { + // try to write data + $r = fwrite($stdout, $data); + + // nothing could be written despite being writable => closed + if ($r === 0) { + $loop->removeWriteStream($stdout); + fclose($stdout); + fwrite(STDERR, 'Stopped because STDOUT closed' . PHP_EOL); + + return; + } + + // implement a very simple ring buffer, unless everything has been written at once: + // everything written in this iteration will be appended for next iteration + if (isset($data[$r])) { + $data = substr($data, $r) . substr($data, 0, $r); + } +}); + +$loop->run(); diff --git a/examples/21-http-server.php b/examples/21-http-server.php new file mode 100644 index 00000000..67423957 --- /dev/null +++ b/examples/21-http-server.php @@ -0,0 +1,30 @@ +addReadStream($server, function ($server) use ($loop) { + $conn = stream_socket_accept($server); + $data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n"; + $loop->addWriteStream($conn, function ($conn) use (&$data, $loop) { + $written = fwrite($conn, $data); + if ($written === strlen($data)) { + fclose($conn); + $loop->removeStream($conn); + } else { + $data = substr($data, $written); + } + }); +}); + +$loop->addPeriodicTimer(5, function () { + $memory = memory_get_usage() / 1024; + $formatted = number_format($memory, 3).'K'; + echo "Current memory usage: {$formatted}\n"; +}); + +$loop->run(); From ac541bfe238dc3a8b32f8145199d7d1e4b7dceee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 25 Apr 2017 13:36:31 +0200 Subject: [PATCH 031/203] Documentation and examples for deferred execution via futureTick() --- README.md | 29 +++++++++++++++++++++++++- examples/03-ticks.php | 15 +++++++++++++ examples/91-benchmark-ticks.php | 15 +++++++++++++ examples/92-benchmark-timers.php | 15 +++++++++++++ examples/93-benchmark-ticks-delay.php | 22 +++++++++++++++++++ examples/94-benchmark-timers-delay.php | 22 +++++++++++++++++++ src/LoopInterface.php | 10 ++++++++- 7 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 examples/03-ticks.php create mode 100644 examples/91-benchmark-ticks.php create mode 100644 examples/92-benchmark-timers.php create mode 100644 examples/93-benchmark-ticks-delay.php create mode 100644 examples/94-benchmark-timers-delay.php diff --git a/README.md b/README.md index 4e4ee3d0..d46414ca 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,34 @@ All of the loops support these features: * File descriptor polling * One-off timers * Periodic timers -* Deferred execution of callbacks +* Deferred execution on future loop tick + +### futureTick() + +The `futureTick(callable $listener): void` method can be used to +schedule a callback to be invoked on a future tick of the event loop. + +This works very much similar to timers with an interval of zero seconds, +but does not require the overhead of scheduling a timer queue. + +Unlike timers, callbacks are guaranteed to be executed in the order they +are enqueued. +Also, once a callback is enqueued, there's no way to cancel this operation. + +This is often used to break down bigger tasks into smaller steps (a form of +cooperative multitasking). + +```php +$loop->futureTick(function () { + echo 'b'; +}); +$loop->futureTick(function () { + echo 'c'; +}); +echo 'a'; +``` + +See also [example #3](examples). ## Install diff --git a/examples/03-ticks.php b/examples/03-ticks.php new file mode 100644 index 00000000..3f36c6d4 --- /dev/null +++ b/examples/03-ticks.php @@ -0,0 +1,15 @@ +futureTick(function () { + echo 'b'; +}); +$loop->futureTick(function () { + echo 'c'; +}); +echo 'a'; + +$loop->run(); diff --git a/examples/91-benchmark-ticks.php b/examples/91-benchmark-ticks.php new file mode 100644 index 00000000..cdaa1e15 --- /dev/null +++ b/examples/91-benchmark-ticks.php @@ -0,0 +1,15 @@ +nextTick(function () { }); +} + +$loop->run(); diff --git a/examples/92-benchmark-timers.php b/examples/92-benchmark-timers.php new file mode 100644 index 00000000..e2e02e49 --- /dev/null +++ b/examples/92-benchmark-timers.php @@ -0,0 +1,15 @@ +addTimer(0, function () { }); +} + +$loop->run(); diff --git a/examples/93-benchmark-ticks-delay.php b/examples/93-benchmark-ticks-delay.php new file mode 100644 index 00000000..e242c65f --- /dev/null +++ b/examples/93-benchmark-ticks-delay.php @@ -0,0 +1,22 @@ + 0) { + --$ticks; + //$loop->addTimer(0, $tick); + $loop->nextTick($tick); + } else { + echo 'done'; + } +}; + +$tick(); + +$loop->run(); diff --git a/examples/94-benchmark-timers-delay.php b/examples/94-benchmark-timers-delay.php new file mode 100644 index 00000000..69084e37 --- /dev/null +++ b/examples/94-benchmark-timers-delay.php @@ -0,0 +1,22 @@ + 0) { + --$ticks; + //$loop->nextTick($tick); + $loop->addTimer(0, $tick); + } else { + echo 'done'; + } +}; + +$tick(); + +$loop->run(); diff --git a/src/LoopInterface.php b/src/LoopInterface.php index b4750dd0..c2e5f75c 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -88,7 +88,15 @@ public function isTimerActive(TimerInterface $timer); /** * Schedule a callback to be invoked on a future tick of the event loop. * - * Callbacks are guaranteed to be executed in the order they are enqueued. + * This works very much similar to timers with an interval of zero seconds, + * but does not require the overhead of scheduling a timer queue. + * + * Unlike timers, callbacks are guaranteed to be executed in the order they + * are enqueued. + * Also, once a callback is enqueued, there's no way to cancel this operation. + * + * This is often used to break down bigger tasks into smaller steps (a form of + * cooperative multitasking). * * @param callable $listener The callback to invoke. */ From b0e721529b88b278dd2155f840e31980ffd53259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 26 Apr 2017 10:37:40 +0200 Subject: [PATCH 032/203] Documentation for timer API --- README.md | 125 ++++++++++++++++++++++++++++++++++++++++++ src/LoopInterface.php | 102 ++++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) diff --git a/README.md b/README.md index d67ad534..16f8d8a1 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,131 @@ All of the loops support these features: * Periodic timers * Deferred execution on future loop tick +### addTimer() + +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 timer callback function MUST NOT throw an `Exception`. +The return value of the timer callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure +the callback will be invoked only once after the given interval. +You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer. + +```php +$loop->addTimer(0.8, function () { + echo 'world!' . PHP_EOL; +}); + +$loop->addTimer(0.3, function () { + echo 'hello '; +}); +``` + +See also [example #1](examples). + +If you want to access any variables within your callback function, you +can bind arbitrary data to a callback closure like this: + +```php +function hello(LoopInterface $loop, $name) +{ + $loop->addTimer(1.0, function () use ($name) { + echo "hello $name\n"; + }); +} + +hello('Tester'); +``` + +The execution order of timers scheduled to execute at the same time is +not guaranteed. + +### addPeriodicTimer() + +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 timer callback function MUST NOT throw an `Exception`. +The return value of the timer callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +Unlike [`addTimer()`](#addtimer), this method will ensure the the +callback will be invoked infinitely after the given interval or until you +invoke [`cancelTimer`](#canceltimer). + +```php +$timer = $loop->addPeriodicTimer(0.1, function () { + echo 'tick!' . PHP_EOL; +}); + +$loop->addTimer(1.0, function () use ($loop, $timer) { + $loop->cancelTimer($timer); + echo 'Done' . PHP_EOL; +}); +``` + +See also [example #2](examples). + +If you want to limit the number of executions, you can bind +arbitrary data to a callback closure like this: + +```php +function hello(LoopInterface $loop, $name) +{ + $n = 3; + $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) { + if ($n > 0) { + --$n; + echo "hello $name\n"; + } else { + $loop->cancelTimer($timer); + } + }); +} + +hello('Tester'); +``` + +The execution order of timers scheduled to execute at the same time is +not guaranteed. + +### cancelTimer() + +The `cancelTimer(TimerInterface $timer): void` method can be used to +cancel a pending timer. + +See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples). + +You can use the [`isTimerActive()`](#istimeractive) method to check if +this timer is still "active". After a timer is successfully canceled, +it is no longer considered "active". + +Calling this method on a timer instance that has not been added to this +loop instance or on a timer + +### isTimerActive() + +The `isTimerActive(TimerInterface $timer): bool` method can be used to +check if a given timer is active. + +A timer is considered "active" if it has been added to this loop instance +via [`addTimer()`](#addtimer) or [`addPeriodicTimer()`](#addperiodictimer) +and has not been canceled via [`cancelTimer()`](#canceltimer) and is not +a non-periodic timer that has already been triggered after its interval. + ### futureTick() The `futureTick(callable $listener): void` method can be used to diff --git a/src/LoopInterface.php b/src/LoopInterface.php index c2e5f75c..4290d2a8 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -46,6 +46,45 @@ public function removeStream($stream); /** * Enqueue a callback to be invoked once after the given interval. * + * The timer callback function MUST be able to accept a single parameter, + * the timer instance as also returned by this method or you MAY use a + * function which has no parameters at all. + * + * The timer callback function MUST NOT throw an `Exception`. + * The return value of the timer callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure + * the callback will be invoked only once after the given interval. + * You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer. + * + * ```php + * $loop->addTimer(0.8, function () { + * echo 'world!' . PHP_EOL; + * }); + * + * $loop->addTimer(0.3, function () { + * echo 'hello '; + * }); + * ``` + * + * See also [example #1](examples). + * + * If you want to access any variables within your callback function, you + * can bind arbitrary data to a callback closure like this: + * + * ```php + * function hello(LoopInterface $loop, $name) + * { + * $loop->addTimer(1.0, function () use ($name) { + * echo "hello $name\n"; + * }); + * } + * + * hello('Tester'); + * ``` + * * The execution order of timers scheduled to execute at the same time is * not guaranteed. * @@ -59,6 +98,52 @@ public function addTimer($interval, callable $callback); /** * Enqueue a callback to be invoked repeatedly after the given interval. * + * The timer callback function MUST be able to accept a single parameter, + * the timer instance as also returned by this method or you MAY use a + * function which has no parameters at all. + * + * The timer callback function MUST NOT throw an `Exception`. + * The return value of the timer callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * Unlike [`addTimer()`](#addtimer), this method will ensure the the + * callback will be invoked infinitely after the given interval or until you + * invoke [`cancelTimer`](#canceltimer). + * + * ```php + * $timer = $loop->addPeriodicTimer(0.1, function () { + * echo 'tick!' . PHP_EOL; + * }); + * + * $loop->addTimer(1.0, function () use ($loop, $timer) { + * $loop->cancelTimer($timer); + * echo 'Done' . PHP_EOL; + * }); + * ``` + * + * See also [example #2](examples). + * + * If you want to limit the number of executions, you can bind + * arbitrary data to a callback closure like this: + * + * ```php + * function hello(LoopInterface $loop, $name) + * { + * $n = 3; + * $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) { + * if ($n > 0) { + * --$n; + * echo "hello $name\n"; + * } else { + * $loop->cancelTimer($timer); + * } + * }); + * } + * + * hello('Tester'); + * ``` + * * The execution order of timers scheduled to execute at the same time is * not guaranteed. * @@ -72,13 +157,30 @@ public function addPeriodicTimer($interval, callable $callback); /** * Cancel a pending timer. * + * See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples). + * + * You can use the [`isTimerActive()`](#istimeractive) method to check if + * this timer is still "active". After a timer is successfully canceled, + * it is no longer considered "active". + * + * Calling this method on a timer instance that has not been added to this + * loop instance or on a timer that is not "active" (or has already been + * canceled) has no effect. + * * @param TimerInterface $timer The timer to cancel. + * + * @return void */ public function cancelTimer(TimerInterface $timer); /** * Check if a given timer is active. * + * A timer is considered "active" if it has been added to this loop instance + * via [`addTimer()`](#addtimer) or [`addPeriodicTimer()`](#addperiodictimer) + * and has not been canceled via [`cancelTimer()`](#canceltimer) and is not + * a non-periodic timer that has already been triggered after its interval. + * * @param TimerInterface $timer The timer to check. * * @return boolean True if the timer is still enqueued for execution. From 7eb31c1fa9d801b18b9c56e8fc4d0a47bf109ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 26 Apr 2017 10:38:24 +0200 Subject: [PATCH 033/203] Remove unneeded and undocumented timer data, use closure binding instead The mixed "data" attribute is mostly unused and can easily be replaced by closure binding instead. This also prepares the timer instance to become an immutable data structure. --- src/Timer/Timer.php | 21 +-------------------- src/Timer/TimerInterface.php | 14 -------------- 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/src/Timer/Timer.php b/src/Timer/Timer.php index f670ab3c..bf68bb75 100644 --- a/src/Timer/Timer.php +++ b/src/Timer/Timer.php @@ -12,7 +12,6 @@ class Timer implements TimerInterface protected $interval; protected $callback; protected $periodic; - protected $data; /** * Constructor initializes the fields of the Timer @@ -21,9 +20,8 @@ class Timer implements TimerInterface * @param float $interval The interval after which this timer will execute, in seconds * @param callable $callback The callback that will be executed when this timer elapses * @param bool $periodic Whether the time is periodic - * @param mixed $data Arbitrary data associated with timer */ - public function __construct(LoopInterface $loop, $interval, callable $callback, $periodic = false, $data = null) + public function __construct(LoopInterface $loop, $interval, callable $callback, $periodic = false) { if ($interval < self::MIN_INTERVAL) { $interval = self::MIN_INTERVAL; @@ -33,7 +31,6 @@ public function __construct(LoopInterface $loop, $interval, callable $callback, $this->interval = (float) $interval; $this->callback = $callback; $this->periodic = (bool) $periodic; - $this->data = null; } /** @@ -60,22 +57,6 @@ public function getCallback() return $this->callback; } - /** - * {@inheritdoc} - */ - public function setData($data) - { - $this->data = $data; - } - - /** - * {@inheritdoc} - */ - public function getData() - { - return $this->data; - } - /** * {@inheritdoc} */ diff --git a/src/Timer/TimerInterface.php b/src/Timer/TimerInterface.php index d066f369..654cfe1d 100644 --- a/src/Timer/TimerInterface.php +++ b/src/Timer/TimerInterface.php @@ -27,20 +27,6 @@ public function getInterval(); */ public function getCallback(); - /** - * Set arbitrary data associated with timer - * - * @param mixed $data - */ - public function setData($data); - - /** - * Get arbitrary data associated with timer - * - * @return mixed - */ - public function getData(); - /** * Determine whether the time is periodic * From 2f8b16ad9278f590066049fe41378ed370bd3ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 26 Apr 2017 10:47:19 +0200 Subject: [PATCH 034/203] Remove unneeded duplicate methods cancel() and isActive() These can easily be replaced by Loop::cancelTimer() and Loop::isTimerActive(). This change ensures that timers are now a plain immutable data structure and offer no methods to change its state. This also prepares the timer to remove the cyclic dependency with the loop instance. --- src/Timer/Timer.php | 16 ---------------- src/Timer/TimerInterface.php | 12 ------------ tests/Timer/AbstractTimerTest.php | 8 ++++---- 3 files changed, 4 insertions(+), 32 deletions(-) diff --git a/src/Timer/Timer.php b/src/Timer/Timer.php index bf68bb75..f74a7c03 100644 --- a/src/Timer/Timer.php +++ b/src/Timer/Timer.php @@ -64,20 +64,4 @@ public function isPeriodic() { return $this->periodic; } - - /** - * {@inheritdoc} - */ - public function isActive() - { - return $this->loop->isTimerActive($this); - } - - /** - * {@inheritdoc} - */ - public function cancel() - { - $this->loop->cancelTimer($this); - } } diff --git a/src/Timer/TimerInterface.php b/src/Timer/TimerInterface.php index 654cfe1d..2cba1971 100644 --- a/src/Timer/TimerInterface.php +++ b/src/Timer/TimerInterface.php @@ -33,16 +33,4 @@ public function getCallback(); * @return bool */ public function isPeriodic(); - - /** - * Determine whether the time is active - * - * @return bool - */ - public function isActive(); - - /** - * Cancel this timer - */ - public function cancel(); } diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index e930ad37..dc32a577 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -43,7 +43,7 @@ public function testAddPeriodicTimerWithCancel() usleep(1000); $this->tickLoop($loop); - $timer->cancel(); + $loop->cancelTimer($timer); usleep(1000); $this->tickLoop($loop); @@ -55,11 +55,11 @@ public function testAddPeriodicTimerCancelsItself() $loop = $this->createLoop(); - $loop->addPeriodicTimer(0.001, function ($timer) use (&$i) { + $loop->addPeriodicTimer(0.001, function ($timer) use (&$i, $loop) { $i++; if ($i == 2) { - $timer->cancel(); + $loop->cancelTimer($timer); } }); @@ -81,7 +81,7 @@ public function testIsTimerActive() $this->assertTrue($loop->isTimerActive($timer)); - $timer->cancel(); + $loop->cancelTimer($timer); $this->assertFalse($loop->isTimerActive($timer)); } From cc8995f30636d595b962864ef18e7039f89f120b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 26 Apr 2017 10:53:23 +0200 Subject: [PATCH 035/203] Remove unneeded getLoop() method and cyclic dependency with loop Thid can easily be replaced by using closure binding as documented. --- src/ExtEventLoop.php | 4 ++-- src/LibEvLoop.php | 4 ++-- src/LibEventLoop.php | 4 ++-- src/StreamSelectLoop.php | 4 ++-- src/Timer/Timer.php | 15 +-------------- src/Timer/TimerInterface.php | 9 --------- tests/Timer/TimersTest.php | 6 +++--- 7 files changed, 12 insertions(+), 34 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index d94e65db..244ca87d 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -112,7 +112,7 @@ public function removeStream($stream) */ public function addTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, false); + $timer = new Timer($interval, $callback, false); $this->scheduleTimer($timer); @@ -124,7 +124,7 @@ public function addTimer($interval, callable $callback) */ public function addPeriodicTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, true); + $timer = new Timer($interval, $callback, true); $this->scheduleTimer($timer); diff --git a/src/LibEvLoop.php b/src/LibEvLoop.php index 929d2eb6..b4e7102c 100644 --- a/src/LibEvLoop.php +++ b/src/LibEvLoop.php @@ -108,7 +108,7 @@ public function removeStream($stream) */ public function addTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, false); + $timer = new Timer( $interval, $callback, false); $callback = function () use ($timer) { call_user_func($timer->getCallback(), $timer); @@ -130,7 +130,7 @@ public function addTimer($interval, callable $callback) */ public function addPeriodicTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, true); + $timer = new Timer($interval, $callback, true); $callback = function () use ($timer) { call_user_func($timer->getCallback(), $timer); diff --git a/src/LibEventLoop.php b/src/LibEventLoop.php index b2c771cf..af7e7943 100644 --- a/src/LibEventLoop.php +++ b/src/LibEventLoop.php @@ -116,7 +116,7 @@ public function removeStream($stream) */ public function addTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, false); + $timer = new Timer($interval, $callback, false); $this->scheduleTimer($timer); @@ -128,7 +128,7 @@ public function addTimer($interval, callable $callback) */ public function addPeriodicTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, true); + $timer = new Timer($interval, $callback, true); $this->scheduleTimer($timer); diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 92fc5787..80916032 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -94,7 +94,7 @@ public function removeStream($stream) */ public function addTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, false); + $timer = new Timer($interval, $callback, false); $this->timers->add($timer); @@ -106,7 +106,7 @@ public function addTimer($interval, callable $callback) */ public function addPeriodicTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, true); + $timer = new Timer($interval, $callback, true); $this->timers->add($timer); diff --git a/src/Timer/Timer.php b/src/Timer/Timer.php index f74a7c03..dc9a37c7 100644 --- a/src/Timer/Timer.php +++ b/src/Timer/Timer.php @@ -2,13 +2,10 @@ namespace React\EventLoop\Timer; -use React\EventLoop\LoopInterface; - class Timer implements TimerInterface { const MIN_INTERVAL = 0.000001; - protected $loop; protected $interval; protected $callback; protected $periodic; @@ -16,31 +13,21 @@ class Timer implements TimerInterface /** * Constructor initializes the fields of the Timer * - * @param LoopInterface $loop The loop with which this timer is associated * @param float $interval The interval after which this timer will execute, in seconds * @param callable $callback The callback that will be executed when this timer elapses * @param bool $periodic Whether the time is periodic */ - public function __construct(LoopInterface $loop, $interval, callable $callback, $periodic = false) + public function __construct($interval, callable $callback, $periodic = false) { if ($interval < self::MIN_INTERVAL) { $interval = self::MIN_INTERVAL; } - $this->loop = $loop; $this->interval = (float) $interval; $this->callback = $callback; $this->periodic = (bool) $periodic; } - /** - * {@inheritdoc} - */ - public function getLoop() - { - return $this->loop; - } - /** * {@inheritdoc} */ diff --git a/src/Timer/TimerInterface.php b/src/Timer/TimerInterface.php index 2cba1971..cf9028f9 100644 --- a/src/Timer/TimerInterface.php +++ b/src/Timer/TimerInterface.php @@ -2,17 +2,8 @@ namespace React\EventLoop\Timer; -use React\EventLoop\LoopInterface; - interface TimerInterface { - /** - * Get the loop with which this timer is associated - * - * @return LoopInterface - */ - public function getLoop(); - /** * Get the interval after which this timer will execute, in seconds * diff --git a/tests/Timer/TimersTest.php b/tests/Timer/TimersTest.php index 0ca87e16..c70a39cd 100644 --- a/tests/Timer/TimersTest.php +++ b/tests/Timer/TimersTest.php @@ -13,14 +13,14 @@ public function testBlockedTimer() $loop = $this ->getMockBuilder('React\EventLoop\LoopInterface') ->getMock(); - + $timers = new Timers(); $timers->tick(); - + // simulate a bunch of processing on stream events, // part of which schedules a future timer... sleep(1); - $timers->add(new Timer($loop, 0.5, function () { + $timers->add(new Timer(0.5, function () { $this->fail("Timer shouldn't be called"); })); From a6acc481e991cc114ce7a417c69e46a62d4035f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 26 Apr 2017 10:58:14 +0200 Subject: [PATCH 036/203] Mark internal timer API as final to discourage external usage --- src/Timer/Timer.php | 16 ++++++++++++---- src/Timer/Timers.php | 10 +++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Timer/Timer.php b/src/Timer/Timer.php index dc9a37c7..433f5be2 100644 --- a/src/Timer/Timer.php +++ b/src/Timer/Timer.php @@ -2,13 +2,21 @@ namespace React\EventLoop\Timer; -class Timer implements TimerInterface +/** + * The actual connection implementation for TimerInterface + * + * This class should only be used internally, see TimerInterface instead. + * + * @see TimerInterface + * @internal + */ +final class Timer implements TimerInterface { const MIN_INTERVAL = 0.000001; - protected $interval; - protected $callback; - protected $periodic; + private $interval; + private $callback; + private $periodic; /** * Constructor initializes the fields of the Timer diff --git a/src/Timer/Timers.php b/src/Timer/Timers.php index bebe5520..81a21735 100644 --- a/src/Timer/Timers.php +++ b/src/Timer/Timers.php @@ -5,7 +5,15 @@ use SplObjectStorage; use SplPriorityQueue; -class Timers +/** + * A scheduler implementation that can hold multiple timer instances + * + * This class should only be used internally, see TimerInterface instead. + * + * @see TimerInterface + * @internal + */ +final class Timers { private $time; private $timers; From 059d75a17c2f252c5fe1b5217c91866089c09ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 26 Apr 2017 11:48:27 +0200 Subject: [PATCH 037/203] Tick callback receives no arguments, remove cyclic dependency This can easily be replaced by using closure binding as documented. --- README.md | 29 ++++++++++++++--- examples/91-benchmark-ticks.php | 2 +- examples/93-benchmark-ticks-delay.php | 2 +- examples/94-benchmark-timers-delay.php | 2 +- src/ExtEventLoop.php | 2 +- src/LibEvLoop.php | 2 +- src/LibEventLoop.php | 2 +- src/LoopInterface.php | 43 +++++++++++++++++++++++--- src/StreamSelectLoop.php | 2 +- src/Tick/FutureTickQueue.php | 11 ++----- tests/AbstractLoopTest.php | 3 +- 11 files changed, 74 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index d67ad534..19e86181 100644 --- a/README.md +++ b/README.md @@ -126,12 +126,33 @@ schedule a callback to be invoked on a future tick of the event loop. This works very much similar to timers with an interval of zero seconds, but does not require the overhead of scheduling a timer queue. -Unlike timers, callbacks are guaranteed to be executed in the order they -are enqueued. +The tick callback function MUST be able to accept zero parameters. + +The tick callback function MUST NOT throw an `Exception`. +The return value of the tick callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +If you want to access any variables within your callback function, you +can bind arbitrary data to a callback closure like this: + +```php +function hello(LoopInterface $loop, $name) +{ + $loop->futureTick(function () use ($name) { + echo "hello $name\n"; + }); +} + +hello('Tester'); +``` + +Unlike timers, tick callbacks are guaranteed to be executed in the order +they are enqueued. Also, once a callback is enqueued, there's no way to cancel this operation. -This is often used to break down bigger tasks into smaller steps (a form of -cooperative multitasking). +This is often used to break down bigger tasks into smaller steps (a form +of cooperative multitasking). ```php $loop->futureTick(function () { diff --git a/examples/91-benchmark-ticks.php b/examples/91-benchmark-ticks.php index cdaa1e15..3f4690b3 100644 --- a/examples/91-benchmark-ticks.php +++ b/examples/91-benchmark-ticks.php @@ -9,7 +9,7 @@ $n = isset($argv[1]) ? (int)$argv[1] : 1000 * 100; for ($i = 0; $i < $n; ++$i) { - $loop->nextTick(function () { }); + $loop->futureTick(function () { }); } $loop->run(); diff --git a/examples/93-benchmark-ticks-delay.php b/examples/93-benchmark-ticks-delay.php index e242c65f..95ee78c6 100644 --- a/examples/93-benchmark-ticks-delay.php +++ b/examples/93-benchmark-ticks-delay.php @@ -11,7 +11,7 @@ if ($ticks > 0) { --$ticks; //$loop->addTimer(0, $tick); - $loop->nextTick($tick); + $loop->futureTick($tick); } else { echo 'done'; } diff --git a/examples/94-benchmark-timers-delay.php b/examples/94-benchmark-timers-delay.php index 69084e37..2d6cfa25 100644 --- a/examples/94-benchmark-timers-delay.php +++ b/examples/94-benchmark-timers-delay.php @@ -10,7 +10,7 @@ $tick = function () use (&$tick, &$ticks, $loop) { if ($ticks > 0) { --$ticks; - //$loop->nextTick($tick); + //$loop->futureTick($tick); $loop->addTimer(0, $tick); } else { echo 'done'; diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index d94e65db..538a3b9b 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -29,7 +29,7 @@ class ExtEventLoop implements LoopInterface public function __construct(EventBaseConfig $config = null) { $this->eventBase = new EventBase($config); - $this->futureTickQueue = new FutureTickQueue($this); + $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); $this->createTimerCallback(); diff --git a/src/LibEvLoop.php b/src/LibEvLoop.php index 929d2eb6..f26ff6cd 100644 --- a/src/LibEvLoop.php +++ b/src/LibEvLoop.php @@ -26,7 +26,7 @@ class LibEvLoop implements LoopInterface public function __construct() { $this->loop = new EventLoop(); - $this->futureTickQueue = new FutureTickQueue($this); + $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); } diff --git a/src/LibEventLoop.php b/src/LibEventLoop.php index b2c771cf..cf923d05 100644 --- a/src/LibEventLoop.php +++ b/src/LibEventLoop.php @@ -30,7 +30,7 @@ class LibEventLoop implements LoopInterface public function __construct() { $this->eventBase = event_base_new(); - $this->futureTickQueue = new FutureTickQueue($this); + $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); $this->createTimerCallback(); diff --git a/src/LoopInterface.php b/src/LoopInterface.php index c2e5f75c..90ddba9d 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -91,14 +91,49 @@ public function isTimerActive(TimerInterface $timer); * This works very much similar to timers with an interval of zero seconds, * but does not require the overhead of scheduling a timer queue. * - * Unlike timers, callbacks are guaranteed to be executed in the order they - * are enqueued. + * The tick callback function MUST be able to accept zero parameters. + * + * The tick callback function MUST NOT throw an `Exception`. + * The return value of the tick callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * If you want to access any variables within your callback function, you + * can bind arbitrary data to a callback closure like this: + * + * ```php + * function hello(LoopInterface $loop, $name) + * { + * $loop->futureTick(function () use ($name) { + * echo "hello $name\n"; + * }); + * } + * + * hello('Tester'); + * ``` + * + * Unlike timers, tick callbacks are guaranteed to be executed in the order + * they are enqueued. * Also, once a callback is enqueued, there's no way to cancel this operation. * - * This is often used to break down bigger tasks into smaller steps (a form of - * cooperative multitasking). + * This is often used to break down bigger tasks into smaller steps (a form + * of cooperative multitasking). + * + * ```php + * $loop->futureTick(function () { + * echo 'b'; + * }); + * $loop->futureTick(function () { + * echo 'c'; + * }); + * echo 'a'; + * ``` + * + * See also [example #3](examples). * * @param callable $listener The callback to invoke. + * + * @return void */ public function futureTick(callable $listener); diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 92fc5787..b4eafa2c 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -24,7 +24,7 @@ class StreamSelectLoop implements LoopInterface public function __construct() { - $this->futureTickQueue = new FutureTickQueue($this); + $this->futureTickQueue = new FutureTickQueue(); $this->timers = new Timers(); } diff --git a/src/Tick/FutureTickQueue.php b/src/Tick/FutureTickQueue.php index eeffd363..4a7c0bf7 100644 --- a/src/Tick/FutureTickQueue.php +++ b/src/Tick/FutureTickQueue.php @@ -2,20 +2,14 @@ namespace React\EventLoop\Tick; -use React\EventLoop\LoopInterface; use SplQueue; class FutureTickQueue { - private $eventLoop; private $queue; - /** - * @param LoopInterface $eventLoop The event loop passed as the first parameter to callbacks. - */ - public function __construct(LoopInterface $eventLoop) + public function __construct() { - $this->eventLoop = $eventLoop; $this->queue = new SplQueue(); } @@ -41,8 +35,7 @@ public function tick() while ($count--) { call_user_func( - $this->queue->dequeue(), - $this->eventLoop + $this->queue->dequeue() ); } } diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 3351a8aa..2470e555 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -296,8 +296,7 @@ public function testFutureTick() { $called = false; - $callback = function ($loop) use (&$called) { - $this->assertSame($this->loop, $loop); + $callback = function () use (&$called) { $called = true; }; From 4827e00b5ac62a8d4d285f370bd8188eacbb0513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 26 Apr 2017 11:50:48 +0200 Subject: [PATCH 038/203] Mark internal tick API as final to discourage external usage --- src/Tick/FutureTickQueue.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Tick/FutureTickQueue.php b/src/Tick/FutureTickQueue.php index 4a7c0bf7..eb65345d 100644 --- a/src/Tick/FutureTickQueue.php +++ b/src/Tick/FutureTickQueue.php @@ -4,7 +4,15 @@ use SplQueue; -class FutureTickQueue +/** + * A tick queue implementation that can hold multiple callback functions + * + * This class should only be used internally, see LoopInterface instead. + * + * @see LoopInterface + * @internal + */ +final class FutureTickQueue { private $queue; From af65ac7b845e636799026b5984962325a56e7b28 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 25 Jul 2017 21:37:35 +0200 Subject: [PATCH 039/203] Run HHVM tests on trusty and allow it to fail --- .travis.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0b7ce2cd..13e6b6fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,14 @@ php: - 5.6 - 7.0 - 7.1 - - hhvm + +# also test against HHVM, but require "trusty" and ignore errors +matrix: + include: + - php: hhvm + dist: trusty + allow_failures: + - php: hhvm sudo: false From 0835fc1beb38142bbf754d0d2ad00aebcdbe33f1 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Wed, 26 Jul 2017 09:32:17 +0200 Subject: [PATCH 040/203] Updated autoloading path and renamed to 95-benchmark-memory.php --- examples/{test-memory.php => 95-benchmark-memory.php} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename examples/{test-memory.php => 95-benchmark-memory.php} (97%) diff --git a/examples/test-memory.php b/examples/95-benchmark-memory.php similarity index 97% rename from examples/test-memory.php rename to examples/95-benchmark-memory.php index 36cf5ba2..7720aca1 100644 --- a/examples/test-memory.php +++ b/examples/95-benchmark-memory.php @@ -11,7 +11,7 @@ use React\EventLoop\LoopInterface; use React\EventLoop\Timer\TimerInterface; -require __DIR__.'/vendor/autoload.php'; +require __DIR__ . '/../vendor/autoload.php'; $args = getopt('t:l:r:'); $t = isset($args['t']) ? (int)$args['t'] : 0; From 72a24354c3e2212bc21adca7c8309acbb59a6362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 4 Aug 2017 12:03:17 +0200 Subject: [PATCH 041/203] Lock Travis distro so new future defaults will not break the build --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 13e6b6fc..3bf76646 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,9 @@ php: - 7.0 - 7.1 +# lock distro so new future defaults will not break the build +dist: precise + # also test against HHVM, but require "trusty" and ignore errors matrix: include: From f32f0a1a3de9a1b5fc9ef955039e8a545d56a768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 4 Aug 2017 12:09:49 +0200 Subject: [PATCH 042/203] Update Travis distro to precise --- .travis.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3bf76646..63db6ca1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,15 +6,12 @@ php: - 5.6 - 7.0 - 7.1 + - hhvm # ignore errors, see below # lock distro so new future defaults will not break the build -dist: precise +dist: trusty -# also test against HHVM, but require "trusty" and ignore errors matrix: - include: - - php: hhvm - dist: trusty allow_failures: - php: hhvm From bda850fdb4d67b0a5d316e36cdd01ac3842345d9 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Thu, 17 Nov 2016 11:49:01 -0500 Subject: [PATCH 043/203] Discourage libevent on PHP 7 --- src/Factory.php | 7 ++++--- src/LibEventLoop.php | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Factory.php b/src/Factory.php index 9a481e35..271c2654 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -7,12 +7,13 @@ class Factory public static function create() { // @codeCoverageIgnoreStart - if (function_exists('event_base_new')) { - return new LibEventLoop(); - } elseif (class_exists('libev\EventLoop', false)) { + if (class_exists('libev\EventLoop', false)) { return new LibEvLoop; } 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 + return new LibEventLoop(); } return new StreamSelectLoop(); diff --git a/src/LibEventLoop.php b/src/LibEventLoop.php index 4b252d45..ff27cd2d 100644 --- a/src/LibEventLoop.php +++ b/src/LibEventLoop.php @@ -29,6 +29,10 @@ class LibEventLoop implements LoopInterface public function __construct() { + if (version_compare(PHP_VERSION, '7.0.0') >= 0) { + trigger_error('The libevent extension has not yet been updated for PHP 7, it is recommended you use a different loop', E_USER_WARNING); + } + $this->eventBase = event_base_new(); $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); From f1c4746aed686fad34b149eaef37c3ea38eb9df9 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Wed, 11 Oct 2017 09:38:07 -0400 Subject: [PATCH 044/203] Remove warning on invalid version mismatch --- src/LibEventLoop.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/LibEventLoop.php b/src/LibEventLoop.php index ff27cd2d..4b252d45 100644 --- a/src/LibEventLoop.php +++ b/src/LibEventLoop.php @@ -29,10 +29,6 @@ class LibEventLoop implements LoopInterface public function __construct() { - if (version_compare(PHP_VERSION, '7.0.0') >= 0) { - trigger_error('The libevent extension has not yet been updated for PHP 7, it is recommended you use a different loop', E_USER_WARNING); - } - $this->eventBase = event_base_new(); $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); From 7c7aae976127a86ac255542026862ada979acef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 13 Oct 2017 12:27:54 +0200 Subject: [PATCH 045/203] Documentation for stream API --- README.md | 77 +++++++++++++++++++++++++++++++++++++++++++ src/LoopInterface.php | 52 +++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/README.md b/README.md index aa80be6e..edf4f596 100644 --- a/README.md +++ b/README.md @@ -291,6 +291,83 @@ echo 'a'; See also [example #3](examples). +### addReadStream() + +The `addReadStream(resource $stream, callable $callback): void` method can be used to +register a listener to be notified when a stream is ready to read. + +The listener callback function MUST be able to accept a single parameter, +the stream resource added by this method or you MAY use a function which +has no parameters at all. + +The listener callback function MUST NOT throw an `Exception`. +The return value of the listener callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +If you want to access any variables within your callback function, you +can bind arbitrary data to a callback closure like this: + +```php +$loop->addReadStream($stream, function ($stream) use ($name) { + echo $name . ' said: ' . fread($stream); +}); +``` + +See also [example #11](examples). + +You can invoke [`removeReadStream()`](#removereadstream) to remove the +read event listener for this stream. + +The execution order of listeners when multiple streams become ready at +the same time is not guaranteed. + +### addWriteStream() + +The `addWriteStream(resource $stream, callable $callback): void` method can be used to +register a listener to be notified when a stream is ready to write. + +The listener callback function MUST be able to accept a single parameter, +the stream resource added by this method or you MAY use a function which +has no parameters at all. + +The listener callback function MUST NOT throw an `Exception`. +The return value of the listener callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +If you want to access any variables within your callback function, you +can bind arbitrary data to a callback closure like this: + +```php +$loop->addWriteStream($stream, function ($stream) use ($name) { + fwrite($stream, 'Hello ' . $name); +}); +``` + +See also [example #12](examples). + +You can invoke [`removeWriteStream()`](#removewritestream) to remove the +write event listener for this stream. + +The execution order of listeners when multiple streams become ready at +the same time is not guaranteed. + +### removeReadStream() + +The `removeReadStream(resource $stream): void` method can be used to +remove the read event listener for the given stream. + +### removeWriteStream() + +The `removeWriteStream(resource $stream): void` method can be used to +remove the write event listener for the given stream. + +### removeStream() + +The `removeStream(resource $stream): void` method can be used to +remove all listeners for the given stream. + ## Install The recommended way to install this library is [through Composer](http://getcomposer.org). diff --git a/src/LoopInterface.php b/src/LoopInterface.php index 789ef192..7bb7de59 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -9,6 +9,32 @@ interface LoopInterface /** * Register a listener to be notified when a stream is ready to read. * + * The listener callback function MUST be able to accept a single parameter, + * the stream resource added by this method or you MAY use a function which + * has no parameters at all. + * + * The listener callback function MUST NOT throw an `Exception`. + * The return value of the listener callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * If you want to access any variables within your callback function, you + * can bind arbitrary data to a callback closure like this: + * + * ```php + * $loop->addReadStream($stream, function ($stream) use ($name) { + * echo $name . ' said: ' . fread($stream); + * }); + * ``` + * + * See also [example #11](examples). + * + * You can invoke [`removeReadStream()`](#removereadstream) to remove the + * read event listener for this stream. + * + * The execution order of listeners when multiple streams become ready at + * the same time is not guaranteed. + * * @param resource $stream The PHP stream resource to check. * @param callable $listener Invoked when the stream is ready. */ @@ -17,6 +43,32 @@ public function addReadStream($stream, callable $listener); /** * Register a listener to be notified when a stream is ready to write. * + * The listener callback function MUST be able to accept a single parameter, + * the stream resource added by this method or you MAY use a function which + * has no parameters at all. + * + * The listener callback function MUST NOT throw an `Exception`. + * The return value of the listener callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * If you want to access any variables within your callback function, you + * can bind arbitrary data to a callback closure like this: + * + * ```php + * $loop->addWriteStream($stream, function ($stream) use ($name) { + * fwrite($stream, 'Hello ' . $name); + * }); + * ``` + * + * See also [example #12](examples). + * + * You can invoke [`removeWriteStream()`](#removewritestream) to remove the + * write event listener for this stream. + * + * The execution order of listeners when multiple streams become ready at + * the same time is not guaranteed. + * * @param resource $stream The PHP stream resource to check. * @param callable $listener Invoked when the stream is ready. */ From 8997565319b8aceea8ae6300f3c0930bf0ed0fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 13 Oct 2017 12:29:05 +0200 Subject: [PATCH 046/203] Remove unneeded and undocumented loop argument for stream listeners --- src/ExtEventLoop.php | 4 ++-- src/LibEvLoop.php | 4 ++-- src/LibEventLoop.php | 4 ++-- src/StreamSelectLoop.php | 4 ++-- tests/StreamSelectLoopTest.php | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index e50b8777..0d088d50 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -289,11 +289,11 @@ private function createStreamCallback() $key = (int) $stream; if (Event::READ === (Event::READ & $flags) && isset($this->readListeners[$key])) { - call_user_func($this->readListeners[$key], $stream, $this); + call_user_func($this->readListeners[$key], $stream); } if (Event::WRITE === (Event::WRITE & $flags) && isset($this->writeListeners[$key])) { - call_user_func($this->writeListeners[$key], $stream, $this); + call_user_func($this->writeListeners[$key], $stream); } }; } diff --git a/src/LibEvLoop.php b/src/LibEvLoop.php index f6f0f490..3bbd8c4e 100644 --- a/src/LibEvLoop.php +++ b/src/LibEvLoop.php @@ -40,7 +40,7 @@ public function addReadStream($stream, callable $listener) } $callback = function () use ($stream, $listener) { - call_user_func($listener, $stream, $this); + call_user_func($listener, $stream); }; $event = new IOEvent($callback, $stream, IOEvent::READ); @@ -59,7 +59,7 @@ public function addWriteStream($stream, callable $listener) } $callback = function () use ($stream, $listener) { - call_user_func($listener, $stream, $this); + call_user_func($listener, $stream); }; $event = new IOEvent($callback, $stream, IOEvent::WRITE); diff --git a/src/LibEventLoop.php b/src/LibEventLoop.php index 4b252d45..ee648e7f 100644 --- a/src/LibEventLoop.php +++ b/src/LibEventLoop.php @@ -307,11 +307,11 @@ private function createStreamCallback() $key = (int) $stream; if (EV_READ === (EV_READ & $flags) && isset($this->readListeners[$key])) { - call_user_func($this->readListeners[$key], $stream, $this); + call_user_func($this->readListeners[$key], $stream); } if (EV_WRITE === (EV_WRITE & $flags) && isset($this->writeListeners[$key])) { - call_user_func($this->writeListeners[$key], $stream, $this); + call_user_func($this->writeListeners[$key], $stream); } }; } diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 053948aa..085c7703 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -206,7 +206,7 @@ private function waitForStreamActivity($timeout) $key = (int) $stream; if (isset($this->readListeners[$key])) { - call_user_func($this->readListeners[$key], $stream, $this); + call_user_func($this->readListeners[$key], $stream); } } @@ -214,7 +214,7 @@ private function waitForStreamActivity($timeout) $key = (int) $stream; if (isset($this->writeListeners[$key])) { - call_user_func($this->writeListeners[$key], $stream, $this); + call_user_func($this->writeListeners[$key], $stream); } } } diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index d2e3e078..247070e2 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -90,11 +90,11 @@ public function testSignalInterruptWithStream($signal) // add stream to the loop list($writeStream, $readStream) = $this->createSocketPair(); - $this->loop->addReadStream($readStream, function($stream, $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.05, function() use ($writeStream) { From 3a7ad74ffb7464aa4f226abc4fad64049b63acb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 13 Oct 2017 12:55:53 +0200 Subject: [PATCH 047/203] Documentation for removing stream listeners --- README.md | 9 +++++++++ src/LoopInterface.php | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/README.md b/README.md index edf4f596..32e9b9fb 100644 --- a/README.md +++ b/README.md @@ -358,16 +358,25 @@ the same time is not guaranteed. The `removeReadStream(resource $stream): void` method can be used to remove the read event listener for the given stream. +Removing a stream from the loop that has already been removed or trying +to remove a stream that was never added or is invalid has no effect. + ### removeWriteStream() The `removeWriteStream(resource $stream): void` method can be used to remove the write event listener for the given stream. +Removing a stream from the loop that has already been removed or trying +to remove a stream that was never added or is invalid has no effect. + ### removeStream() The `removeStream(resource $stream): void` method can be used to remove all listeners for the given stream. +Removing a stream from the loop that has already been removed or trying +to remove a stream that was never added or is invalid has no effect. + ## Install The recommended way to install this library is [through Composer](http://getcomposer.org). diff --git a/src/LoopInterface.php b/src/LoopInterface.php index 7bb7de59..2285f2be 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -77,6 +77,9 @@ public function addWriteStream($stream, callable $listener); /** * Remove the read event listener for the given stream. * + * Removing a stream from the loop that has already been removed or trying + * to remove a stream that was never added or is invalid has no effect. + * * @param resource $stream The PHP stream resource. */ public function removeReadStream($stream); @@ -84,6 +87,9 @@ public function removeReadStream($stream); /** * Remove the write event listener for the given stream. * + * Removing a stream from the loop that has already been removed or trying + * to remove a stream that was never added or is invalid has no effect. + * * @param resource $stream The PHP stream resource. */ public function removeWriteStream($stream); @@ -91,6 +97,9 @@ public function removeWriteStream($stream); /** * Remove all listeners for the given stream. * + * Removing a stream from the loop that has already been removed or trying + * to remove a stream that was never added or is invalid has no effect. + * * @param resource $stream The PHP stream resource. */ public function removeStream($stream); From 786846b7b9667f2020998d965c15602b1c6b4557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 13 Oct 2017 15:11:12 +0200 Subject: [PATCH 048/203] Documentation for stream resources --- README.md | 14 ++++++++++++++ src/LoopInterface.php | 16 ++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/README.md b/README.md index 32e9b9fb..2a3e4324 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,13 @@ See also [example #3](examples). The `addReadStream(resource $stream, callable $callback): void` method can be used to register a listener to be notified when a stream is ready to read. +The first parameter MUST be a valid stream resource that supports +checking whether it is ready to read by this loop implementation. +A single stream resource MUST NOT be added more than once. +Instead, either call [`removeReadStream()`](#removereadstream) first or +react to this event with a single listener and then dispatch from this +listener. + 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. @@ -327,6 +334,13 @@ the same time is not guaranteed. The `addWriteStream(resource $stream, callable $callback): void` method can be used to register a listener to be notified when a stream is ready to write. +The first parameter MUST be a valid stream resource that supports +checking whether it is ready to write by this loop implementation. +A single stream resource MUST NOT be added more than once. +Instead, either call [`removeWriteStream()`](#removewritestream) first or +react to this event with a single listener and then dispatch from this +listener. + 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. diff --git a/src/LoopInterface.php b/src/LoopInterface.php index 2285f2be..0a8c970f 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -9,6 +9,13 @@ interface LoopInterface /** * Register a listener to be notified when a stream is ready to read. * + * The first parameter MUST be a valid stream resource that supports + * checking whether it is ready to read by this loop implementation. + * A single stream resource MUST NOT be added more than once. + * Instead, either call [`removeReadStream()`](#removereadstream) first or + * react to this event with a single listener and then dispatch from this + * listener. + * * 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. @@ -37,12 +44,20 @@ interface LoopInterface * * @param resource $stream The PHP stream resource to check. * @param callable $listener Invoked when the stream is ready. + * @see self::removeReadStream() */ public function addReadStream($stream, callable $listener); /** * Register a listener to be notified when a stream is ready to write. * + * The first parameter MUST be a valid stream resource that supports + * checking whether it is ready to write by this loop implementation. + * A single stream resource MUST NOT be added more than once. + * Instead, either call [`removeWriteStream()`](#removewritestream) first or + * react to this event with a single listener and then dispatch from this + * listener. + * * 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. @@ -71,6 +86,7 @@ public function addReadStream($stream, callable $listener); * * @param resource $stream The PHP stream resource to check. * @param callable $listener Invoked when the stream is ready. + * @see self::removeWriteStream() */ public function addWriteStream($stream, callable $listener); From 8047bce82fd0a03a3f839b599d3ad72dad52478b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 13 Oct 2017 15:32:37 +0200 Subject: [PATCH 049/203] Mark stream API as advanced and link to Stream component instead --- README.md | 19 +++++++++++++++++++ src/LoopInterface.php | 14 ++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2a3e4324..3aa0cc45 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ For the code of the current stable 0.4.x release, checkout the * [Install](#install) * [Tests](#tests) * [License](#license) +* [More](#more) ## Quickstart example @@ -293,6 +294,11 @@ See also [example #3](examples). ### addReadStream() +> Advanced! Note that this low-level API is considered advanced usage. + Most use cases should probably use the higher-level + [readable Stream API](https://github.com/reactphp/stream#readablestreaminterface) + instead. + The `addReadStream(resource $stream, callable $callback): void` method can be used to register a listener to be notified when a stream is ready to read. @@ -331,6 +337,11 @@ the same time is not guaranteed. ### addWriteStream() +> Advanced! Note that this low-level API is considered advanced usage. + Most use cases should probably use the higher-level + [writable Stream API](https://github.com/reactphp/stream#writablestreaminterface) + instead. + The `addWriteStream(resource $stream, callable $callback): void` method can be used to register a listener to be notified when a stream is ready to write. @@ -420,3 +431,11 @@ $ php vendor/bin/phpunit ## License MIT, see [LICENSE file](LICENSE). + +## More + +* See our [Stream component](https://github.com/reactphp/stream) for more + information on how streams are used in real-world applications. +* See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the + [dependents on Packagist](https://packagist.org/packages/react/event-loop/dependents) + for a list of packages that use the EventLoop in real-world applications. diff --git a/src/LoopInterface.php b/src/LoopInterface.php index 0a8c970f..a4d394c0 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -7,7 +7,12 @@ interface LoopInterface { /** - * Register a listener to be notified when a stream is ready to read. + * [Advanced] Register a listener to be notified when a stream is ready to read. + * + * Note that this low-level API is considered advanced usage. + * Most use cases should probably use the higher-level + * [readable Stream API](https://github.com/reactphp/stream#readablestreaminterface) + * instead. * * The first parameter MUST be a valid stream resource that supports * checking whether it is ready to read by this loop implementation. @@ -49,7 +54,12 @@ interface LoopInterface public function addReadStream($stream, callable $listener); /** - * Register a listener to be notified when a stream is ready to write. + * [Advanced] Register a listener to be notified when a stream is ready to write. + * + * Note that this low-level API is considered advanced usage. + * Most use cases should probably use the higher-level + * [writable Stream API](https://github.com/reactphp/stream#writablestreaminterface) + * instead. * * The first parameter MUST be a valid stream resource that supports * checking whether it is ready to write by this loop implementation. From adf10794dce0f0f53a085bccd58d6a4d38167402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 13 Oct 2017 14:56:17 +0200 Subject: [PATCH 050/203] Make signal handling tests more robust and increase test timeouts --- tests/StreamSelectLoopTest.php | 46 ++++++++++++++++------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index d2e3e078..3dd82f01 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -47,8 +47,6 @@ public function signalProvider() ]; } - private $_signalHandled = false; - /** * Test signal interrupt when no stream is attached to the loop * @dataProvider signalProvider @@ -59,20 +57,24 @@ public function testSignalInterruptNoStream($signal) $this->markTestSkipped('"pcntl" extension is required to run this test.'); } - // dispatch signal handler once before signal is sent and once after - $this->loop->addTimer(0.01, function() { pcntl_signal_dispatch(); }); - $this->loop->addTimer(0.03, function() { pcntl_signal_dispatch(); }); - if (defined('HHVM_VERSION')) { - // hhvm startup is slow so we need to add another handler much later - $this->loop->addTimer(0.5, function() { pcntl_signal_dispatch(); }); - } + // dispatch signal handler every 10ms for 0.1s + $check = $this->loop->addPeriodicTimer(0.01, function() { + pcntl_signal_dispatch(); + }); + $this->loop->addTimer(0.1, function () use ($check) { + $this->loop->cancelTimer($check); + }); - $this->setUpSignalHandler($signal); + $handled = false; + $this->assertTrue(pcntl_signal(constant($signal), function () use (&$handled) { + $handled = true; + })); // spawn external process to send signal to current process id $this->forkSendSignal($signal); + $this->loop->run(); - $this->assertTrue($this->_signalHandled); + $this->assertTrue($handled); } /** @@ -86,7 +88,9 @@ public function testSignalInterruptWithStream($signal) } // dispatch signal handler every 10ms - $this->loop->addPeriodicTimer(0.01, function() { pcntl_signal_dispatch(); }); + $this->loop->addPeriodicTimer(0.01, function() { + pcntl_signal_dispatch(); + }); // add stream to the loop list($writeStream, $readStream) = $this->createSocketPair(); @@ -97,27 +101,21 @@ public function testSignalInterruptWithStream($signal) $loop->stop(); } }); - $this->loop->addTimer(0.05, function() use ($writeStream) { + $this->loop->addTimer(0.1, function() use ($writeStream) { fwrite($writeStream, "end loop\n"); }); - $this->setUpSignalHandler($signal); + $handled = false; + $this->assertTrue(pcntl_signal(constant($signal), function () use (&$handled) { + $handled = true; + })); // spawn external process to send signal to current process id $this->forkSendSignal($signal); $this->loop->run(); - $this->assertTrue($this->_signalHandled); - } - - /** - * add signal handler for signal - */ - protected function setUpSignalHandler($signal) - { - $this->_signalHandled = false; - $this->assertTrue(pcntl_signal(constant($signal), function() { $this->_signalHandled = true; })); + $this->assertTrue($handled); } /** From a2d261e096a416bb587498a4c15104f284cacf1c Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 31 Oct 2017 18:45:06 +0100 Subject: [PATCH 051/203] Change suggests from version to for which loop they are --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 5001a9c8..e5fca202 100644 --- a/composer.json +++ b/composer.json @@ -10,9 +10,9 @@ "phpunit/phpunit": "~4.8" }, "suggest": { - "ext-libevent": ">=0.1.0", - "ext-event": "~1.0", - "ext-libev": "*" + "ext-libevent": ">=0.1.0 for LibEventLoop", + "ext-event": "~1.0 for ExtEventLoop", + "ext-libev": "for LibEvLoop" }, "autoload": { "psr-4": { From ac898d2b0872ff0de9108c0f5c21785f46ba303c Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 31 Oct 2017 22:48:42 +0100 Subject: [PATCH 052/203] Added @cboden's suggestion of adding "and PHP5 only" to the LibEventLoop suggest --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e5fca202..f48f6eaf 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ "phpunit/phpunit": "~4.8" }, "suggest": { - "ext-libevent": ">=0.1.0 for LibEventLoop", + "ext-libevent": ">=0.1.0 for LibEventLoop and PHP5 only", "ext-event": "~1.0 for ExtEventLoop", "ext-libev": "for LibEvLoop" }, From f201956a17d3cc5ff7492ea6e6196819c8f32d03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 10 Nov 2017 16:39:15 +0100 Subject: [PATCH 053/203] Remove removeStream() method, use removeReadStream/removeWriteStream --- README.md | 10 +--------- examples/21-http-server.php | 2 +- src/ExtEventLoop.php | 5 +---- src/LibEvLoop.php | 9 --------- src/LibEventLoop.php | 5 +---- src/LoopInterface.php | 10 ---------- src/StreamSelectLoop.php | 9 --------- tests/AbstractLoopTest.php | 33 ++------------------------------- 8 files changed, 6 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 3aa0cc45..a8c300d1 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ $loop->addReadStream($server, function ($server) use ($loop) { $written = fwrite($conn, $data); if ($written === strlen($data)) { fclose($conn); - $loop->removeStream($conn); + $loop->removeWriteStream($conn); } else { $data = substr($data, $written); } @@ -394,14 +394,6 @@ remove the write event listener for the given stream. Removing a stream from the loop that has already been removed or trying to remove a stream that was never added or is invalid has no effect. -### removeStream() - -The `removeStream(resource $stream): void` method can be used to -remove all listeners for the given stream. - -Removing a stream from the loop that has already been removed or trying -to remove a stream that was never added or is invalid has no effect. - ## Install The recommended way to install this library is [through Composer](http://getcomposer.org). diff --git a/examples/21-http-server.php b/examples/21-http-server.php index 67423957..66180de1 100644 --- a/examples/21-http-server.php +++ b/examples/21-http-server.php @@ -14,7 +14,7 @@ $written = fwrite($conn, $data); if ($written === strlen($data)) { fclose($conn); - $loop->removeStream($conn); + $loop->removeWriteStream($conn); } else { $data = substr($data, $written); } diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 0d088d50..1e246b16 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -88,10 +88,7 @@ public function removeWriteStream($stream) } } - /** - * {@inheritdoc} - */ - public function removeStream($stream) + private function removeStream($stream) { $key = (int) $stream; diff --git a/src/LibEvLoop.php b/src/LibEvLoop.php index 3bbd8c4e..8d03544b 100644 --- a/src/LibEvLoop.php +++ b/src/LibEvLoop.php @@ -94,15 +94,6 @@ public function removeWriteStream($stream) } } - /** - * {@inheritdoc} - */ - public function removeStream($stream) - { - $this->removeReadStream($stream); - $this->removeWriteStream($stream); - } - /** * {@inheritdoc} */ diff --git a/src/LibEventLoop.php b/src/LibEventLoop.php index ee648e7f..2ec0d1cc 100644 --- a/src/LibEventLoop.php +++ b/src/LibEventLoop.php @@ -89,10 +89,7 @@ public function removeWriteStream($stream) } } - /** - * {@inheritdoc} - */ - public function removeStream($stream) + private function removeStream($stream) { $key = (int) $stream; diff --git a/src/LoopInterface.php b/src/LoopInterface.php index a4d394c0..578567cf 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -120,16 +120,6 @@ public function removeReadStream($stream); */ public function removeWriteStream($stream); - /** - * Remove all listeners for the given stream. - * - * Removing a stream from the loop that has already been removed or trying - * to remove a stream that was never added or is invalid has no effect. - * - * @param resource $stream The PHP stream resource. - */ - public function removeStream($stream); - /** * Enqueue a callback to be invoked once after the given interval. * diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 085c7703..fa5fae78 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -80,15 +80,6 @@ public function removeWriteStream($stream) ); } - /** - * {@inheritdoc} - */ - public function removeStream($stream) - { - $this->removeReadStream($stream); - $this->removeWriteStream($stream); - } - /** * {@inheritdoc} */ diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 2470e555..832f3906 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -126,18 +126,6 @@ public function testRemoveWriteStreamAfterWriting() $this->tickLoop($this->loop); } - public function testRemoveStreamInstantly() - { - list ($input, $output) = $this->createSocketPair(); - - $this->loop->addReadStream($input, $this->expectCallableNever()); - $this->loop->addWriteStream($input, $this->expectCallableNever()); - $this->loop->removeStream($input); - - fwrite($output, "bar\n"); - $this->tickLoop($this->loop); - } - public function testRemoveStreamForReadOnly() { list ($input, $output) = $this->createSocketPair(); @@ -163,22 +151,6 @@ public function testRemoveStreamForWriteOnly() $this->tickLoop($this->loop); } - public function testRemoveStream() - { - list ($input, $output) = $this->createSocketPair(); - - $this->loop->addReadStream($input, $this->expectCallableOnce()); - $this->loop->addWriteStream($input, $this->expectCallableOnce()); - - fwrite($output, "bar\n"); - $this->tickLoop($this->loop); - - $this->loop->removeStream($input); - - fwrite($output, "bar\n"); - $this->tickLoop($this->loop); - } - public function testRemoveInvalid() { list ($stream) = $this->createSocketPair(); @@ -186,7 +158,6 @@ public function testRemoveInvalid() // remove a valid stream from the event loop that was never added in the first place $this->loop->removeReadStream($stream); $this->loop->removeWriteStream($stream); - $this->loop->removeStream($stream); } /** @test */ @@ -202,7 +173,7 @@ public function runShouldReturnWhenNoMoreFds() $loop = $this->loop; $this->loop->addReadStream($input, function ($stream) use ($loop) { - $loop->removeStream($stream); + $loop->removeReadStream($stream); }); fwrite($output, "foo\n"); @@ -366,7 +337,7 @@ public function testRunWaitsForFutureTickEvents() $this->loop->addWriteStream( $stream, function () use ($stream) { - $this->loop->removeStream($stream); + $this->loop->removeWriteStream($stream); $this->loop->futureTick( function () { echo 'future-tick' . PHP_EOL; From f8ad0169f652fedd6ad14908da1184fcd3a18685 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 24 Jul 2017 17:18:40 +0200 Subject: [PATCH 054/203] Support for signal handling --- README.md | 43 ++++++++++++++++ composer.json | 3 +- examples/04-signals.php | 12 +++++ src/ExtEventLoop.php | 39 +++++++++++++++ src/LibEvLoop.php | 40 +++++++++++++++ src/LibEventLoop.php | 43 ++++++++++++++++ src/LoopInterface.php | 35 +++++++++++++ src/SignalsHandler.php | 97 ++++++++++++++++++++++++++++++++++++ src/StreamSelectLoop.php | 61 +++++++++++++++++++++++ tests/AbstractLoopTest.php | 90 +++++++++++++++++++++++++++++++++ tests/SignalsHandlerTest.php | 92 ++++++++++++++++++++++++++++++++++ tests/bootstrap.php | 8 +++ 12 files changed, 562 insertions(+), 1 deletion(-) create mode 100644 examples/04-signals.php create mode 100644 src/SignalsHandler.php create mode 100644 tests/SignalsHandlerTest.php diff --git a/README.md b/README.md index 3aa0cc45..e3fa8d2c 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,49 @@ echo 'a'; See also [example #3](examples). +### addSignal() + +The `addSignal(int $signal, callable $listener): void` method can be used to +be notified about OS signals. This is useful to catch user interrupt signals or +shutdown signals from tools like `supervisor` or `systemd`. + +The listener callback function MUST be able to accept a single parameter, +the signal added by this method or you MAY use a function which +has no parameters at all. + +The listener callback function MUST NOT throw an `Exception`. +The return value of the listener callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +```php +$listener = function (int $signal) { + echo 'Caught user iterrupt signal', PHP_EOL; +}; +$loop->addSignal(SIGINT, $listener); +``` + +See also [example #4](examples). + +**Note: A listener can only be added once to the same signal, any attempts to add it +more then once will be ignored.** + +**Note: Signaling is only available on Unix-like platform, Windows isn't supported due +to limitations from underlying signal handlers.** + +### removeSignal() + +The `removeSignal(int $signal, callable $listener): void` removes a previously added +signal listener. + +Any attempts to remove listeners that aren't registerred will be ignored. + +```php +$loop->removeSignal(SIGINT, $listener); +``` + +See also [example #4](examples). + ### addReadStream() > Advanced! Note that this low-level API is considered advanced usage. diff --git a/composer.json b/composer.json index f48f6eaf..67060367 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ "suggest": { "ext-libevent": ">=0.1.0 for LibEventLoop and PHP5 only", "ext-event": "~1.0 for ExtEventLoop", - "ext-libev": "for LibEvLoop" + "ext-libev": "for LibEvLoop", + "ext-pcntl": "For signals support when using the stream_select loop" }, "autoload": { "psr-4": { diff --git a/examples/04-signals.php b/examples/04-signals.php new file mode 100644 index 00000000..4e03e4b7 --- /dev/null +++ b/examples/04-signals.php @@ -0,0 +1,12 @@ +addSignal(SIGINT, $func = function ($signal) use ($loop, &$func) { + echo 'Signal: ', (string)$signal, PHP_EOL; + $loop->removeSignal(SIGINT, $func); +}); + +$loop->run(); diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 0d088d50..25797678 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -25,6 +25,8 @@ class ExtEventLoop implements LoopInterface private $readListeners = []; private $writeListeners = []; private $running; + private $signals; + private $signalEvents = []; public function __construct(EventBaseConfig $config = null) { @@ -32,6 +34,27 @@ public function __construct(EventBaseConfig $config = null) $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); + $this->signals = new SignalsHandler( + $this, + function ($signal) { + $this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, $f = function () use ($signal, &$f) { + $this->signals->call($signal); + // Ensure there are two copies of the callable around until it has been executed. + // For more information see: https://bugs.php.net/bug.php?id=62452 + // Only an issue for PHP 5, this hack can be removed once PHP 5 suppose has been dropped. + $g = $f; + $f = $g; + }); + $this->signalEvents[$signal]->add(); + }, + function ($signal) { + if ($this->signals->count($signal) === 0) { + $this->signalEvents[$signal]->del(); + unset($this->signalEvents[$signal]); + } + } + ); + $this->createTimerCallback(); $this->createStreamCallback(); } @@ -158,6 +181,22 @@ public function futureTick(callable $listener) $this->futureTickQueue->add($listener); } + /** + * {@inheritdoc} + */ + public function addSignal($signal, callable $listener) + { + $this->signals->add($signal, $listener); + } + + /** + * {@inheritdoc} + */ + public function removeSignal($signal, callable $listener) + { + $this->signals->remove($signal, $listener); + } + /** * {@inheritdoc} */ diff --git a/src/LibEvLoop.php b/src/LibEvLoop.php index 3bbd8c4e..ad282820 100644 --- a/src/LibEvLoop.php +++ b/src/LibEvLoop.php @@ -4,6 +4,7 @@ use libev\EventLoop; use libev\IOEvent; +use libev\SignalEvent; use libev\TimerEvent; use React\EventLoop\Tick\FutureTickQueue; use React\EventLoop\Timer\Timer; @@ -22,12 +23,35 @@ class LibEvLoop implements LoopInterface private $readEvents = []; private $writeEvents = []; private $running; + private $signals; + private $signalEvents = []; public function __construct() { $this->loop = new EventLoop(); $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); + + $this->signals = new SignalsHandler( + $this, + function ($signal) { + $this->signalEvents[$signal] = new SignalEvent($f = function () use ($signal, &$f) { + $this->signals->call($signal); + // Ensure there are two copies of the callable around until it has been executed. + // For more information see: https://bugs.php.net/bug.php?id=62452 + // Only an issue for PHP 5, this hack can be removed once PHP 5 suppose has been dropped. + $g = $f; + $f = $g; + }, $signal); + $this->loop->add($this->signalEvents[$signal]); + }, + function ($signal) { + if ($this->signals->count($signal) === 0) { + $this->loop->remove($this->signalEvents[$signal]); + unset($this->signalEvents[$signal]); + } + } + ); } /** @@ -170,6 +194,22 @@ public function futureTick(callable $listener) $this->futureTickQueue->add($listener); } + /** + * {@inheritdoc} + */ + public function addSignal($signal, callable $listener) + { + $this->signals->add($signal, $listener); + } + + /** + * {@inheritdoc} + */ + public function removeSignal($signal, callable $listener) + { + $this->signals->remove($signal, $listener); + } + /** * {@inheritdoc} */ diff --git a/src/LibEventLoop.php b/src/LibEventLoop.php index ee648e7f..299243c2 100644 --- a/src/LibEventLoop.php +++ b/src/LibEventLoop.php @@ -4,6 +4,7 @@ use Event; use EventBase; +use React\EventLoop\Signal\Pcntl; use React\EventLoop\Tick\FutureTickQueue; use React\EventLoop\Timer\Timer; use React\EventLoop\Timer\TimerInterface; @@ -26,6 +27,8 @@ class LibEventLoop implements LoopInterface private $readListeners = []; private $writeListeners = []; private $running; + private $signals; + private $signalEvents = []; public function __construct() { @@ -33,6 +36,30 @@ public function __construct() $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); + $this->signals = new SignalsHandler( + $this, + function ($signal) { + $this->signalEvents[$signal] = event_new(); + event_set($this->signalEvents[$signal], $signal, EV_PERSIST | EV_SIGNAL, $f = function () use ($signal, &$f) { + $this->signals->call($signal); + // Ensure there are two copies of the callable around until it has been executed. + // For more information see: https://bugs.php.net/bug.php?id=62452 + // Only an issue for PHP 5, this hack can be removed once PHP 5 suppose has been dropped. + $g = $f; + $f = $g; + }); + event_base_set($this->signalEvents[$signal], $this->eventBase); + event_add($this->signalEvents[$signal]); + }, + function ($signal) { + if ($this->signals->count($signal) === 0) { + event_del($this->signalEvents[$signal]); + event_free($this->signalEvents[$signal]); + unset($this->signalEvents[$signal]); + } + } + ); + $this->createTimerCallback(); $this->createStreamCallback(); } @@ -166,6 +193,22 @@ public function futureTick(callable $listener) $this->futureTickQueue->add($listener); } + /** + * {@inheritdoc} + */ + public function addSignal($signal, callable $listener) + { + $this->signals->add($signal, $listener); + } + + /** + * {@inheritdoc} + */ + public function removeSignal($signal, callable $listener) + { + $this->signals->remove($signal, $listener); + } + /** * {@inheritdoc} */ diff --git a/src/LoopInterface.php b/src/LoopInterface.php index a4d394c0..595209de 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -326,6 +326,41 @@ public function isTimerActive(TimerInterface $timer); */ public function futureTick(callable $listener); + /** + * Registers a signal listener with the loop, which + * on it's turn registers it with a signal handler + * suitable for the loop implementation. + * + * A listener can only be added once, any attempts + * to add it again will be ignored. + * + * See also [example #4](examples). + * + * @param int $signal + * @param callable $listener + * + * @throws \BadMethodCallException when signals + * aren't supported by the loop, e.g. when required + * extensions are missing. + * + * @return void + */ + public function addSignal($signal, callable $listener); + + /** + * Removed previous registered signal listener from + * the loop, which on it's turn removes it from the + * underlying signal handler. + * + * See also [example #4](examples). + * + * @param int $signal + * @param callable $listener + * + * @return void + */ + public function removeSignal($signal, callable $listener); + /** * Run the event loop until there are no more tasks to perform. */ diff --git a/src/SignalsHandler.php b/src/SignalsHandler.php new file mode 100644 index 00000000..c91bf1e2 --- /dev/null +++ b/src/SignalsHandler.php @@ -0,0 +1,97 @@ +loop = $loop; + $this->on = $on; + $this->off = $off; + } + + public function __destruct() + { + $off = $this->off; + foreach ($this->signals as $signal => $listeners) { + $off($signal); + } + } + + public function add($signal, callable $listener) + { + if (count($this->signals) == 0 && $this->timer === null) { + /** + * Timer to keep the loop alive as long as there are any signal handlers registered + */ + $this->timer = $this->loop->addPeriodicTimer(300, function () {}); + } + + if (!isset($this->signals[$signal])) { + $this->signals[$signal] = []; + + $on = $this->on; + $on($signal); + } + + if (in_array($listener, $this->signals[$signal])) { + return; + } + + $this->signals[$signal][] = $listener; + } + + public function remove($signal, callable $listener) + { + if (!isset($this->signals[$signal])) { + return; + } + + $index = \array_search($listener, $this->signals[$signal], true); + unset($this->signals[$signal][$index]); + + if (isset($this->signals[$signal]) && \count($this->signals[$signal]) === 0) { + unset($this->signals[$signal]); + + $off = $this->off; + $off($signal); + } + + if (count($this->signals) == 0 && $this->timer instanceof TimerInterface) { + $this->loop->cancelTimer($this->timer); + $this->timer = null; + } + } + + public function call($signal) + { + if (!isset($this->signals[$signal])) { + return; + } + + foreach ($this->signals[$signal] as $listener) { + \call_user_func($listener, $signal); + } + } + + public function count($signal) + { + if (!isset($this->signals[$signal])) { + return 0; + } + + return \count($this->signals[$signal]); + } +} diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 085c7703..18de6218 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -2,6 +2,7 @@ namespace React\EventLoop; +use React\EventLoop\Signal\Pcntl; use React\EventLoop\Tick\FutureTickQueue; use React\EventLoop\Timer\Timer; use React\EventLoop\Timer\TimerInterface; @@ -21,11 +22,32 @@ class StreamSelectLoop implements LoopInterface private $writeStreams = []; private $writeListeners = []; private $running; + private $pcntl = false; + private $signals; public function __construct() { $this->futureTickQueue = new FutureTickQueue(); $this->timers = new Timers(); + $this->pcntl = extension_loaded('pcntl'); + $this->signals = new SignalsHandler( + $this, + function ($signal) { + \pcntl_signal($signal, $f = function ($signal) use (&$f) { + $this->signals->call($signal); + // Ensure there are two copies of the callable around until it has been executed. + // For more information see: https://bugs.php.net/bug.php?id=62452 + // Only an issue for PHP 5, this hack can be removed once PHP 5 suppose has been dropped. + $g = $f; + $f = $g; + }); + }, + function ($signal) { + if ($this->signals->count($signal) === 0) { + \pcntl_signal($signal, SIG_DFL); + } + } + ); } /** @@ -137,6 +159,26 @@ public function futureTick(callable $listener) $this->futureTickQueue->add($listener); } + /** + * {@inheritdoc} + */ + public function addSignal($signal, callable $listener) + { + if ($this->pcntl === false) { + throw new \BadMethodCallException('Event loop feature "signals" isn\'t supported by the "StreamSelectLoop"'); + } + + $this->signals->add($signal, $listener); + } + + /** + * {@inheritdoc} + */ + public function removeSignal($signal, callable $listener) + { + $this->signals->remove($signal, $listener); + } + /** * {@inheritdoc} */ @@ -196,6 +238,9 @@ private function waitForStreamActivity($timeout) $write = $this->writeStreams; $available = $this->streamSelect($read, $write, $timeout); + if ($this->pcntl) { + \pcntl_signal_dispatch(); + } if (false === $available) { // if a system call has been interrupted, // we cannot rely on it's outcome @@ -243,4 +288,20 @@ protected function streamSelect(array &$read, array &$write, $timeout) return 0; } + + /** + * Iterate over signal listeners for the given signal + * and call each of them with the signal as first + * argument. + * + * @param int $signal + * + * @return void + */ + private function handleSignal($signal) + { + foreach ($this->signals[$signal] as $listener) { + \call_user_func($listener, $signal); + } + } } diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 2470e555..69bf279f 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -398,6 +398,96 @@ function () { $this->loop->run(); } + public function testSignal() + { + if (!function_exists('posix_kill') || !function_exists('posix_getpid')) { + $this->markTestSkipped('Signal test skipped because functions "posix_kill" and "posix_getpid" are missing.'); + } + + $called = false; + $calledShouldNot = true; + + $timer = $this->loop->addPeriodicTimer(1, function () {}); + + $this->loop->addSignal(SIGUSR2, $func2 = function () use (&$calledShouldNot) { + $calledShouldNot = false; + }); + + $this->loop->addSignal(SIGUSR1, $func1 = function () use (&$func1, &$func2, &$called, $timer) { + $called = true; + $this->loop->removeSignal(SIGUSR1, $func1); + $this->loop->removeSignal(SIGUSR2, $func2); + $this->loop->cancelTimer($timer); + }); + + $this->loop->futureTick(function () { + posix_kill(posix_getpid(), SIGUSR1); + }); + + $this->loop->run(); + + $this->assertTrue($called); + $this->assertTrue($calledShouldNot); + } + + public function testSignalMultipleUsagesForTheSameListener() + { + $funcCallCount = 0; + $func = function () use (&$funcCallCount) { + $funcCallCount++; + }; + $this->loop->addTimer(1, function () {}); + + $this->loop->addSignal(SIGUSR1, $func); + $this->loop->addSignal(SIGUSR1, $func); + + $this->loop->addTimer(0.4, function () { + posix_kill(posix_getpid(), SIGUSR1); + }); + $this->loop->addTimer(0.9, function () use (&$func) { + $this->loop->removeSignal(SIGUSR1, $func); + }); + + $this->loop->run(); + + $this->assertSame(1, $funcCallCount); + } + + public function testSignalsKeepTheLoopRunning() + { + $function = function () {}; + $this->loop->addSignal(SIGUSR1, $function); + $this->loop->addTimer(1.5, function () use ($function) { + $this->loop->removeSignal(SIGUSR1, $function); + $this->loop->stop(); + }); + + $this->assertRunSlowerThan(1.5); + } + + public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop() + { + $function = function () {}; + $this->loop->addSignal(SIGUSR1, $function); + $this->loop->addTimer(1.5, function () use ($function) { + $this->loop->removeSignal(SIGUSR1, $function); + }); + + $this->assertRunFasterThan(1.6); + } + + private function assertRunSlowerThan($minInterval) + { + $start = microtime(true); + + $this->loop->run(); + + $end = microtime(true); + $interval = $end - $start; + + $this->assertLessThan($interval, $minInterval); + } + private function assertRunFasterThan($maxInterval) { $start = microtime(true); diff --git a/tests/SignalsHandlerTest.php b/tests/SignalsHandlerTest.php new file mode 100644 index 00000000..04f37151 --- /dev/null +++ b/tests/SignalsHandlerTest.php @@ -0,0 +1,92 @@ +assertSame(0, $callCount); + $this->assertSame(0, $onCount); + $this->assertSame(0, $offCount); + + $signals->add(SIGUSR1, $func); + $this->assertSame(0, $callCount); + $this->assertSame(1, $onCount); + $this->assertSame(0, $offCount); + + $signals->add(SIGUSR1, $func); + $this->assertSame(0, $callCount); + $this->assertSame(1, $onCount); + $this->assertSame(0, $offCount); + + $signals->add(SIGUSR1, $func); + $this->assertSame(0, $callCount); + $this->assertSame(1, $onCount); + $this->assertSame(0, $offCount); + + $signals->call(SIGUSR1); + $this->assertSame(1, $callCount); + $this->assertSame(1, $onCount); + $this->assertSame(0, $offCount); + + $signals->add(SIGUSR2, $func); + $this->assertSame(1, $callCount); + $this->assertSame(2, $onCount); + $this->assertSame(0, $offCount); + + $signals->add(SIGUSR2, $func); + $this->assertSame(1, $callCount); + $this->assertSame(2, $onCount); + $this->assertSame(0, $offCount); + + $signals->call(SIGUSR2); + $this->assertSame(2, $callCount); + $this->assertSame(2, $onCount); + $this->assertSame(0, $offCount); + + $signals->remove(SIGUSR2, $func); + $this->assertSame(2, $callCount); + $this->assertSame(2, $onCount); + $this->assertSame(1, $offCount); + + $signals->remove(SIGUSR2, $func); + $this->assertSame(2, $callCount); + $this->assertSame(2, $onCount); + $this->assertSame(1, $offCount); + + $signals->call(SIGUSR2); + $this->assertSame(2, $callCount); + $this->assertSame(2, $onCount); + $this->assertSame(1, $offCount); + + $signals->remove(SIGUSR1, $func); + $this->assertSame(2, $callCount); + $this->assertSame(2, $onCount); + $this->assertSame(2, $offCount); + + $signals->call(SIGUSR1); + $this->assertSame(2, $callCount); + $this->assertSame(2, $onCount); + $this->assertSame(2, $offCount); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index d97d8b77..ea7dd4cc 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -5,3 +5,11 @@ $loader = require __DIR__ . '/../../../../vendor/autoload.php'; } $loader->addPsr4('React\\Tests\\EventLoop\\', __DIR__); + +if (!defined('SIGUSR1')) { + define('SIGUSR1', 1); +} + +if (!defined('SIGUSR2')) { + define('SIGUSR2', 2); +} From e360c730904e48581447d8fe11da9148c97c20e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 10 Nov 2017 19:23:57 +0100 Subject: [PATCH 055/203] Documentation for Factory --- README.md | 17 +++++++++++++++++ src/Factory.php | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/README.md b/README.md index 9795f2f9..03a93a12 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,23 @@ $loop->run(); purposes. 3. The loop is run with a single `$loop->run()` call at the end of the program. +## Factory + +The `Factory` class exists as a convenient way to pick the best available +[loop implementation)(#loop-implementation). + +The `create(): LoopInterface` method can be used to create a new loop +instance: + +```php +$loop = React\EventLoop\Factory::create(); +``` + +This method always returns an instance implementing `LoopInterface`, +the actual loop implementation is an implementation detail. + +This method should usually only be called once at the beginning of the program. + ## Loop implementations In addition to the interface there are the following implementations provided: diff --git a/src/Factory.php b/src/Factory.php index 271c2654..7c87180c 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -2,8 +2,25 @@ namespace React\EventLoop; +/** + * The `Factory` class exists as a convenient way to pick the best available loop implementation. + */ class Factory { + /** + * Creates a new loop instance + * + * ```php + * $loop = React\EventLoop\Factory::create(); + * ``` + * + * This method always returns an instance implementing `LoopInterface`, + * the actual loop implementation is an implementation detail. + * + * This method should usually only be called once at the beginning of the program. + * + * @return LoopInterface + */ public static function create() { // @codeCoverageIgnoreStart From f4e932250aec05207db7b24e1986aef54942edf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 10 Nov 2017 19:34:30 +0100 Subject: [PATCH 056/203] Remove unneeded "{@inheritdoc}" annotation --- src/ExtEventLoop.php | 39 --------------------------------------- src/LibEvLoop.php | 39 --------------------------------------- src/LibEventLoop.php | 40 ---------------------------------------- src/StreamSelectLoop.php | 39 --------------------------------------- src/Timer/Timer.php | 9 --------- 5 files changed, 166 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index f192025e..b1cb3e74 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -59,9 +59,6 @@ function ($signal) { $this->createStreamCallback(); } - /** - * {@inheritdoc} - */ public function addReadStream($stream, callable $listener) { $key = (int) $stream; @@ -72,9 +69,6 @@ public function addReadStream($stream, callable $listener) } } - /** - * {@inheritdoc} - */ public function addWriteStream($stream, callable $listener) { $key = (int) $stream; @@ -85,9 +79,6 @@ public function addWriteStream($stream, callable $listener) } } - /** - * {@inheritdoc} - */ public function removeReadStream($stream) { $key = (int) $stream; @@ -98,9 +89,6 @@ public function removeReadStream($stream) } } - /** - * {@inheritdoc} - */ public function removeWriteStream($stream) { $key = (int) $stream; @@ -127,9 +115,6 @@ private function removeStream($stream) } } - /** - * {@inheritdoc} - */ public function addTimer($interval, callable $callback) { $timer = new Timer($interval, $callback, false); @@ -139,9 +124,6 @@ public function addTimer($interval, callable $callback) return $timer; } - /** - * {@inheritdoc} - */ public function addPeriodicTimer($interval, callable $callback) { $timer = new Timer($interval, $callback, true); @@ -151,9 +133,6 @@ public function addPeriodicTimer($interval, callable $callback) return $timer; } - /** - * {@inheritdoc} - */ public function cancelTimer(TimerInterface $timer) { if ($this->isTimerActive($timer)) { @@ -162,41 +141,26 @@ public function cancelTimer(TimerInterface $timer) } } - /** - * {@inheritdoc} - */ public function isTimerActive(TimerInterface $timer) { return $this->timerEvents->contains($timer); } - /** - * {@inheritdoc} - */ public function futureTick(callable $listener) { $this->futureTickQueue->add($listener); } - /** - * {@inheritdoc} - */ public function addSignal($signal, callable $listener) { $this->signals->add($signal, $listener); } - /** - * {@inheritdoc} - */ public function removeSignal($signal, callable $listener) { $this->signals->remove($signal, $listener); } - /** - * {@inheritdoc} - */ public function run() { $this->running = true; @@ -215,9 +179,6 @@ public function run() } } - /** - * {@inheritdoc} - */ public function stop() { $this->running = false; diff --git a/src/LibEvLoop.php b/src/LibEvLoop.php index 7c9b57be..638d9fa9 100644 --- a/src/LibEvLoop.php +++ b/src/LibEvLoop.php @@ -54,9 +54,6 @@ function ($signal) { ); } - /** - * {@inheritdoc} - */ public function addReadStream($stream, callable $listener) { if (isset($this->readEvents[(int) $stream])) { @@ -73,9 +70,6 @@ public function addReadStream($stream, callable $listener) $this->readEvents[(int) $stream] = $event; } - /** - * {@inheritdoc} - */ public function addWriteStream($stream, callable $listener) { if (isset($this->writeEvents[(int) $stream])) { @@ -92,9 +86,6 @@ public function addWriteStream($stream, callable $listener) $this->writeEvents[(int) $stream] = $event; } - /** - * {@inheritdoc} - */ public function removeReadStream($stream) { $key = (int) $stream; @@ -105,9 +96,6 @@ public function removeReadStream($stream) } } - /** - * {@inheritdoc} - */ public function removeWriteStream($stream) { $key = (int) $stream; @@ -118,9 +106,6 @@ public function removeWriteStream($stream) } } - /** - * {@inheritdoc} - */ public function addTimer($interval, callable $callback) { $timer = new Timer( $interval, $callback, false); @@ -140,9 +125,6 @@ public function addTimer($interval, callable $callback) return $timer; } - /** - * {@inheritdoc} - */ public function addPeriodicTimer($interval, callable $callback) { $timer = new Timer($interval, $callback, true); @@ -158,9 +140,6 @@ public function addPeriodicTimer($interval, callable $callback) return $timer; } - /** - * {@inheritdoc} - */ public function cancelTimer(TimerInterface $timer) { if (isset($this->timerEvents[$timer])) { @@ -169,41 +148,26 @@ public function cancelTimer(TimerInterface $timer) } } - /** - * {@inheritdoc} - */ public function isTimerActive(TimerInterface $timer) { return $this->timerEvents->contains($timer); } - /** - * {@inheritdoc} - */ public function futureTick(callable $listener) { $this->futureTickQueue->add($listener); } - /** - * {@inheritdoc} - */ public function addSignal($signal, callable $listener) { $this->signals->add($signal, $listener); } - /** - * {@inheritdoc} - */ public function removeSignal($signal, callable $listener) { $this->signals->remove($signal, $listener); } - /** - * {@inheritdoc} - */ public function run() { $this->running = true; @@ -222,9 +186,6 @@ public function run() } } - /** - * {@inheritdoc} - */ public function stop() { $this->running = false; diff --git a/src/LibEventLoop.php b/src/LibEventLoop.php index fa35fa43..3b49e2e2 100644 --- a/src/LibEventLoop.php +++ b/src/LibEventLoop.php @@ -4,7 +4,6 @@ use Event; use EventBase; -use React\EventLoop\Signal\Pcntl; use React\EventLoop\Tick\FutureTickQueue; use React\EventLoop\Timer\Timer; use React\EventLoop\Timer\TimerInterface; @@ -64,9 +63,6 @@ function ($signal) { $this->createStreamCallback(); } - /** - * {@inheritdoc} - */ public function addReadStream($stream, callable $listener) { $key = (int) $stream; @@ -77,9 +73,6 @@ public function addReadStream($stream, callable $listener) } } - /** - * {@inheritdoc} - */ public function addWriteStream($stream, callable $listener) { $key = (int) $stream; @@ -90,9 +83,6 @@ public function addWriteStream($stream, callable $listener) } } - /** - * {@inheritdoc} - */ public function removeReadStream($stream) { $key = (int) $stream; @@ -103,9 +93,6 @@ public function removeReadStream($stream) } } - /** - * {@inheritdoc} - */ public function removeWriteStream($stream) { $key = (int) $stream; @@ -135,9 +122,6 @@ private function removeStream($stream) } } - /** - * {@inheritdoc} - */ public function addTimer($interval, callable $callback) { $timer = new Timer($interval, $callback, false); @@ -147,9 +131,6 @@ public function addTimer($interval, callable $callback) return $timer; } - /** - * {@inheritdoc} - */ public function addPeriodicTimer($interval, callable $callback) { $timer = new Timer($interval, $callback, true); @@ -159,9 +140,6 @@ public function addPeriodicTimer($interval, callable $callback) return $timer; } - /** - * {@inheritdoc} - */ public function cancelTimer(TimerInterface $timer) { if ($this->isTimerActive($timer)) { @@ -174,41 +152,26 @@ public function cancelTimer(TimerInterface $timer) } } - /** - * {@inheritdoc} - */ public function isTimerActive(TimerInterface $timer) { return $this->timerEvents->contains($timer); } - /** - * {@inheritdoc} - */ public function futureTick(callable $listener) { $this->futureTickQueue->add($listener); } - /** - * {@inheritdoc} - */ public function addSignal($signal, callable $listener) { $this->signals->add($signal, $listener); } - /** - * {@inheritdoc} - */ public function removeSignal($signal, callable $listener) { $this->signals->remove($signal, $listener); } - /** - * {@inheritdoc} - */ public function run() { $this->running = true; @@ -227,9 +190,6 @@ public function run() } } - /** - * {@inheritdoc} - */ public function stop() { $this->running = false; diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 1d151829..4273f24e 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -50,9 +50,6 @@ function ($signal) { ); } - /** - * {@inheritdoc} - */ public function addReadStream($stream, callable $listener) { $key = (int) $stream; @@ -63,9 +60,6 @@ public function addReadStream($stream, callable $listener) } } - /** - * {@inheritdoc} - */ public function addWriteStream($stream, callable $listener) { $key = (int) $stream; @@ -76,9 +70,6 @@ public function addWriteStream($stream, callable $listener) } } - /** - * {@inheritdoc} - */ public function removeReadStream($stream) { $key = (int) $stream; @@ -89,9 +80,6 @@ public function removeReadStream($stream) ); } - /** - * {@inheritdoc} - */ public function removeWriteStream($stream) { $key = (int) $stream; @@ -102,9 +90,6 @@ public function removeWriteStream($stream) ); } - /** - * {@inheritdoc} - */ public function addTimer($interval, callable $callback) { $timer = new Timer($interval, $callback, false); @@ -114,9 +99,6 @@ public function addTimer($interval, callable $callback) return $timer; } - /** - * {@inheritdoc} - */ public function addPeriodicTimer($interval, callable $callback) { $timer = new Timer($interval, $callback, true); @@ -126,33 +108,21 @@ public function addPeriodicTimer($interval, callable $callback) return $timer; } - /** - * {@inheritdoc} - */ public function cancelTimer(TimerInterface $timer) { $this->timers->cancel($timer); } - /** - * {@inheritdoc} - */ public function isTimerActive(TimerInterface $timer) { return $this->timers->contains($timer); } - /** - * {@inheritdoc} - */ public function futureTick(callable $listener) { $this->futureTickQueue->add($listener); } - /** - * {@inheritdoc} - */ public function addSignal($signal, callable $listener) { if ($this->pcntl === false) { @@ -162,17 +132,11 @@ public function addSignal($signal, callable $listener) $this->signals->add($signal, $listener); } - /** - * {@inheritdoc} - */ public function removeSignal($signal, callable $listener) { $this->signals->remove($signal, $listener); } - /** - * {@inheritdoc} - */ public function run() { $this->running = true; @@ -212,9 +176,6 @@ public function run() } } - /** - * {@inheritdoc} - */ public function stop() { $this->running = false; diff --git a/src/Timer/Timer.php b/src/Timer/Timer.php index 433f5be2..8a8926c6 100644 --- a/src/Timer/Timer.php +++ b/src/Timer/Timer.php @@ -36,25 +36,16 @@ public function __construct($interval, callable $callback, $periodic = false) $this->periodic = (bool) $periodic; } - /** - * {@inheritdoc} - */ public function getInterval() { return $this->interval; } - /** - * {@inheritdoc} - */ public function getCallback() { return $this->callback; } - /** - * {@inheritdoc} - */ public function isPeriodic() { return $this->periodic; From 1b77fa2313fe0366e68bd995e70a3ce35e19a5a9 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Mon, 13 Nov 2017 05:59:17 -0200 Subject: [PATCH 057/203] Use PHPUnit\Framework\TestCase instead of PHPUnit_Framework_TestCase --- composer.json | 2 +- tests/TestCase.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 67060367..e4c58eb2 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "phpunit/phpunit": "~4.8.35" }, "suggest": { "ext-libevent": ">=0.1.0 for LibEventLoop and PHP5 only", diff --git a/tests/TestCase.php b/tests/TestCase.php index 0bc26a40..dbdd54ce 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,9 +2,10 @@ namespace React\Tests\EventLoop; +use PHPUnit\Framework\TestCase as BaseTestCase; use React\EventLoop\LoopInterface; -class TestCase extends \PHPUnit_Framework_TestCase +class TestCase extends BaseTestCase { protected function expectCallableExactly($amount) { From d8ae70062d757d106499eab8aea053e92862074a Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Tue, 14 Nov 2017 06:09:21 -0200 Subject: [PATCH 058/203] Support PHPUnit 5 and 6 --- composer.json | 2 +- tests/StreamSelectLoopTest.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index e4c58eb2..e39df9ab 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "~4.8.35" + "phpunit/phpunit": "~4.8.35 || ^5.7 || ^6.4" }, "suggest": { "ext-libevent": ">=0.1.0 for LibEventLoop and PHP5 only", diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index 9927ca2b..1c5cff98 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -154,7 +154,9 @@ protected function forkSendSignal($signal) public function testSmallTimerInterval() { /** @var StreamSelectLoop|\PHPUnit_Framework_MockObject_MockObject $loop */ - $loop = $this->getMock('React\EventLoop\StreamSelectLoop', ['streamSelect']); + $loop = $this->getMockBuilder('React\EventLoop\StreamSelectLoop') + ->setMethods(['streamSelect']) + ->getMock(); $loop ->expects($this->at(0)) ->method('streamSelect') From 04f7ca63ffd88dabee041957ef3068f8f49e9624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 1 Dec 2017 12:45:49 +0100 Subject: [PATCH 059/203] Test loop implementations to keep track of stream resources (refcount) --- tests/AbstractLoopTest.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index a256c82a..bee7f495 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -61,6 +61,39 @@ public function testAddReadStreamIgnoresSecondCallable() $this->tickLoop($this->loop); } + public function testAddReadStreamReceivesDataFromStreamReference() + { + $this->received = ''; + $this->subAddReadStreamReceivesDataFromStreamReference(); + $this->assertEquals('', $this->received); + + $this->assertRunFasterThan($this->tickTimeout * 2); + $this->assertEquals('[hello]X', $this->received); + } + + /** + * Telper for above test. This happens in another helper method to verify + * the loop keep track of assigned stream resources (refcount). + */ + private function subAddReadStreamReceivesDataFromStreamReference() + { + list ($input, $output) = $this->createSocketPair(); + + fwrite($input, 'hello'); + fclose($input); + + $this->loop->addReadStream($output, function ($output) { + $chunk = fread($output, 1024); + if ($chunk === '') { + $this->received .= 'X'; + $this->loop->removeReadStream($output); + fclose($output); + } else { + $this->received .= '[' . $chunk . ']'; + } + }); + } + public function testAddWriteStream() { list ($input) = $this->createSocketPair(); From 4dd1532e0f8f137035fe4ca93b8b0118c63c1f7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 1 Dec 2017 13:15:41 +0100 Subject: [PATCH 060/203] Explicitly keep track of stream resources for ExtEventLoop on PHP7+ --- src/ExtEventLoop.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index b1cb3e74..8eee66ad 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -22,6 +22,7 @@ class ExtEventLoop implements LoopInterface private $streamCallback; private $streamEvents = []; private $streamFlags = []; + private $streamRefs = []; private $readListeners = []; private $writeListeners = []; private $running; @@ -110,7 +111,8 @@ private function removeStream($stream) $this->streamFlags[$key], $this->streamEvents[$key], $this->readListeners[$key], - $this->writeListeners[$key] + $this->writeListeners[$key], + $this->streamRefs[$key] ); } } @@ -224,6 +226,12 @@ private function subscribeStreamEvent($stream, $flag) $this->streamEvents[$key] = $event; $this->streamFlags[$key] = $flag; + + // 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->streamRefs[$key] = $stream; + } } $event->add(); From 8879b8d50bbee5ba271d60e2eeac98cebe9e70f4 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Fri, 1 Dec 2017 10:55:07 -0200 Subject: [PATCH 061/203] Test against PHP 7.2 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 63db6ca1..b51dcb9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ php: - 5.6 - 7.0 - 7.1 + - 7.2 - hhvm # ignore errors, see below # lock distro so new future defaults will not break the build From 742e5c6699006319b117aa10a43651812b0569ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 11 Nov 2017 18:53:09 +0100 Subject: [PATCH 062/203] Minor documentation fixes --- README.md | 23 ++++++++++++----------- src/ExtEventLoop.php | 2 +- src/LibEvLoop.php | 2 +- src/LibEventLoop.php | 2 +- src/LoopInterface.php | 18 +++++++++--------- src/StreamSelectLoop.php | 2 +- tests/AbstractLoopTest.php | 4 ++-- 7 files changed, 27 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 03a93a12..c3ee54a3 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Here is an async HTTP server built with just the event loop. $loop = React\EventLoop\Factory::create(); $server = stream_socket_server('tcp://127.0.0.1:8080'); -stream_set_blocking($server, 0); +stream_set_blocking($server, false); $loop->addReadStream($server, function ($server) use ($loop) { $conn = stream_socket_accept($server); @@ -97,7 +97,7 @@ $loop->run(); ## Factory The `Factory` class exists as a convenient way to pick the best available -[loop implementation)(#loop-implementation). +[loop implementation](#loop-implementations). The `create(): LoopInterface` method can be used to create a new loop instance: @@ -170,14 +170,14 @@ If you want to access any variables within your callback function, you can bind arbitrary data to a callback closure like this: ```php -function hello(LoopInterface $loop, $name) +function hello($name, LoopInterface $loop) { $loop->addTimer(1.0, function () use ($name) { echo "hello $name\n"; }); } -hello('Tester'); +hello('Tester', $loop); ``` The execution order of timers scheduled to execute at the same time is @@ -218,7 +218,7 @@ If you want to limit the number of executions, you can bind arbitrary data to a callback closure like this: ```php -function hello(LoopInterface $loop, $name) +function hello($name, LoopInterface $loop) { $n = 3; $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) { @@ -231,7 +231,7 @@ function hello(LoopInterface $loop, $name) }); } -hello('Tester'); +hello('Tester', $loop); ``` The execution order of timers scheduled to execute at the same time is @@ -245,11 +245,12 @@ cancel a pending timer. See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples). You can use the [`isTimerActive()`](#istimeractive) method to check if -this timer is still "active". After a timer is successfully canceled, +this timer is still "active". After a timer is successfully cancelled, it is no longer considered "active". Calling this method on a timer instance that has not been added to this -loop instance or on a timer +loop instance or on a timer that is not "active" (or has already been +cancelled) has no effect. ### isTimerActive() @@ -258,7 +259,7 @@ check if a given timer is active. A timer is considered "active" if it has been added to this loop instance via [`addTimer()`](#addtimer) or [`addPeriodicTimer()`](#addperiodictimer) -and has not been canceled via [`cancelTimer()`](#canceltimer) and is not +and has not been cancelled via [`cancelTimer()`](#canceltimer) and is not a non-periodic timer that has already been triggered after its interval. ### futureTick() @@ -280,14 +281,14 @@ If you want to access any variables within your callback function, you can bind arbitrary data to a callback closure like this: ```php -function hello(LoopInterface $loop, $name) +function hello($name, LoopInterface $loop) { $loop->futureTick(function () use ($name) { echo "hello $name\n"; }); } -hello('Tester'); +hello('Tester', $loop); ``` Unlike timers, tick callbacks are guaranteed to be executed in the order diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 8eee66ad..05653c6e 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -42,7 +42,7 @@ function ($signal) { $this->signals->call($signal); // Ensure there are two copies of the callable around until it has been executed. // For more information see: https://bugs.php.net/bug.php?id=62452 - // Only an issue for PHP 5, this hack can be removed once PHP 5 suppose has been dropped. + // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. $g = $f; $f = $g; }); diff --git a/src/LibEvLoop.php b/src/LibEvLoop.php index 638d9fa9..47701118 100644 --- a/src/LibEvLoop.php +++ b/src/LibEvLoop.php @@ -39,7 +39,7 @@ function ($signal) { $this->signals->call($signal); // Ensure there are two copies of the callable around until it has been executed. // For more information see: https://bugs.php.net/bug.php?id=62452 - // Only an issue for PHP 5, this hack can be removed once PHP 5 suppose has been dropped. + // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. $g = $f; $f = $g; }, $signal); diff --git a/src/LibEventLoop.php b/src/LibEventLoop.php index 3b49e2e2..2e5a6c80 100644 --- a/src/LibEventLoop.php +++ b/src/LibEventLoop.php @@ -43,7 +43,7 @@ function ($signal) { $this->signals->call($signal); // Ensure there are two copies of the callable around until it has been executed. // For more information see: https://bugs.php.net/bug.php?id=62452 - // Only an issue for PHP 5, this hack can be removed once PHP 5 suppose has been dropped. + // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. $g = $f; $f = $g; }); diff --git a/src/LoopInterface.php b/src/LoopInterface.php index c294d6e7..cf074ca0 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -152,14 +152,14 @@ public function removeWriteStream($stream); * can bind arbitrary data to a callback closure like this: * * ```php - * function hello(LoopInterface $loop, $name) + * function hello($name, LoopInterface $loop) * { * $loop->addTimer(1.0, function () use ($name) { * echo "hello $name\n"; * }); * } * - * hello('Tester'); + * hello('Tester', $loop); * ``` * * The execution order of timers scheduled to execute at the same time is @@ -205,7 +205,7 @@ public function addTimer($interval, callable $callback); * arbitrary data to a callback closure like this: * * ```php - * function hello(LoopInterface $loop, $name) + * function hello($name, LoopInterface $loop) * { * $n = 3; * $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) { @@ -218,7 +218,7 @@ public function addTimer($interval, callable $callback); * }); * } * - * hello('Tester'); + * hello('Tester', $loop); * ``` * * The execution order of timers scheduled to execute at the same time is @@ -237,12 +237,12 @@ public function addPeriodicTimer($interval, callable $callback); * See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples). * * You can use the [`isTimerActive()`](#istimeractive) method to check if - * this timer is still "active". After a timer is successfully canceled, + * this timer is still "active". After a timer is successfully cancelled, * it is no longer considered "active". * * Calling this method on a timer instance that has not been added to this * loop instance or on a timer that is not "active" (or has already been - * canceled) has no effect. + * cancelled) has no effect. * * @param TimerInterface $timer The timer to cancel. * @@ -255,7 +255,7 @@ public function cancelTimer(TimerInterface $timer); * * A timer is considered "active" if it has been added to this loop instance * via [`addTimer()`](#addtimer) or [`addPeriodicTimer()`](#addperiodictimer) - * and has not been canceled via [`cancelTimer()`](#canceltimer) and is not + * and has not been cancelled via [`cancelTimer()`](#canceltimer) and is not * a non-periodic timer that has already been triggered after its interval. * * @param TimerInterface $timer The timer to check. @@ -281,14 +281,14 @@ public function isTimerActive(TimerInterface $timer); * can bind arbitrary data to a callback closure like this: * * ```php - * function hello(LoopInterface $loop, $name) + * function hello($name, LoopInterface $loop) * { * $loop->futureTick(function () use ($name) { * echo "hello $name\n"; * }); * } * - * hello('Tester'); + * hello('Tester', $loop); * ``` * * Unlike timers, tick callbacks are guaranteed to be executed in the order diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 4273f24e..1cf6d1a3 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -37,7 +37,7 @@ function ($signal) { $this->signals->call($signal); // Ensure there are two copies of the callable around until it has been executed. // For more information see: https://bugs.php.net/bug.php?id=62452 - // Only an issue for PHP 5, this hack can be removed once PHP 5 suppose has been dropped. + // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. $g = $f; $f = $g; }); diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index bee7f495..6e338cc6 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -72,8 +72,8 @@ public function testAddReadStreamReceivesDataFromStreamReference() } /** - * Telper for above test. This happens in another helper method to verify - * the loop keep track of assigned stream resources (refcount). + * Helper for above test. This happens in another helper method to verify + * the loop keeps track of assigned stream resources (refcount). */ private function subAddReadStreamReceivesDataFromStreamReference() { From 199195cee8fcd0b0da9d333be38d6e35886281a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 11 Nov 2017 19:06:40 +0100 Subject: [PATCH 063/203] Make examples more robust and link to higher level abstractions --- examples/04-signals.php | 7 +++++++ examples/11-consume-stdin.php | 8 ++++++-- examples/12-generate-yes.php | 10 ++++++---- examples/21-http-server.php | 8 +++++++- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/examples/04-signals.php b/examples/04-signals.php index 4e03e4b7..90b68989 100644 --- a/examples/04-signals.php +++ b/examples/04-signals.php @@ -2,6 +2,11 @@ require __DIR__ . '/../vendor/autoload.php'; +if (!defined('SIGINT')) { + fwrite(STDERR, 'Not supported on your platform (ext-pcntl missing or Windows?)' . PHP_EOL); + exit(1); +} + $loop = React\EventLoop\Factory::create(); $loop->addSignal(SIGINT, $func = function ($signal) use ($loop, &$func) { @@ -9,4 +14,6 @@ $loop->removeSignal(SIGINT, $func); }); +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 c1874dd3..2a772455 100644 --- a/examples/11-consume-stdin.php +++ b/examples/11-consume-stdin.php @@ -4,19 +4,23 @@ require __DIR__ . '/../vendor/autoload.php'; -if (stream_set_blocking(STDIN, false) !== true) { - fwrite(STDERR, 'ERROR: Unable to set STDIN non-blocking' . PHP_EOL); +if (!defined('STDIN') || stream_set_blocking(STDIN, false) !== true) { + fwrite(STDERR, 'ERROR: Unable to set STDIN non-blocking (not CLI or Windows?)' . PHP_EOL); exit(1); } $loop = Factory::create(); +// read everything from STDIN and report number of bytes +// for illustration purposes only, should use react/stream instead $loop->addReadStream(STDIN, function ($stream) use ($loop) { $chunk = fread($stream, 64 * 1024); // reading nothing means we reached EOF if ($chunk === '') { $loop->removeReadStream($stream); + stream_set_blocking($stream, true); + fclose($stream); return; } diff --git a/examples/12-generate-yes.php b/examples/12-generate-yes.php index bb78e88f..ebc2beb4 100644 --- a/examples/12-generate-yes.php +++ b/examples/12-generate-yes.php @@ -10,13 +10,14 @@ $loop = React\EventLoop\Factory::create(); -$stdout = STDOUT; -if (stream_set_blocking($stdout, false) !== true) { - fwrite(STDERR, 'ERROR: Unable to set STDOUT non-blocking' . PHP_EOL); +if (!defined('STDOUT') || stream_set_blocking(STDOUT, false) !== true) { + fwrite(STDERR, 'ERROR: Unable to set STDOUT non-blocking (not CLI or Windows?)' . PHP_EOL); exit(1); } -$loop->addWriteStream($stdout, function () use ($loop, $stdout, &$data) { +// write data to STDOUT whenever its write buffer accepts data +// for illustrations purpose only, should use react/stream instead +$loop->addWriteStream(STDOUT, function ($stdout) use ($loop, &$data) { // try to write data $r = fwrite($stdout, $data); @@ -24,6 +25,7 @@ if ($r === 0) { $loop->removeWriteStream($stdout); fclose($stdout); + stream_set_blocking($stdout, true); fwrite(STDERR, 'Stopped because STDOUT closed' . PHP_EOL); return; diff --git a/examples/21-http-server.php b/examples/21-http-server.php index 66180de1..89520cec 100644 --- a/examples/21-http-server.php +++ b/examples/21-http-server.php @@ -4,9 +4,15 @@ $loop = React\EventLoop\Factory::create(); +// start TCP/IP server on localhost:8080 +// for illustration purposes only, should use react/socket instead $server = stream_socket_server('tcp://127.0.0.1:8080'); -stream_set_blocking($server, 0); +if (!$server) { + exit(1); +} +stream_set_blocking($server, false); +// wait for incoming connections on server socket $loop->addReadStream($server, function ($server) use ($loop) { $conn = stream_socket_accept($server); $data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n"; From cb3a43a4849b250ce3ae1f0a7489f9597b7f1f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 11 Nov 2017 19:13:15 +0100 Subject: [PATCH 064/203] Add higher level HTTP client examples --- examples/13-http-client-blocking.php | 35 ++++++++++++++++ examples/14-http-client-async.php | 63 ++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 examples/13-http-client-blocking.php create mode 100644 examples/14-http-client-async.php diff --git a/examples/13-http-client-blocking.php b/examples/13-http-client-blocking.php new file mode 100644 index 00000000..a2dde55c --- /dev/null +++ b/examples/13-http-client-blocking.php @@ -0,0 +1,35 @@ +addReadStream($stream, function ($stream) use ($loop) { + $chunk = fread($stream, 64 * 1024); + + // reading nothing means we reached EOF + if ($chunk === '') { + echo '[END]' . PHP_EOL; + $loop->removeReadStream($stream); + fclose($stream); + return; + } + + echo $chunk; +}); + +$loop->run(); diff --git a/examples/14-http-client-async.php b/examples/14-http-client-async.php new file mode 100644 index 00000000..c82c9887 --- /dev/null +++ b/examples/14-http-client-async.php @@ -0,0 +1,63 @@ +addPeriodicTimer(0.01, function () { + echo '.'; +}); + +// wait for connection success/error +$loop->addWriteStream($stream, function ($stream) use ($loop, $timer) { + $loop->removeWriteStream($stream); + $loop->cancelTimer($timer); + + // check for socket error (connection rejected) + if (stream_socket_get_name($stream, true) === false) { + echo '[unable to connect]' . PHP_EOL; + exit(1); + } else { + echo '[connected]' . PHP_EOL; + } + + // send HTTP request + fwrite($stream, "GET / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n"); + + // wait for HTTP response + $loop->addReadStream($stream, function ($stream) use ($loop) { + $chunk = fread($stream, 64 * 1024); + + // reading nothing means we reached EOF + if ($chunk === '') { + echo '[END]' . PHP_EOL; + $loop->removeReadStream($stream); + fclose($stream); + return; + } + + echo $chunk; + }); +}); + +$loop->run(); From c619c8b986a8a84c2ddf5b9977a854104a4d3af2 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Fri, 1 Dec 2017 15:53:02 -0200 Subject: [PATCH 065/203] Test PHP 7.2 --- travis-init.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/travis-init.sh b/travis-init.sh index 87456016..c8c8b55d 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -10,7 +10,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" ]]; then + "$TRAVIS_PHP_VERSION" != "7.1" && + "$TRAVIS_PHP_VERSION" != "7.2" ]]; then curl http://pecl.php.net/get/libevent-0.1.0.tgz | tar -xz pushd libevent-0.1.0 phpize @@ -23,7 +24,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" ]]; then + "$TRAVIS_PHP_VERSION" != "7.1" && + "$TRAVIS_PHP_VERSION" != "7.2" ]]; then git clone --recursive https://github.com/m4rw3r/php-libev pushd php-libev phpize From d431fa7d420e05757a4eaf47e19a76480e40a0fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 3 Dec 2017 16:41:29 +0100 Subject: [PATCH 066/203] Update signal handling documentation --- README.md | 32 +++++++++++++++-------------- src/LoopInterface.php | 47 ++++++++++++++++++++++++++++++++----------- 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index c3ee54a3..d98e4c73 100644 --- a/README.md +++ b/README.md @@ -313,8 +313,10 @@ See also [example #3](examples). ### addSignal() The `addSignal(int $signal, callable $listener): void` method can be used to -be notified about OS signals. This is useful to catch user interrupt signals or -shutdown signals from tools like `supervisor` or `systemd`. +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 @@ -326,32 +328,32 @@ no effect, so for performance reasons you're recommended to not return any excessive data structures. ```php -$listener = function (int $signal) { - echo 'Caught user iterrupt signal', PHP_EOL; -}; -$loop->addSignal(SIGINT, $listener); +$loop->addSignal(SIGINT, function (int $signal) { + echo 'Caught user interrupt signal' . PHP_EOL; +}); ``` See also [example #4](examples). -**Note: A listener can only be added once to the same signal, any attempts to add it -more then once will be ignored.** +Signaling is only available on Unix-like platform, Windows isn't +supported due to operating system limitations. +This method may throw a `BadMethodCallException` if signals aren't +supported on this platform, for example when required extensions are +missing. -**Note: Signaling is only available on Unix-like platform, Windows isn't supported due -to limitations from underlying signal handlers.** +**Note: A listener can only be added once to the same signal, any +attempts to add it more then once will be ignored.** ### removeSignal() -The `removeSignal(int $signal, callable $listener): void` removes a previously added -signal listener. - -Any attempts to remove listeners that aren't registerred will be ignored. +The `removeSignal(int $signal, callable $listener): void` method can be used to +remove a previously added signal listener. ```php $loop->removeSignal(SIGINT, $listener); ``` -See also [example #4](examples). +Any attempts to remove listeners that aren't registered will be ignored. ### addReadStream() diff --git a/src/LoopInterface.php b/src/LoopInterface.php index cf074ca0..57783fab 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -317,32 +317,55 @@ public function isTimerActive(TimerInterface $timer); public function futureTick(callable $listener); /** - * Registers a signal listener with the loop, which - * on it's turn registers it with a signal handler - * suitable for the loop implementation. + * Register a listener to be notified when a signal has been caught by this process. * - * A listener can only be added once, any attempts - * to add it again will be ignored. + * This is useful to catch user interrupt signals or shutdown signals from + * tools like `supervisor` or `systemd`. + * + * The listener callback function MUST be able to accept a single parameter, + * the signal added by this method or you MAY use a function which + * has no parameters at all. + * + * The listener callback function MUST NOT throw an `Exception`. + * The return value of the listener callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * ```php + * $loop->addSignal(SIGINT, function (int $signal) { + * echo 'Caught user interrupt signal' . PHP_EOL; + * }); + * ``` * * See also [example #4](examples). * + * Signaling is only available on Unix-like platform, Windows isn't + * supported due to operating system limitations. + * This method may throw a `BadMethodCallException` if signals aren't + * supported on this platform, for example when required extensions are + * missing. + * + * **Note: A listener can only be added once to the same signal, any + * attempts to add it more then once will be ignored.** + * * @param int $signal * @param callable $listener * - * @throws \BadMethodCallException when signals - * aren't supported by the loop, e.g. when required - * extensions are missing. + * @throws \BadMethodCallException when signals aren't supported on this + * platform, for example when required extensions are missing. * * @return void */ public function addSignal($signal, callable $listener); /** - * Removed previous registered signal listener from - * the loop, which on it's turn removes it from the - * underlying signal handler. + * Removes a previously added signal listener. * - * See also [example #4](examples). + * ```php + * $loop->removeSignal(SIGINT, $listener); + * ``` + * + * Any attempts to remove listeners that aren't registered will be ignored. * * @param int $signal * @param callable $listener From 9a6c2b9c9afa1f8d89b83ba753b2f51ffba5f9dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 1 Dec 2017 15:56:38 +0100 Subject: [PATCH 067/203] Add all sections to TOC --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index d98e4c73..870a2a63 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,19 @@ For the code of the current stable 0.4.x release, checkout the * [Quickstart example](#quickstart-example) * [Usage](#usage) +* [Factory](#factory) * [Loop implementations](#loop-implementations) + * [addtimer()](#addtimer) + * [addPeriodicTimer()](#addperiodictimer) + * [cancelTimer()](#canceltimer) + * [isTimerActive()](#istimeractive) + * [futureTick()](#futuretick) + * [addSignal()](#addsignal) + * [removeSignal()](#removesignal) + * [addReadStream()](#addreadstream) + * [addWriteStream()](#addwritestream) + * [removeReadStream()](#removereadstream) + * [removeWriteStream()](#removewritestream) * [Install](#install) * [Tests](#tests) * [License](#license) From 5944b2daff8e96e6b9d64581b17589b8ba5a5a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 2 Dec 2017 16:13:07 +0100 Subject: [PATCH 068/203] Documentation for event loop implementations --- README.md | 136 ++++++++++++++++++++++++++------------- src/ExtEventLoop.php | 7 +- src/Factory.php | 6 +- src/LibEvLoop.php | 5 ++ src/LibEventLoop.php | 7 +- src/StreamSelectLoop.php | 9 ++- 6 files changed, 118 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 870a2a63..37e6de74 100644 --- a/README.md +++ b/README.md @@ -18,19 +18,25 @@ For the code of the current stable 0.4.x release, checkout the * [Quickstart example](#quickstart-example) * [Usage](#usage) -* [Factory](#factory) -* [Loop implementations](#loop-implementations) - * [addtimer()](#addtimer) - * [addPeriodicTimer()](#addperiodictimer) - * [cancelTimer()](#canceltimer) - * [isTimerActive()](#istimeractive) - * [futureTick()](#futuretick) - * [addSignal()](#addsignal) - * [removeSignal()](#removesignal) - * [addReadStream()](#addreadstream) - * [addWriteStream()](#addwritestream) - * [removeReadStream()](#removereadstream) - * [removeWriteStream()](#removewritestream) + * [Factory](#factory) + * [create()](#create) + * [Loop implementations](#loop-implementations) + * [StreamSelectLoop](#streamselectloop) + * [LibEventLoop](#libeventloop) + * [LibEvLoop](#libevloop) + * [ExtEventLoop](#exteventloop) + * [LoopInterface](#loopinterface) + * [addtimer()](#addtimer) + * [addPeriodicTimer()](#addperiodictimer) + * [cancelTimer()](#canceltimer) + * [isTimerActive()](#istimeractive) + * [futureTick()](#futuretick) + * [addSignal()](#addsignal) + * [removeSignal()](#removesignal) + * [addReadStream()](#addreadstream) + * [addWriteStream()](#addwritestream) + * [removeReadStream()](#removereadstream) + * [removeWriteStream()](#removewritestream) * [Install](#install) * [Tests](#tests) * [License](#license) @@ -106,49 +112,79 @@ $loop->run(); purposes. 3. The loop is run with a single `$loop->run()` call at the end of the program. -## Factory +### Factory The `Factory` class exists as a convenient way to pick the best available -[loop implementation](#loop-implementations). +[event loop implementation](#loop-implementations). -The `create(): LoopInterface` method can be used to create a new loop +#### create() + +The `create(): LoopInterface` method can be used to create a new event loop instance: ```php $loop = React\EventLoop\Factory::create(); ``` -This method always returns an instance implementing `LoopInterface`, -the actual loop implementation is an implementation detail. +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 interface there are the following implementations provided: - -* `StreamSelectLoop`: This is the only implementation which works out of the - box with PHP. It does a simple `select` system call. It's not the most - performant of loops, but still does the job quite well. +### Loop implementations -* `LibEventLoop`: This uses the `libevent` pecl extension. `libevent` itself - supports a number of system-specific backends (epoll, kqueue). +In addition to the [`LoopInterface`](#loopinterface), there are a number of +event loop implementations provided. -* `LibEvLoop`: This uses the `libev` pecl extension - ([github](https://github.com/m4rw3r/php-libev)). It supports the same - backends as libevent. - -* `ExtEventLoop`: This uses the `event` pecl extension. It supports the same - backends as libevent. - -All of the loops support these features: +All of the event loops support these features: * File descriptor polling * One-off timers * Periodic timers * Deferred execution on future loop tick -### addTimer() +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. + +Advanced! If you explicitly need a certain event loop implementation, you can +manually instantiate one of the following classes. +Note that you may have to install the required PHP extensions for the respective +event loop implementation first or this may result in a fatal error. + +#### StreamSelectLoop + +A `stream_select()` based event loop. + +This uses the [`stream_select()`](http://php.net/manual/en/function.stream-select.php) +function and is the only implementation which works out of the box with PHP. +It does a simple `select` system call. +It's not the most performant of loops, but still does the job quite well. + +#### LibEventLoop + +An `ext-libevent` based event loop. + +This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent). +`libevent` itself supports a number of system-specific backends (epoll, kqueue). + +#### LibEvLoop + +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. + +#### ExtEventLoop + +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. + +### LoopInterface + +#### addTimer() The `addTimer(float $interval, callable $callback): TimerInterface` method can be used to enqueue a callback to be invoked once after the given interval. @@ -195,7 +231,7 @@ hello('Tester', $loop); The execution order of timers scheduled to execute at the same time is not guaranteed. -### addPeriodicTimer() +#### addPeriodicTimer() The `addPeriodicTimer(float $interval, callable $callback): TimerInterface` method can be used to enqueue a callback to be invoked repeatedly after the given interval. @@ -249,7 +285,7 @@ hello('Tester', $loop); The execution order of timers scheduled to execute at the same time is not guaranteed. -### cancelTimer() +#### cancelTimer() The `cancelTimer(TimerInterface $timer): void` method can be used to cancel a pending timer. @@ -264,7 +300,7 @@ Calling this method on a timer instance that has not been added to this loop instance or on a timer that is not "active" (or has already been cancelled) has no effect. -### isTimerActive() +#### isTimerActive() The `isTimerActive(TimerInterface $timer): bool` method can be used to check if a given timer is active. @@ -274,7 +310,7 @@ via [`addTimer()`](#addtimer) or [`addPeriodicTimer()`](#addperiodictimer) and has not been cancelled via [`cancelTimer()`](#canceltimer) and is not a non-periodic timer that has already been triggered after its interval. -### futureTick() +#### futureTick() The `futureTick(callable $listener): void` method can be used to schedule a callback to be invoked on a future tick of the event loop. @@ -322,7 +358,7 @@ echo 'a'; See also [example #3](examples). -### addSignal() +#### addSignal() The `addSignal(int $signal, callable $listener): void` method can be used to register a listener to be notified when a signal has been caught by this process. @@ -356,7 +392,7 @@ missing. **Note: A listener can only be added once to the same signal, any attempts to add it more then once will be ignored.** -### removeSignal() +#### removeSignal() The `removeSignal(int $signal, callable $listener): void` method can be used to remove a previously added signal listener. @@ -367,7 +403,7 @@ $loop->removeSignal(SIGINT, $listener); Any attempts to remove listeners that aren't registered will be ignored. -### addReadStream() +#### addReadStream() > Advanced! Note that this low-level API is considered advanced usage. Most use cases should probably use the higher-level @@ -410,7 +446,7 @@ read event listener for this stream. The execution order of listeners when multiple streams become ready at the same time is not guaranteed. -### addWriteStream() +#### addWriteStream() > Advanced! Note that this low-level API is considered advanced usage. Most use cases should probably use the higher-level @@ -453,7 +489,7 @@ write event listener for this stream. The execution order of listeners when multiple streams become ready at the same time is not guaranteed. -### removeReadStream() +#### removeReadStream() The `removeReadStream(resource $stream): void` method can be used to remove the read event listener for the given stream. @@ -461,7 +497,7 @@ remove the read event listener for the given stream. Removing a stream from the loop that has already been removed or trying to remove a stream that was never added or is invalid has no effect. -### removeWriteStream() +#### removeWriteStream() The `removeWriteStream(resource $stream): void` method can be used to remove the write event listener for the given stream. @@ -480,6 +516,14 @@ This will install the latest supported version: $ composer require react/event-loop ``` +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.4 through current PHP 7+ and +HHVM. +It's *highly recommended to use PHP 7+* 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. + ## Tests To run the test suite, you first need to clone this repo and then install all diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 05653c6e..eb08e9b3 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -11,7 +11,12 @@ use SplObjectStorage; /** - * An ext-event based event-loop. + * 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. + * + * @link https://pecl.php.net/package/event */ class ExtEventLoop implements LoopInterface { diff --git a/src/Factory.php b/src/Factory.php index 7c87180c..0e868358 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -3,19 +3,19 @@ namespace React\EventLoop; /** - * The `Factory` class exists as a convenient way to pick the best available loop implementation. + * The `Factory` class exists as a convenient way to pick the best available event loop implementation. */ class Factory { /** - * Creates a new loop instance + * Creates a new event loop instance * * ```php * $loop = React\EventLoop\Factory::create(); * ``` * * This method always returns an instance implementing `LoopInterface`, - * the actual loop implementation is an implementation detail. + * the actual event loop implementation is an implementation detail. * * This method should usually only be called once at the beginning of the program. * diff --git a/src/LibEvLoop.php b/src/LibEvLoop.php index 47701118..5dbaf62a 100644 --- a/src/LibEvLoop.php +++ b/src/LibEvLoop.php @@ -12,6 +12,11 @@ use SplObjectStorage; /** + * 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. + * * @see https://github.com/m4rw3r/php-libev * @see https://gist.github.com/1688204 */ diff --git a/src/LibEventLoop.php b/src/LibEventLoop.php index 2e5a6c80..2a1ee6aa 100644 --- a/src/LibEventLoop.php +++ b/src/LibEventLoop.php @@ -10,7 +10,12 @@ use SplObjectStorage; /** - * An ext-libevent based event-loop. + * An `ext-libevent` based event loop. + * + * This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent). + * `libevent` itself supports a number of system-specific backends (epoll, kqueue). + * + * @link https://pecl.php.net/package/libevent */ class LibEventLoop implements LoopInterface { diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 1cf6d1a3..d3498f2d 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -9,7 +9,14 @@ use React\EventLoop\Timer\Timers; /** - * A stream_select() based event-loop. + * A `stream_select()` based event loop. + * + * This uses the [`stream_select()`](http://php.net/manual/en/function.stream-select.php) + * function and is the only implementation which works out of the box with PHP. + * It does a simple `select` system call. + * It's not the most performant of loops, but still does the job quite well. + * + * @link http://php.net/manual/en/function.stream-select.php */ class StreamSelectLoop implements LoopInterface { From ffe26e93ff97a44762a5f7a8ca9f8957dced395f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 4 Dec 2017 09:52:14 +0100 Subject: [PATCH 069/203] Documentation for common event loop use cases --- README.md | 40 ++++++++++++++++++++++++++++++++++++++-- src/ExtEventLoop.php | 2 ++ src/LibEvLoop.php | 4 ++++ src/LibEventLoop.php | 7 +++++++ src/StreamSelectLoop.php | 27 +++++++++++++++++++++++++-- 5 files changed, 76 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 37e6de74..e9531a50 100644 --- a/README.md +++ b/README.md @@ -158,8 +158,31 @@ A `stream_select()` based event loop. This uses the [`stream_select()`](http://php.net/manual/en/function.stream-select.php) function and is the only implementation which works out of the box with PHP. -It does a simple `select` system call. -It's not the most performant of loops, but still does the job quite well. + +This event loop works out of the box on PHP 5.4 through PHP 7+ and HHVM. +This means that no installation is required and this library works on all +platforms and supported PHP versions. +Accordingly, the [`Factory`](#factory) will use this event loop by default if +you do not install any of the event loop extensions listed below. + +Under the hood, it does a simple `select` system call. +This system call is limited to the maximum file descriptor number of +`FD_SETSIZE` (platform dependent, commonly 1024) and scales with `O(m)` +(`m` being the maximum file descriptor number passed). +This means that you may run into issues when handling thousands of streams +concurrently and you may want to look into using one of the alternative +event loop implementations listed below in this case. +If your use case is among the many common use cases that involve handling only +dozens or a few hundred streams at once, then this event loop implementation +performs really well. + +If you want to use signal handling (see also [`addSignal()`](#addsignal) below), +this event loop implementation requires `ext-pcntl`. +This extension is only available for Unix-like platforms and does not support +Windows. +It is commonly installed as part of many PHP distributions. +If this extension is missing (or you're running on Windows), signal handling is +not supported and throws a `BadMethodCallException` instead. #### LibEventLoop @@ -168,6 +191,13 @@ An `ext-libevent` based event loop. This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent). `libevent` itself supports a number of system-specific backends (epoll, kqueue). +This event loop does only work with PHP 5. +An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for +PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s. +To reiterate: Using this event loop on PHP 7 is not recommended. +Accordingly, the [`Factory`](#factory) will not try to use this event loop on +PHP 7. + #### LibEvLoop An `ext-libev` based event loop. @@ -175,6 +205,10 @@ 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 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. + #### ExtEventLoop An `ext-event` based event loop. @@ -182,6 +216,8 @@ 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 loop is known to work with PHP 5.4 through PHP 7+. + ### LoopInterface #### addTimer() diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index eb08e9b3..73e7e97c 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -16,6 +16,8 @@ * This uses the [`event` PECL extension](https://pecl.php.net/package/event). * It supports the same backends as libevent. * + * This loop is known to work with PHP 5.4 through PHP 7+. + * * @link https://pecl.php.net/package/event */ class ExtEventLoop implements LoopInterface diff --git a/src/LibEvLoop.php b/src/LibEvLoop.php index 5dbaf62a..640c7da7 100644 --- a/src/LibEvLoop.php +++ b/src/LibEvLoop.php @@ -17,6 +17,10 @@ * This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev). * It supports the same backends as libevent. * + * This loop does only work with PHP 5. + * An update for PHP 7 is [unlikely](https://github.com/m4rw3r/php-libev/issues/8) + * to happen any time soon. + * * @see https://github.com/m4rw3r/php-libev * @see https://gist.github.com/1688204 */ diff --git a/src/LibEventLoop.php b/src/LibEventLoop.php index 2a1ee6aa..94cdbb01 100644 --- a/src/LibEventLoop.php +++ b/src/LibEventLoop.php @@ -15,6 +15,13 @@ * This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent). * `libevent` itself supports a number of system-specific backends (epoll, kqueue). * + * This event loop does only work with PHP 5. + * An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for + * PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s. + * To reiterate: Using this event loop on PHP 7 is not recommended. + * Accordingly, the [`Factory`](#factory) will not try to use this event loop on + * PHP 7. + * * @link https://pecl.php.net/package/libevent */ class LibEventLoop implements LoopInterface diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index d3498f2d..af46ea23 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -13,8 +13,31 @@ * * This uses the [`stream_select()`](http://php.net/manual/en/function.stream-select.php) * function and is the only implementation which works out of the box with PHP. - * It does a simple `select` system call. - * It's not the most performant of loops, but still does the job quite well. + * + * This event loop works out of the box on PHP 5.4 through PHP 7+ and HHVM. + * This means that no installation is required and this library works on all + * platforms and supported PHP versions. + * Accordingly, the [`Factory`](#factory) will use this event loop by default if + * you do not install any of the event loop extensions listed below. + * + * Under the hood, it does a simple `select` system call. + * This system call is limited to the maximum file descriptor number of + * `FD_SETSIZE` (platform dependent, commonly 1024) and scales with `O(m)` + * (`m` being the maximum file descriptor number passed). + * This means that you may run into issues when handling thousands of streams + * concurrently and you may want to look into using one of the alternative + * event loop implementations listed below in this case. + * If your use case is among the many common use cases that involve handling only + * dozens or a few hundred streams at once, then this event loop implementation + * performs really well. + * + * If you want to use signal handling (see also [`addSignal()`](#addsignal) below), + * this event loop implementation requires `ext-pcntl`. + * This extension is only available for Unix-like platforms and does not support + * Windows. + * It is commonly installed as part of many PHP distributions. + * If this extension is missing (or you're running on Windows), signal handling is + * not supported and throws a `BadMethodCallException` instead. * * @link http://php.net/manual/en/function.stream-select.php */ From d8e65fbe6e8a0573ce5ce47bcf777ecb313d6445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 4 Dec 2017 10:42:47 +0100 Subject: [PATCH 070/203] Consistent naming for event loop implementations --- README.md | 26 +++++++++---------- composer.json | 6 ++--- src/{LibEvLoop.php => ExtLibevLoop.php} | 2 +- src/{LibEventLoop.php => ExtLibeventLoop.php} | 2 +- src/Factory.php | 6 ++--- ...LibEvLoopTest.php => ExtLibevLoopTest.php} | 8 +++--- ...ntLoopTest.php => ExtLibeventLoopTest.php} | 6 ++--- ...bEvTimerTest.php => ExtLibevTimerTest.php} | 6 ++--- ...TimerTest.php => ExtLibeventTimerTest.php} | 6 ++--- 9 files changed, 34 insertions(+), 34 deletions(-) rename src/{LibEvLoop.php => ExtLibevLoop.php} (99%) rename src/{LibEventLoop.php => ExtLibeventLoop.php} (99%) rename tests/{LibEvLoopTest.php => ExtLibevLoopTest.php} (66%) rename tests/{LibEventLoopTest.php => ExtLibeventLoopTest.php} (91%) rename tests/Timer/{LibEvTimerTest.php => ExtLibevTimerTest.php} (67%) rename tests/Timer/{LibEventTimerTest.php => ExtLibeventTimerTest.php} (66%) diff --git a/README.md b/README.md index e9531a50..27bb0bd3 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ For the code of the current stable 0.4.x release, checkout the * [create()](#create) * [Loop implementations](#loop-implementations) * [StreamSelectLoop](#streamselectloop) - * [LibEventLoop](#libeventloop) - * [LibEvLoop](#libevloop) * [ExtEventLoop](#exteventloop) + * [ExtLibeventLoop](#extlibeventloop) + * [ExtLibevLoop](#extlibevloop) * [LoopInterface](#loopinterface) * [addtimer()](#addtimer) * [addPeriodicTimer()](#addperiodictimer) @@ -184,7 +184,16 @@ 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. -#### LibEventLoop +#### ExtEventLoop + +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 loop is known to work with PHP 5.4 through PHP 7+. + +#### ExtLibeventLoop An `ext-libevent` based event loop. @@ -198,7 +207,7 @@ 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. -#### LibEvLoop +#### ExtLibevLoop An `ext-libev` based event loop. @@ -209,15 +218,6 @@ 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. -#### ExtEventLoop - -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 loop is known to work with PHP 5.4 through PHP 7+. - ### LoopInterface #### addTimer() diff --git a/composer.json b/composer.json index e39df9ab..ad14cf4b 100644 --- a/composer.json +++ b/composer.json @@ -10,10 +10,10 @@ "phpunit/phpunit": "~4.8.35 || ^5.7 || ^6.4" }, "suggest": { - "ext-libevent": ">=0.1.0 for LibEventLoop and PHP5 only", "ext-event": "~1.0 for ExtEventLoop", - "ext-libev": "for LibEvLoop", - "ext-pcntl": "For signals support when using the stream_select loop" + "ext-libevent": ">=0.1.0 for ExtLibeventLoop and PHP 5 only", + "ext-libev": "for ExtLibevLoop and PHP 5 only", + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" }, "autoload": { "psr-4": { diff --git a/src/LibEvLoop.php b/src/ExtLibevLoop.php similarity index 99% rename from src/LibEvLoop.php rename to src/ExtLibevLoop.php index 640c7da7..93af0cd9 100644 --- a/src/LibEvLoop.php +++ b/src/ExtLibevLoop.php @@ -24,7 +24,7 @@ * @see https://github.com/m4rw3r/php-libev * @see https://gist.github.com/1688204 */ -class LibEvLoop implements LoopInterface +class ExtLibevLoop implements LoopInterface { private $loop; private $futureTickQueue; diff --git a/src/LibEventLoop.php b/src/ExtLibeventLoop.php similarity index 99% rename from src/LibEventLoop.php rename to src/ExtLibeventLoop.php index 94cdbb01..0d0a1b3e 100644 --- a/src/LibEventLoop.php +++ b/src/ExtLibeventLoop.php @@ -24,7 +24,7 @@ * * @link https://pecl.php.net/package/libevent */ -class LibEventLoop implements LoopInterface +class ExtLibeventLoop implements LoopInterface { const MICROSECONDS_PER_SECOND = 1000000; diff --git a/src/Factory.php b/src/Factory.php index 0e868358..b497667d 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -25,12 +25,12 @@ public static function create() { // @codeCoverageIgnoreStart if (class_exists('libev\EventLoop', false)) { - return new LibEvLoop; + return new ExtLibevLoop(); } elseif (class_exists('EventBase', false)) { - return new ExtEventLoop; + return new ExtEventLoop(); } elseif (function_exists('event_base_new') && PHP_VERSION_ID < 70000) { // only use ext-libevent on PHP < 7 for now - return new LibEventLoop(); + return new ExtLibeventLoop(); } return new StreamSelectLoop(); diff --git a/tests/LibEvLoopTest.php b/tests/ExtLibevLoopTest.php similarity index 66% rename from tests/LibEvLoopTest.php rename to tests/ExtLibevLoopTest.php index 5ea98e30..19a5e876 100644 --- a/tests/LibEvLoopTest.php +++ b/tests/ExtLibevLoopTest.php @@ -2,9 +2,9 @@ namespace React\Tests\EventLoop; -use React\EventLoop\LibEvLoop; +use React\EventLoop\ExtLibevLoop; -class LibEvLoopTest extends AbstractLoopTest +class ExtLibevLoopTest extends AbstractLoopTest { public function createLoop() { @@ -12,11 +12,11 @@ public function createLoop() $this->markTestSkipped('libev tests skipped because ext-libev is not installed.'); } - return new LibEvLoop(); + return new ExtLibevLoop(); } public function testLibEvConstructor() { - $loop = new LibEvLoop(); + $loop = new ExtLibevLoop(); } } diff --git a/tests/LibEventLoopTest.php b/tests/ExtLibeventLoopTest.php similarity index 91% rename from tests/LibEventLoopTest.php rename to tests/ExtLibeventLoopTest.php index 920b33cc..84970658 100644 --- a/tests/LibEventLoopTest.php +++ b/tests/ExtLibeventLoopTest.php @@ -2,9 +2,9 @@ namespace React\Tests\EventLoop; -use React\EventLoop\LibEventLoop; +use React\EventLoop\ExtLibeventLoop; -class LibEventLoopTest extends AbstractLoopTest +class ExtLibeventLoopTest extends AbstractLoopTest { private $fifoPath; @@ -18,7 +18,7 @@ public function createLoop() $this->markTestSkipped('libevent tests skipped because ext-libevent is not installed.'); } - return new LibEventLoop(); + return new ExtLibeventLoop(); } public function tearDown() diff --git a/tests/Timer/LibEvTimerTest.php b/tests/Timer/ExtLibevTimerTest.php similarity index 67% rename from tests/Timer/LibEvTimerTest.php rename to tests/Timer/ExtLibevTimerTest.php index 73abe8ed..65e82bee 100644 --- a/tests/Timer/LibEvTimerTest.php +++ b/tests/Timer/ExtLibevTimerTest.php @@ -2,9 +2,9 @@ namespace React\Tests\EventLoop\Timer; -use React\EventLoop\LibEvLoop; +use React\EventLoop\ExtLibevLoop; -class LibEvTimerTest extends AbstractTimerTest +class ExtLibevTimerTest extends AbstractTimerTest { public function createLoop() { @@ -12,6 +12,6 @@ public function createLoop() $this->markTestSkipped('libev tests skipped because ext-libev is not installed.'); } - return new LibEvLoop(); + return new ExtLibevLoop(); } } diff --git a/tests/Timer/LibEventTimerTest.php b/tests/Timer/ExtLibeventTimerTest.php similarity index 66% rename from tests/Timer/LibEventTimerTest.php rename to tests/Timer/ExtLibeventTimerTest.php index 3db20350..9089b9a5 100644 --- a/tests/Timer/LibEventTimerTest.php +++ b/tests/Timer/ExtLibeventTimerTest.php @@ -2,9 +2,9 @@ namespace React\Tests\EventLoop\Timer; -use React\EventLoop\LibEventLoop; +use React\EventLoop\ExtLibeventLoop; -class LibEventTimerTest extends AbstractTimerTest +class ExtLibeventTimerTest extends AbstractTimerTest { public function createLoop() { @@ -12,6 +12,6 @@ public function createLoop() $this->markTestSkipped('libevent tests skipped because ext-libevent is not installed.'); } - return new LibEventLoop(); + return new ExtLibeventLoop(); } } From 536aef8fb79012b0d4713bb8818cbfcfe0569a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 4 Dec 2017 11:59:32 +0100 Subject: [PATCH 071/203] Documentation for edge-triggered event listeners and stream buffers --- README.md | 17 +++++++++++++++++ src/ExtLibeventLoop.php | 9 +++++++++ src/LoopInterface.php | 8 ++++++++ 3 files changed, 34 insertions(+) diff --git a/README.md b/README.md index 27bb0bd3..2c48b410 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,15 @@ To reiterate: Using this event loop on PHP 7 is not recommended. Accordingly, the [`Factory`](#factory) will not try to use this event loop on PHP 7. +This event loop is known to trigger a readable listener only if +the stream *becomes* readable (edge-triggered) and may not trigger if the +stream has already been readable from the beginning. +This also implies that a stream may not be recognized as readable when data +is still left in PHP's internal stream buffers. +As such, it's recommended to use `stream_set_read_buffer($stream, 0);` +to disable PHP's internal read buffer in this case. +See also [`addReadStream()`](#addreadstream) for more details. + #### ExtLibevLoop An `ext-libev` based event loop. @@ -482,6 +491,14 @@ read event listener for this stream. The execution order of listeners when multiple streams become ready at the same time is not guaranteed. +Some event loop implementations are known to only trigger the listener if +the stream *becomes* readable (edge-triggered) and may not trigger if the +stream has already been readable from the beginning. +This also implies that a stream may not be recognized as readable when data +is still left in PHP's internal stream buffers. +As such, it's recommended to use `stream_set_read_buffer($stream, 0);` +to disable PHP's internal read buffer in this case. + #### addWriteStream() > Advanced! Note that this low-level API is considered advanced usage. diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index 0d0a1b3e..08896b4f 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -22,6 +22,15 @@ * Accordingly, the [`Factory`](#factory) will not try to use this event loop on * PHP 7. * + * This event loop is known to trigger a readable listener only if + * the stream *becomes* readable (edge-triggered) and may not trigger if the + * stream has already been readable from the beginning. + * This also implies that a stream may not be recognized as readable when data + * is still left in PHP's internal stream buffers. + * As such, it's recommended to use `stream_set_read_buffer($stream, 0);` + * to disable PHP's internal read buffer in this case. + * See also [`addReadStream()`](#addreadstream) for more details. + * * @link https://pecl.php.net/package/libevent */ class ExtLibeventLoop implements LoopInterface diff --git a/src/LoopInterface.php b/src/LoopInterface.php index 57783fab..fa905914 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -94,6 +94,14 @@ public function addReadStream($stream, callable $listener); * The execution order of listeners when multiple streams become ready at * the same time is not guaranteed. * + * Some event loop implementations are known to only trigger the listener if + * the stream *becomes* readable (edge-triggered) and may not trigger if the + * stream has already been readable from the beginning. + * This also implies that a stream may not be recognized as readable when data + * is still left in PHP's internal stream buffers. + * As such, it's recommended to use `stream_set_read_buffer($stream, 0);` + * to disable PHP's internal read buffer in this case. + * * @param resource $stream The PHP stream resource to check. * @param callable $listener Invoked when the stream is ready. * @see self::removeWriteStream() From 1558f3f49dc39df062d04fc753617ae3c43e8e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 4 Dec 2017 12:37:19 +0100 Subject: [PATCH 072/203] Documentation for monotonic time source vs wall-clock time --- README.md | 22 +++++++++++++++++++++- src/LoopInterface.php | 11 +++++++++++ src/StreamSelectLoop.php | 9 +++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 27bb0bd3..48928dab 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ For the code of the current stable 0.4.x release, checkout the * [ExtLibeventLoop](#extlibeventloop) * [ExtLibevLoop](#extlibevloop) * [LoopInterface](#loopinterface) - * [addtimer()](#addtimer) + * [addTimer()](#addtimer) * [addPeriodicTimer()](#addperiodictimer) * [cancelTimer()](#canceltimer) * [isTimerActive()](#istimeractive) @@ -184,6 +184,15 @@ 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. +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. +See also [`addTimer()`](#addtimer) for more details. + #### ExtEventLoop An `ext-event` based event loop. @@ -267,6 +276,17 @@ hello('Tester', $loop); The execution order of timers scheduled to execute at the same time is not guaranteed. +This interface suggests that event loop implementations SHOULD use a +monotic 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. +While this does not affect many common use cases, this is an important +distinction for programs that rely on a high time precision or on systems +that are subject to discontinuous time adjustments (time jumps). +This means that if you schedule a timer to trigger in 30s and then adjust +your system time forward by 20s, the timer SHOULD still trigger in 30s. +See also [event loop implementations](#loop-implementations) for more details. + #### addPeriodicTimer() The `addPeriodicTimer(float $interval, callable $callback): TimerInterface` method can be used to diff --git a/src/LoopInterface.php b/src/LoopInterface.php index 57783fab..e91b856e 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -224,6 +224,17 @@ public function addTimer($interval, callable $callback); * The execution order of timers scheduled to execute at the same time is * not guaranteed. * + * This interface suggests that event loop implementations SHOULD use a + * monotic 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. + * While this does not affect many common use cases, this is an important + * distinction for programs that rely on a high time precision or on systems + * that are subject to discontinuous time adjustments (time jumps). + * This means that if you schedule a timer to trigger in 30s and then adjust + * your system time forward by 20s, the timer SHOULD still trigger in 30s. + * See also [event loop implementations](#loop-implementations) for more details. + * * @param int|float $interval The number of seconds to wait before execution. * @param callable $callback The callback to invoke. * diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index af46ea23..e556d8b3 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -39,6 +39,15 @@ * 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. + * 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. + * See also [`addTimer()`](#addtimer) for more details. + * * @link http://php.net/manual/en/function.stream-select.php */ class StreamSelectLoop implements LoopInterface From 06cc5bf1c92df596da0e08f8ec0a3a82caeaa3aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 4 Dec 2017 13:57:19 +0100 Subject: [PATCH 073/203] Ensure large timer interval does not overflow on 32bit systems --- src/StreamSelectLoop.php | 12 +++++++----- tests/AbstractLoopTest.php | 17 +++++++++++++++++ tests/StreamSelectLoopTest.php | 33 --------------------------------- 3 files changed, 24 insertions(+), 38 deletions(-) diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index af46ea23..9b678e7a 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -186,11 +186,11 @@ public function run() if ($timeout < 0) { $timeout = 0; } else { - /* - * round() needed to correct float error: - * https://github.com/reactphp/event-loop/issues/48 - */ - $timeout = round($timeout * self::MICROSECONDS_PER_SECOND); + // Convert float seconds to int microseconds. + // Ensure we do not exceed maximum integer size, which may + // cause the loop to tick once every ~35min on 32bit systems. + $timeout *= self::MICROSECONDS_PER_SECOND; + $timeout = $timeout > PHP_INT_MAX ? PHP_INT_MAX : (int)$timeout; } // The only possible event is stream activity, so wait forever ... @@ -213,6 +213,8 @@ public function stop() /** * Wait/check for stream activity, or until the next timer is due. + * + * @param integer|null $timeout Activity timeout in microseconds, or null to wait forever. */ private function waitForStreamActivity($timeout) { diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 6e338cc6..7ef881a3 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -480,6 +480,23 @@ public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop() $this->assertRunFasterThan(1.6); } + public function testTimerIntervalCanBeFarInFuture() + { + // get only one part of the pair to ensure the other side will close immediately + list($stream) = $this->createSocketPair(); + + // start a timer very far in the future + $timer = $this->loop->addTimer(PHP_INT_MAX, function () { }); + + // remove stream and timer when the stream is readable (closes) + $this->loop->addReadStream($stream, function ($stream) use ($timer) { + $this->loop->removeReadStream($stream); + $this->loop->cancelTimer($timer); + }); + + $this->assertRunFasterThan($this->tickTimeout); + } + private function assertRunSlowerThan($minInterval) { $start = microtime(true); diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index 1c5cff98..87bbbe69 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -4,7 +4,6 @@ use React\EventLoop\LoopInterface; use React\EventLoop\StreamSelectLoop; -use React\EventLoop\Timer\Timer; class StreamSelectLoopTest extends AbstractLoopTest { @@ -144,36 +143,4 @@ protected function forkSendSignal($signal) die(); } } - - /** - * https://github.com/reactphp/event-loop/issues/48 - * - * Tests that timer with very small interval uses at least 1 microsecond - * timeout. - */ - public function testSmallTimerInterval() - { - /** @var StreamSelectLoop|\PHPUnit_Framework_MockObject_MockObject $loop */ - $loop = $this->getMockBuilder('React\EventLoop\StreamSelectLoop') - ->setMethods(['streamSelect']) - ->getMock(); - $loop - ->expects($this->at(0)) - ->method('streamSelect') - ->with([], [], 1); - $loop - ->expects($this->at(1)) - ->method('streamSelect') - ->with([], [], 0); - - $callsCount = 0; - $loop->addPeriodicTimer(Timer::MIN_INTERVAL, function() use (&$loop, &$callsCount) { - $callsCount++; - if ($callsCount == 2) { - $loop->stop(); - } - }); - - $loop->run(); - } } From cc791f562e63b688e1102822a87fcc2e72237506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 4 Dec 2017 14:29:59 +0100 Subject: [PATCH 074/203] Remove unneeded isTimerActive() to reduce API surface --- README.md | 18 +----------------- src/ExtEventLoop.php | 9 ++------- src/ExtLibevLoop.php | 7 +------ src/ExtLibeventLoop.php | 9 ++------- src/LoopInterface.php | 21 +-------------------- src/StreamSelectLoop.php | 5 ----- tests/Timer/AbstractTimerTest.php | 13 ------------- 7 files changed, 7 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 70ba09ce..7d7e0f38 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ For the code of the current stable 0.4.x release, checkout the * [addTimer()](#addtimer) * [addPeriodicTimer()](#addperiodictimer) * [cancelTimer()](#canceltimer) - * [isTimerActive()](#istimeractive) * [futureTick()](#futuretick) * [addSignal()](#addsignal) * [removeSignal()](#removesignal) @@ -357,23 +356,8 @@ cancel a pending timer. See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples). -You can use the [`isTimerActive()`](#istimeractive) method to check if -this timer is still "active". After a timer is successfully cancelled, -it is no longer considered "active". - Calling this method on a timer instance that has not been added to this -loop instance or on a timer that is not "active" (or has already been -cancelled) has no effect. - -#### isTimerActive() - -The `isTimerActive(TimerInterface $timer): bool` method can be used to -check if a given timer is active. - -A timer is considered "active" if it has been added to this loop instance -via [`addTimer()`](#addtimer) or [`addPeriodicTimer()`](#addperiodictimer) -and has not been cancelled via [`cancelTimer()`](#canceltimer) and is not -a non-periodic timer that has already been triggered after its interval. +loop instance or on a timer that has already been cancelled has no effect. #### futureTick() diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 73e7e97c..c6fc1e30 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -144,17 +144,12 @@ public function addPeriodicTimer($interval, callable $callback) public function cancelTimer(TimerInterface $timer) { - if ($this->isTimerActive($timer)) { + if ($this->timerEvents->contains($timer)) { $this->timerEvents[$timer]->free(); $this->timerEvents->detach($timer); } } - public function isTimerActive(TimerInterface $timer) - { - return $this->timerEvents->contains($timer); - } - public function futureTick(callable $listener) { $this->futureTickQueue->add($listener); @@ -282,7 +277,7 @@ private function createTimerCallback() $this->timerCallback = function ($_, $__, $timer) { call_user_func($timer->getCallback(), $timer); - if (!$timer->isPeriodic() && $this->isTimerActive($timer)) { + if (!$timer->isPeriodic() && $this->timerEvents->contains($timer)) { $this->cancelTimer($timer); } }; diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php index 93af0cd9..8f598f6e 100644 --- a/src/ExtLibevLoop.php +++ b/src/ExtLibevLoop.php @@ -122,7 +122,7 @@ public function addTimer($interval, callable $callback) $callback = function () use ($timer) { call_user_func($timer->getCallback(), $timer); - if ($this->isTimerActive($timer)) { + if ($this->timerEvents->contains($timer)) { $this->cancelTimer($timer); } }; @@ -157,11 +157,6 @@ public function cancelTimer(TimerInterface $timer) } } - public function isTimerActive(TimerInterface $timer) - { - return $this->timerEvents->contains($timer); - } - public function futureTick(callable $listener) { $this->futureTickQueue->add($listener); diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index 08896b4f..fb66ecbd 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -163,7 +163,7 @@ public function addPeriodicTimer($interval, callable $callback) public function cancelTimer(TimerInterface $timer) { - if ($this->isTimerActive($timer)) { + if ($this->timerEvents->contains($timer)) { $event = $this->timerEvents[$timer]; event_del($event); @@ -173,11 +173,6 @@ public function cancelTimer(TimerInterface $timer) } } - public function isTimerActive(TimerInterface $timer) - { - return $this->timerEvents->contains($timer); - } - public function futureTick(callable $listener) { $this->futureTickQueue->add($listener); @@ -298,7 +293,7 @@ private function createTimerCallback() call_user_func($timer->getCallback(), $timer); // Timer already cancelled ... - if (!$this->isTimerActive($timer)) { + if (!$this->timerEvents->contains($timer)) { return; // Reschedule periodic timers ... diff --git a/src/LoopInterface.php b/src/LoopInterface.php index 6de1bd7c..5467fb61 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -255,13 +255,8 @@ public function addPeriodicTimer($interval, callable $callback); * * See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples). * - * You can use the [`isTimerActive()`](#istimeractive) method to check if - * this timer is still "active". After a timer is successfully cancelled, - * it is no longer considered "active". - * * Calling this method on a timer instance that has not been added to this - * loop instance or on a timer that is not "active" (or has already been - * cancelled) has no effect. + * loop instance or on a timer that has already been cancelled has no effect. * * @param TimerInterface $timer The timer to cancel. * @@ -269,20 +264,6 @@ public function addPeriodicTimer($interval, callable $callback); */ public function cancelTimer(TimerInterface $timer); - /** - * Check if a given timer is active. - * - * A timer is considered "active" if it has been added to this loop instance - * via [`addTimer()`](#addtimer) or [`addPeriodicTimer()`](#addperiodictimer) - * and has not been cancelled via [`cancelTimer()`](#canceltimer) and is not - * a non-periodic timer that has already been triggered after its interval. - * - * @param TimerInterface $timer The timer to check. - * - * @return boolean True if the timer is still enqueued for execution. - */ - public function isTimerActive(TimerInterface $timer); - /** * Schedule a callback to be invoked on a future tick of the event loop. * diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index e556d8b3..d40d60a8 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -152,11 +152,6 @@ public function cancelTimer(TimerInterface $timer) $this->timers->cancel($timer); } - public function isTimerActive(TimerInterface $timer) - { - return $this->timers->contains($timer); - } - public function futureTick(callable $listener) { $this->futureTickQueue->add($listener); diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index dc32a577..28c7a421 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -73,19 +73,6 @@ public function testAddPeriodicTimerCancelsItself() $this->assertSame(2, $i); } - public function testIsTimerActive() - { - $loop = $this->createLoop(); - - $timer = $loop->addPeriodicTimer(0.001, function () {}); - - $this->assertTrue($loop->isTimerActive($timer)); - - $loop->cancelTimer($timer); - - $this->assertFalse($loop->isTimerActive($timer)); - } - public function testMinimumIntervalOneMicrosecond() { $loop = $this->createLoop(); From 57dc09ae26b7cc12a7559bca7c9062d881744695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 4 Dec 2017 12:47:01 +0100 Subject: [PATCH 075/203] Mark internal API as private --- src/ExtLibeventLoop.php | 1 + src/StreamSelectLoop.php | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index 08896b4f..9bff9db7 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -35,6 +35,7 @@ */ class ExtLibeventLoop implements LoopInterface { + /** @internal */ const MICROSECONDS_PER_SECOND = 1000000; private $eventBase; diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 93802493..341b3716 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -52,6 +52,7 @@ */ class StreamSelectLoop implements LoopInterface { + /** @internal */ const MICROSECONDS_PER_SECOND = 1000000; private $futureTickQueue; @@ -268,7 +269,7 @@ private function waitForStreamActivity($timeout) * @return integer|false The total number of streams that are ready for read/write. * Can return false if stream_select() is interrupted by a signal. */ - protected function streamSelect(array &$read, array &$write, $timeout) + private function streamSelect(array &$read, array &$write, $timeout) { if ($read || $write) { $except = null; From 578ee5db1bcd8cd0a252f86cbddd6efc94c06fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 4 Dec 2017 12:45:40 +0100 Subject: [PATCH 076/203] Mark all classes as final --- src/ExtEventLoop.php | 2 +- src/ExtLibevLoop.php | 2 +- src/ExtLibeventLoop.php | 2 +- src/Factory.php | 2 +- src/StreamSelectLoop.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 73e7e97c..a83950d2 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -20,7 +20,7 @@ * * @link https://pecl.php.net/package/event */ -class ExtEventLoop implements LoopInterface +final class ExtEventLoop implements LoopInterface { private $eventBase; private $futureTickQueue; diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php index 93af0cd9..32f8c5bd 100644 --- a/src/ExtLibevLoop.php +++ b/src/ExtLibevLoop.php @@ -24,7 +24,7 @@ * @see https://github.com/m4rw3r/php-libev * @see https://gist.github.com/1688204 */ -class ExtLibevLoop implements LoopInterface +final class ExtLibevLoop implements LoopInterface { private $loop; private $futureTickQueue; diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index 9bff9db7..01528cb9 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -33,7 +33,7 @@ * * @link https://pecl.php.net/package/libevent */ -class ExtLibeventLoop implements LoopInterface +final class ExtLibeventLoop implements LoopInterface { /** @internal */ const MICROSECONDS_PER_SECOND = 1000000; diff --git a/src/Factory.php b/src/Factory.php index b497667d..1a56877d 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -5,7 +5,7 @@ /** * The `Factory` class exists as a convenient way to pick the best available event loop implementation. */ -class Factory +final class Factory { /** * Creates a new event loop instance diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 341b3716..146cff55 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -50,7 +50,7 @@ * * @link http://php.net/manual/en/function.stream-select.php */ -class StreamSelectLoop implements LoopInterface +final class StreamSelectLoop implements LoopInterface { /** @internal */ const MICROSECONDS_PER_SECOND = 1000000; From 2f26416f16bb20819b599f7028e39d65cc177cb2 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 18 Dec 2017 22:00:33 +0100 Subject: [PATCH 077/203] Move TimerInterface one level up --- examples/95-benchmark-memory.php | 2 +- src/ExtEventLoop.php | 2 +- src/ExtLibevLoop.php | 2 +- src/ExtLibeventLoop.php | 2 +- src/LoopInterface.php | 2 -- src/SignalsHandler.php | 2 +- src/StreamSelectLoop.php | 2 +- src/Timer/Timer.php | 2 ++ src/Timer/Timers.php | 1 + src/{Timer => }/TimerInterface.php | 2 +- 10 files changed, 10 insertions(+), 9 deletions(-) rename src/{Timer => }/TimerInterface.php (93%) diff --git a/examples/95-benchmark-memory.php b/examples/95-benchmark-memory.php index 7720aca1..4eb2df46 100644 --- a/examples/95-benchmark-memory.php +++ b/examples/95-benchmark-memory.php @@ -9,7 +9,7 @@ use React\EventLoop\Factory; use React\EventLoop\LoopInterface; -use React\EventLoop\Timer\TimerInterface; +use React\EventLoop\TimerInterface; require __DIR__ . '/../vendor/autoload.php'; diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index b1215de8..f64aa88f 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -7,7 +7,7 @@ use EventConfig as EventBaseConfig; use React\EventLoop\Tick\FutureTickQueue; use React\EventLoop\Timer\Timer; -use React\EventLoop\Timer\TimerInterface; +use React\EventLoop\TimerInterface; use SplObjectStorage; /** diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php index 0d5a4f09..05c025a4 100644 --- a/src/ExtLibevLoop.php +++ b/src/ExtLibevLoop.php @@ -8,7 +8,7 @@ use libev\TimerEvent; use React\EventLoop\Tick\FutureTickQueue; use React\EventLoop\Timer\Timer; -use React\EventLoop\Timer\TimerInterface; +use React\EventLoop\TimerInterface; use SplObjectStorage; /** diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index 30750afb..c626ab67 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -6,7 +6,7 @@ use EventBase; use React\EventLoop\Tick\FutureTickQueue; use React\EventLoop\Timer\Timer; -use React\EventLoop\Timer\TimerInterface; +use React\EventLoop\TimerInterface; use SplObjectStorage; /** diff --git a/src/LoopInterface.php b/src/LoopInterface.php index 5467fb61..f3070405 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -2,8 +2,6 @@ namespace React\EventLoop; -use React\EventLoop\Timer\TimerInterface; - interface LoopInterface { /** diff --git a/src/SignalsHandler.php b/src/SignalsHandler.php index c91bf1e2..b7c779a9 100644 --- a/src/SignalsHandler.php +++ b/src/SignalsHandler.php @@ -2,7 +2,7 @@ namespace React\EventLoop; -use React\EventLoop\Timer\TimerInterface; +use React\EventLoop\TimerInterface; /** * @internal diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index f8d11529..99f82241 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -5,7 +5,7 @@ use React\EventLoop\Signal\Pcntl; use React\EventLoop\Tick\FutureTickQueue; use React\EventLoop\Timer\Timer; -use React\EventLoop\Timer\TimerInterface; +use React\EventLoop\TimerInterface; use React\EventLoop\Timer\Timers; /** diff --git a/src/Timer/Timer.php b/src/Timer/Timer.php index 8a8926c6..53424980 100644 --- a/src/Timer/Timer.php +++ b/src/Timer/Timer.php @@ -2,6 +2,8 @@ namespace React\EventLoop\Timer; +use React\EventLoop\TimerInterface; + /** * The actual connection implementation for TimerInterface * diff --git a/src/Timer/Timers.php b/src/Timer/Timers.php index 81a21735..17bbdac8 100644 --- a/src/Timer/Timers.php +++ b/src/Timer/Timers.php @@ -2,6 +2,7 @@ namespace React\EventLoop\Timer; +use React\EventLoop\TimerInterface; use SplObjectStorage; use SplPriorityQueue; diff --git a/src/Timer/TimerInterface.php b/src/TimerInterface.php similarity index 93% rename from src/Timer/TimerInterface.php rename to src/TimerInterface.php index cf9028f9..cdcf7732 100644 --- a/src/Timer/TimerInterface.php +++ b/src/TimerInterface.php @@ -1,6 +1,6 @@ Date: Tue, 19 Dec 2017 13:37:19 +0100 Subject: [PATCH 078/203] Test removing both readable and writable side of stream when closing --- tests/AbstractLoopTest.php | 71 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 7ef881a3..f07f6877 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -184,6 +184,77 @@ public function testRemoveStreamForWriteOnly() $this->tickLoop($this->loop); } + public function testRemoveReadAndWriteStreamFromLoopOnceResourceClosesEndsLoop() + { + list($stream, $other) = $this->createSocketPair(); + stream_set_blocking($stream, false); + stream_set_blocking($other, false); + + // dummy writable handler + $this->loop->addWriteStream($stream, function () { }); + + // remove stream when the stream is readable (closes) + $this->loop->addReadStream($stream, function ($stream) { + $this->loop->removeReadStream($stream); + $this->loop->removeWriteStream($stream); + fclose($stream); + }); + + // close other side + fclose($other); + + $this->assertRunFasterThan($this->tickTimeout); + } + + public function testRemoveReadAndWriteStreamFromLoopOnceResourceClosesOnEndOfFileEndsLoop() + { + list($stream, $other) = $this->createSocketPair(); + stream_set_blocking($stream, false); + stream_set_blocking($other, false); + + // dummy writable handler + $this->loop->addWriteStream($stream, function () { }); + + // remove stream when the stream is readable (closes) + $this->loop->addReadStream($stream, function ($stream) { + $data = fread($stream, 1024); + if ($data !== '') { + return; + } + + $this->loop->removeReadStream($stream); + $this->loop->removeWriteStream($stream); + fclose($stream); + }); + + // send data and close stream + fwrite($other, str_repeat('.', 60000)); + $this->loop->addTimer(0.01, function () use ($other) { + fclose($other); + }); + + $this->assertRunFasterThan(0.1); + } + + public function testRemoveReadAndWriteStreamFromLoopWithClosingResourceEndsLoop() + { + // get only one part of the pair to ensure the other side will close immediately + list($stream) = $this->createSocketPair(); + stream_set_blocking($stream, false); + + // dummy writable handler + $this->loop->addWriteStream($stream, function () { }); + + // remove stream when the stream is readable (closes) + $this->loop->addReadStream($stream, function ($stream) { + $this->loop->removeReadStream($stream); + $this->loop->removeWriteStream($stream); + fclose($stream); + }); + + $this->assertRunFasterThan($this->tickTimeout); + } + public function testRemoveInvalid() { list ($stream) = $this->createSocketPair(); From 88b5c2a20ab5693945f240b56c3bbcde3e8dd7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 19 Dec 2017 18:37:02 +0100 Subject: [PATCH 079/203] Use separate event listeners for readable and writable side --- src/ExtEventLoop.php | 128 +++++++++++++--------------------------- src/ExtLibeventLoop.php | 117 +++++++++++------------------------- 2 files changed, 73 insertions(+), 172 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index b1215de8..a15f5315 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -27,11 +27,12 @@ final class ExtEventLoop implements LoopInterface private $timerCallback; private $timerEvents; private $streamCallback; - private $streamEvents = []; - private $streamFlags = []; - private $streamRefs = []; + private $readEvents = []; + private $writeEvents = []; private $readListeners = []; private $writeListeners = []; + private $readRefs = []; + private $writeRefs = []; private $running; private $signals; private $signalEvents = []; @@ -70,56 +71,65 @@ function ($signal) { public function addReadStream($stream, callable $listener) { $key = (int) $stream; + if (isset($this->readListeners[$key])) { + return; + } - if (!isset($this->readListeners[$key])) { - $this->readListeners[$key] = $listener; - $this->subscribeStreamEvent($stream, Event::READ); + $event = new Event($this->eventBase, $stream, Event::PERSIST | Event::READ, $this->streamCallback); + $event->add(); + $this->readEvents[$key] = $event; + $this->readListeners[$key] = $listener; + + // ext-event does not increase refcount on stream resources for PHP 7+ + // manually keep track of stream resource to prevent premature garbage collection + if (PHP_VERSION_ID >= 70000) { + $this->readRefs[$key] = $stream; } } public function addWriteStream($stream, callable $listener) { $key = (int) $stream; - - if (!isset($this->writeListeners[$key])) { - $this->writeListeners[$key] = $listener; - $this->subscribeStreamEvent($stream, Event::WRITE); + if (isset($this->writeListeners[$key])) { + return; } - } - public function removeReadStream($stream) - { - $key = (int) $stream; + $event = new Event($this->eventBase, $stream, Event::PERSIST | Event::WRITE, $this->streamCallback); + $event->add(); + $this->writeEvents[$key] = $event; + $this->writeListeners[$key] = $listener; - if (isset($this->readListeners[$key])) { - unset($this->readListeners[$key]); - $this->unsubscribeStreamEvent($stream, Event::READ); + // ext-event does not increase refcount on stream resources for PHP 7+ + // manually keep track of stream resource to prevent premature garbage collection + if (PHP_VERSION_ID >= 70000) { + $this->writeRefs[$key] = $stream; } } - public function removeWriteStream($stream) + public function removeReadStream($stream) { $key = (int) $stream; - if (isset($this->writeListeners[$key])) { - unset($this->writeListeners[$key]); - $this->unsubscribeStreamEvent($stream, Event::WRITE); + if (isset($this->readEvents[$key])) { + $this->readEvents[$key]->free(); + unset( + $this->readEvents[$key], + $this->readListeners[$key], + $this->readRefs[$key] + ); } } - private function removeStream($stream) + public function removeWriteStream($stream) { $key = (int) $stream; - if (isset($this->streamEvents[$key])) { - $this->streamEvents[$key]->free(); - + if (isset($this->writeEvents[$key])) { + $this->writeEvents[$key]->free(); unset( - $this->streamFlags[$key], - $this->streamEvents[$key], - $this->readListeners[$key], + $this->writeEvents[$key], $this->writeListeners[$key], - $this->streamRefs[$key] + $this->writeRefs[$key] ); } } @@ -175,7 +185,7 @@ public function run() $flags = EventBase::LOOP_ONCE; if (!$this->running || !$this->futureTickQueue->isEmpty()) { $flags |= EventBase::LOOP_NONBLOCK; - } elseif (!$this->streamEvents && !$this->timerEvents->count()) { + } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count()) { break; } @@ -207,64 +217,6 @@ private function scheduleTimer(TimerInterface $timer) $event->add($timer->getInterval()); } - /** - * Create a new ext-event Event object, or update the existing one. - * - * @param resource $stream - * @param integer $flag Event::READ or Event::WRITE - */ - private function subscribeStreamEvent($stream, $flag) - { - $key = (int) $stream; - - if (isset($this->streamEvents[$key])) { - $event = $this->streamEvents[$key]; - $flags = ($this->streamFlags[$key] |= $flag); - - $event->del(); - $event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback); - } else { - $event = new Event($this->eventBase, $stream, Event::PERSIST | $flag, $this->streamCallback); - - $this->streamEvents[$key] = $event; - $this->streamFlags[$key] = $flag; - - // 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->streamRefs[$key] = $stream; - } - } - - $event->add(); - } - - /** - * Update the ext-event Event object for this stream to stop listening to - * the given event type, or remove it entirely if it's no longer needed. - * - * @param resource $stream - * @param integer $flag Event::READ or Event::WRITE - */ - private function unsubscribeStreamEvent($stream, $flag) - { - $key = (int) $stream; - - $flags = $this->streamFlags[$key] &= ~$flag; - - if (0 === $flags) { - $this->removeStream($stream); - - return; - } - - $event = $this->streamEvents[$key]; - - $event->del(); - $event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback); - $event->add(); - } - /** * Create a callback used as the target of timer events. * diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index 30750afb..6f6379ff 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -43,8 +43,8 @@ final class ExtLibeventLoop implements LoopInterface private $timerCallback; private $timerEvents; private $streamCallback; - private $streamEvents = []; - private $streamFlags = []; + private $readEvents = []; + private $writeEvents = []; private $readListeners = []; private $writeListeners = []; private $running; @@ -88,21 +88,33 @@ function ($signal) { public function addReadStream($stream, callable $listener) { $key = (int) $stream; - - if (!isset($this->readListeners[$key])) { - $this->readListeners[$key] = $listener; - $this->subscribeStreamEvent($stream, EV_READ); + 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, callable $listener) { $key = (int) $stream; - - if (!isset($this->writeListeners[$key])) { - $this->writeListeners[$key] = $listener; - $this->subscribeStreamEvent($stream, EV_WRITE); + 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) @@ -110,8 +122,14 @@ public function removeReadStream($stream) $key = (int) $stream; if (isset($this->readListeners[$key])) { - unset($this->readListeners[$key]); - $this->unsubscribeStreamEvent($stream, EV_READ); + $event = $this->readEvents[$key]; + event_del($event); + event_free($event); + + unset( + $this->readEvents[$key], + $this->readListeners[$key] + ); } } @@ -120,25 +138,12 @@ public function removeWriteStream($stream) $key = (int) $stream; if (isset($this->writeListeners[$key])) { - unset($this->writeListeners[$key]); - $this->unsubscribeStreamEvent($stream, EV_WRITE); - } - } - - private function removeStream($stream) - { - $key = (int) $stream; - - if (isset($this->streamEvents[$key])) { - $event = $this->streamEvents[$key]; - + $event = $this->writeEvents[$key]; event_del($event); event_free($event); unset( - $this->streamFlags[$key], - $this->streamEvents[$key], - $this->readListeners[$key], + $this->writeEvents[$key], $this->writeListeners[$key] ); } @@ -166,7 +171,6 @@ public function cancelTimer(TimerInterface $timer) { if ($this->timerEvents->contains($timer)) { $event = $this->timerEvents[$timer]; - event_del($event); event_free($event); @@ -199,7 +203,7 @@ public function run() $flags = EVLOOP_ONCE; if (!$this->running || !$this->futureTickQueue->isEmpty()) { $flags |= EVLOOP_NONBLOCK; - } elseif (!$this->streamEvents && !$this->timerEvents->count()) { + } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count()) { break; } @@ -226,61 +230,6 @@ private function scheduleTimer(TimerInterface $timer) event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND); } - /** - * Create a new ext-libevent event resource, or update the existing one. - * - * @param resource $stream - * @param integer $flag EV_READ or EV_WRITE - */ - private function subscribeStreamEvent($stream, $flag) - { - $key = (int) $stream; - - if (isset($this->streamEvents[$key])) { - $event = $this->streamEvents[$key]; - $flags = $this->streamFlags[$key] |= $flag; - - event_del($event); - event_set($event, $stream, EV_PERSIST | $flags, $this->streamCallback); - } else { - $event = event_new(); - - event_set($event, $stream, EV_PERSIST | $flag, $this->streamCallback); - event_base_set($event, $this->eventBase); - - $this->streamEvents[$key] = $event; - $this->streamFlags[$key] = $flag; - } - - event_add($event); - } - - /** - * Update the ext-libevent event resource for this stream to stop listening to - * the given event type, or remove it entirely if it's no longer needed. - * - * @param resource $stream - * @param integer $flag EV_READ or EV_WRITE - */ - private function unsubscribeStreamEvent($stream, $flag) - { - $key = (int) $stream; - - $flags = $this->streamFlags[$key] &= ~$flag; - - if (0 === $flags) { - $this->removeStream($stream); - - return; - } - - $event = $this->streamEvents[$key]; - - event_del($event); - event_set($event, $stream, EV_PERSIST | $flags, $this->streamCallback); - event_add($event); - } - /** * Create a callback used as the target of timer events. * From adf8b462f63851c58517191c768f6f9a5d9df59b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 10 Nov 2017 18:36:56 +0100 Subject: [PATCH 080/203] Remove all callable type hints for consistency and performance reasons --- src/ExtEventLoop.php | 14 +++++++------- src/ExtLibevLoop.php | 14 +++++++------- src/ExtLibeventLoop.php | 14 +++++++------- src/LoopInterface.php | 14 +++++++------- src/SignalsHandler.php | 6 +++--- src/StreamSelectLoop.php | 14 +++++++------- src/Tick/FutureTickQueue.php | 2 +- src/Timer/Timer.php | 2 +- 8 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index b1215de8..f81ce163 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -67,7 +67,7 @@ function ($signal) { $this->createStreamCallback(); } - public function addReadStream($stream, callable $listener) + public function addReadStream($stream, $listener) { $key = (int) $stream; @@ -77,7 +77,7 @@ public function addReadStream($stream, callable $listener) } } - public function addWriteStream($stream, callable $listener) + public function addWriteStream($stream, $listener) { $key = (int) $stream; @@ -124,7 +124,7 @@ private function removeStream($stream) } } - public function addTimer($interval, callable $callback) + public function addTimer($interval, $callback) { $timer = new Timer($interval, $callback, false); @@ -133,7 +133,7 @@ public function addTimer($interval, callable $callback) return $timer; } - public function addPeriodicTimer($interval, callable $callback) + public function addPeriodicTimer($interval, $callback) { $timer = new Timer($interval, $callback, true); @@ -150,17 +150,17 @@ public function cancelTimer(TimerInterface $timer) } } - public function futureTick(callable $listener) + public function futureTick($listener) { $this->futureTickQueue->add($listener); } - public function addSignal($signal, callable $listener) + public function addSignal($signal, $listener) { $this->signals->add($signal, $listener); } - public function removeSignal($signal, callable $listener) + public function removeSignal($signal, $listener) { $this->signals->remove($signal, $listener); } diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php index 0d5a4f09..fd1d4803 100644 --- a/src/ExtLibevLoop.php +++ b/src/ExtLibevLoop.php @@ -63,7 +63,7 @@ function ($signal) { ); } - public function addReadStream($stream, callable $listener) + public function addReadStream($stream, $listener) { if (isset($this->readEvents[(int) $stream])) { return; @@ -79,7 +79,7 @@ public function addReadStream($stream, callable $listener) $this->readEvents[(int) $stream] = $event; } - public function addWriteStream($stream, callable $listener) + public function addWriteStream($stream, $listener) { if (isset($this->writeEvents[(int) $stream])) { return; @@ -115,7 +115,7 @@ public function removeWriteStream($stream) } } - public function addTimer($interval, callable $callback) + public function addTimer($interval, $callback) { $timer = new Timer( $interval, $callback, false); @@ -134,7 +134,7 @@ public function addTimer($interval, callable $callback) return $timer; } - public function addPeriodicTimer($interval, callable $callback) + public function addPeriodicTimer($interval, $callback) { $timer = new Timer($interval, $callback, true); @@ -157,17 +157,17 @@ public function cancelTimer(TimerInterface $timer) } } - public function futureTick(callable $listener) + public function futureTick($listener) { $this->futureTickQueue->add($listener); } - public function addSignal($signal, callable $listener) + public function addSignal($signal, $listener) { $this->signals->add($signal, $listener); } - public function removeSignal($signal, callable $listener) + public function removeSignal($signal, $listener) { $this->signals->remove($signal, $listener); } diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index 30750afb..cce3914e 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -85,7 +85,7 @@ function ($signal) { $this->createStreamCallback(); } - public function addReadStream($stream, callable $listener) + public function addReadStream($stream, $listener) { $key = (int) $stream; @@ -95,7 +95,7 @@ public function addReadStream($stream, callable $listener) } } - public function addWriteStream($stream, callable $listener) + public function addWriteStream($stream, $listener) { $key = (int) $stream; @@ -144,7 +144,7 @@ private function removeStream($stream) } } - public function addTimer($interval, callable $callback) + public function addTimer($interval, $callback) { $timer = new Timer($interval, $callback, false); @@ -153,7 +153,7 @@ public function addTimer($interval, callable $callback) return $timer; } - public function addPeriodicTimer($interval, callable $callback) + public function addPeriodicTimer($interval, $callback) { $timer = new Timer($interval, $callback, true); @@ -174,17 +174,17 @@ public function cancelTimer(TimerInterface $timer) } } - public function futureTick(callable $listener) + public function futureTick($listener) { $this->futureTickQueue->add($listener); } - public function addSignal($signal, callable $listener) + public function addSignal($signal, $listener) { $this->signals->add($signal, $listener); } - public function removeSignal($signal, callable $listener) + public function removeSignal($signal, $listener) { $this->signals->remove($signal, $listener); } diff --git a/src/LoopInterface.php b/src/LoopInterface.php index 5467fb61..c5d50844 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -51,7 +51,7 @@ interface LoopInterface * @param callable $listener Invoked when the stream is ready. * @see self::removeReadStream() */ - public function addReadStream($stream, callable $listener); + public function addReadStream($stream, $listener); /** * [Advanced] Register a listener to be notified when a stream is ready to write. @@ -106,7 +106,7 @@ public function addReadStream($stream, callable $listener); * @param callable $listener Invoked when the stream is ready. * @see self::removeWriteStream() */ - public function addWriteStream($stream, callable $listener); + public function addWriteStream($stream, $listener); /** * Remove the read event listener for the given stream. @@ -178,7 +178,7 @@ public function removeWriteStream($stream); * * @return TimerInterface */ - public function addTimer($interval, callable $callback); + public function addTimer($interval, $callback); /** * Enqueue a callback to be invoked repeatedly after the given interval. @@ -248,7 +248,7 @@ public function addTimer($interval, callable $callback); * * @return TimerInterface */ - public function addPeriodicTimer($interval, callable $callback); + public function addPeriodicTimer($interval, $callback); /** * Cancel a pending timer. @@ -314,7 +314,7 @@ public function cancelTimer(TimerInterface $timer); * * @return void */ - public function futureTick(callable $listener); + public function futureTick($listener); /** * Register a listener to be notified when a signal has been caught by this process. @@ -356,7 +356,7 @@ public function futureTick(callable $listener); * * @return void */ - public function addSignal($signal, callable $listener); + public function addSignal($signal, $listener); /** * Removes a previously added signal listener. @@ -372,7 +372,7 @@ public function addSignal($signal, callable $listener); * * @return void */ - public function removeSignal($signal, callable $listener); + public function removeSignal($signal, $listener); /** * Run the event loop until there are no more tasks to perform. diff --git a/src/SignalsHandler.php b/src/SignalsHandler.php index c91bf1e2..3b8b530c 100644 --- a/src/SignalsHandler.php +++ b/src/SignalsHandler.php @@ -15,7 +15,7 @@ final class SignalsHandler private $on; private $off; - public function __construct(LoopInterface $loop, callable $on, callable $off) + public function __construct(LoopInterface $loop, $on, $off) { $this->loop = $loop; $this->on = $on; @@ -30,7 +30,7 @@ public function __destruct() } } - public function add($signal, callable $listener) + public function add($signal, $listener) { if (count($this->signals) == 0 && $this->timer === null) { /** @@ -53,7 +53,7 @@ public function add($signal, callable $listener) $this->signals[$signal][] = $listener; } - public function remove($signal, callable $listener) + public function remove($signal, $listener) { if (!isset($this->signals[$signal])) { return; diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index f8d11529..45581338 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -90,7 +90,7 @@ function ($signal) { ); } - public function addReadStream($stream, callable $listener) + public function addReadStream($stream, $listener) { $key = (int) $stream; @@ -100,7 +100,7 @@ public function addReadStream($stream, callable $listener) } } - public function addWriteStream($stream, callable $listener) + public function addWriteStream($stream, $listener) { $key = (int) $stream; @@ -130,7 +130,7 @@ public function removeWriteStream($stream) ); } - public function addTimer($interval, callable $callback) + public function addTimer($interval, $callback) { $timer = new Timer($interval, $callback, false); @@ -139,7 +139,7 @@ public function addTimer($interval, callable $callback) return $timer; } - public function addPeriodicTimer($interval, callable $callback) + public function addPeriodicTimer($interval, $callback) { $timer = new Timer($interval, $callback, true); @@ -153,12 +153,12 @@ public function cancelTimer(TimerInterface $timer) $this->timers->cancel($timer); } - public function futureTick(callable $listener) + public function futureTick($listener) { $this->futureTickQueue->add($listener); } - public function addSignal($signal, callable $listener) + public function addSignal($signal, $listener) { if ($this->pcntl === false) { throw new \BadMethodCallException('Event loop feature "signals" isn\'t supported by the "StreamSelectLoop"'); @@ -167,7 +167,7 @@ public function addSignal($signal, callable $listener) $this->signals->add($signal, $listener); } - public function removeSignal($signal, callable $listener) + public function removeSignal($signal, $listener) { $this->signals->remove($signal, $listener); } diff --git a/src/Tick/FutureTickQueue.php b/src/Tick/FutureTickQueue.php index eb65345d..c79afc56 100644 --- a/src/Tick/FutureTickQueue.php +++ b/src/Tick/FutureTickQueue.php @@ -28,7 +28,7 @@ public function __construct() * * @param callable $listener The callback to invoke. */ - public function add(callable $listener) + public function add($listener) { $this->queue->enqueue($listener); } diff --git a/src/Timer/Timer.php b/src/Timer/Timer.php index 8a8926c6..cf5286b1 100644 --- a/src/Timer/Timer.php +++ b/src/Timer/Timer.php @@ -25,7 +25,7 @@ final class Timer implements TimerInterface * @param callable $callback The callback that will be executed when this timer elapses * @param bool $periodic Whether the time is periodic */ - public function __construct($interval, callable $callback, $periodic = false) + public function __construct($interval, $callback, $periodic = false) { if ($interval < self::MIN_INTERVAL) { $interval = self::MIN_INTERVAL; From fdf007d914212c6a778880e21f9541314503c460 Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Fri, 22 Dec 2017 21:20:55 +0100 Subject: [PATCH 081/203] Fix usage of removed timer methods --- examples/95-benchmark-memory.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/95-benchmark-memory.php b/examples/95-benchmark-memory.php index 4eb2df46..e9cf37c9 100644 --- a/examples/95-benchmark-memory.php +++ b/examples/95-benchmark-memory.php @@ -26,8 +26,8 @@ $runs = 0; if (5 < $t) { - $loop->addTimer($t, function (TimerInterface $timer) { - $timer->getLoop()->stop(); + $loop->addTimer($t, function () use ($loop) { + $loop->stop(); }); } @@ -35,8 +35,8 @@ $loop->addPeriodicTimer(0.001, function () use (&$runs, $loop) { $runs++; - $loop->addPeriodicTimer(1, function (TimerInterface $timer) { - $timer->cancel(); + $loop->addPeriodicTimer(1, function (TimerInterface $timer) use ($loop) { + $loop->cancelTimer($timer); }); }); From a6aae860004d623a4787b08699e76a630ddacd6c Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Fri, 22 Dec 2017 21:21:56 +0100 Subject: [PATCH 082/203] Fix filename in docblock --- examples/95-benchmark-memory.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/95-benchmark-memory.php b/examples/95-benchmark-memory.php index e9cf37c9..084c4042 100644 --- a/examples/95-benchmark-memory.php +++ b/examples/95-benchmark-memory.php @@ -2,9 +2,9 @@ /** * Run the script indefinitely seconds with the loop from the factory and report every 2 seconds: - * php test-memory.php + * php 95-benchmark-memory.php * Run the script for 30 seconds with the stream_select loop and report every 10 seconds: - * php test-memory.php -t 30 -l StreamSelect -r 10 + * php 95-benchmark-memory.php -t 30 -l StreamSelect -r 10 */ use React\EventLoop\Factory; From 7fcbcffaf3d8f8f45116f260ade5a4942495d44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 27 Dec 2017 18:13:08 +0100 Subject: [PATCH 083/203] Avoid PHPUnit warnings by using dummy assertions --- tests/AbstractLoopTest.php | 2 ++ tests/Timer/TimersTest.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index f07f6877..296bdc7e 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -262,6 +262,8 @@ public function testRemoveInvalid() // remove a valid stream from the event loop that was never added in the first place $this->loop->removeReadStream($stream); $this->loop->removeWriteStream($stream); + + $this->assertTrue(true); } /** @test */ diff --git a/tests/Timer/TimersTest.php b/tests/Timer/TimersTest.php index c70a39cd..a2cb134b 100644 --- a/tests/Timer/TimersTest.php +++ b/tests/Timer/TimersTest.php @@ -25,5 +25,7 @@ public function testBlockedTimer() })); $timers->tick(); + + $this->assertTrue(true); } } From e4619fabaf7109e22f13078c079e5c5f70783396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 27 Dec 2017 19:42:25 +0100 Subject: [PATCH 084/203] Improve timer tests to be less fragile by not blocking the loop --- tests/Timer/AbstractTimerTest.php | 96 +++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 31 deletions(-) diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index 28c7a421..15202e41 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -8,69 +8,103 @@ abstract class AbstractTimerTest extends TestCase { abstract public function createLoop(); - public function testAddTimer() + public function testAddTimerReturnsNonPeriodicTimerInstance() { - // usleep is intentionally high + $loop = $this->createLoop(); + + $timer = $loop->addTimer(0.001, $this->expectCallableNever()); + + $this->assertInstanceOf('React\EventLoop\TimerInterface', $timer); + $this->assertFalse($timer->isPeriodic()); + } + public function testAddTimerWillBeInvokedOnceAndBlocksLoopWhenRunning() + { $loop = $this->createLoop(); $loop->addTimer(0.001, $this->expectCallableOnce()); - usleep(1000); - $this->tickLoop($loop); + + $start = microtime(true); + $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); } - public function testAddPeriodicTimer() + public function testAddPeriodicTimerReturnsPeriodicTimerInstance() { $loop = $this->createLoop(); - $loop->addPeriodicTimer(0.001, $this->expectCallableExactly(3)); - usleep(1000); - $this->tickLoop($loop); - usleep(1000); - $this->tickLoop($loop); - usleep(1000); - $this->tickLoop($loop); + $periodic = $loop->addPeriodicTimer(0.1, $this->expectCallableNever()); + + $this->assertInstanceOf('React\EventLoop\TimerInterface', $periodic); + $this->assertTrue($periodic->isPeriodic()); } - public function testAddPeriodicTimerWithCancel() + public function testAddPeriodicTimerWillBeInvokedUntilItIsCancelled() { $loop = $this->createLoop(); - $timer = $loop->addPeriodicTimer(0.001, $this->expectCallableExactly(2)); - - usleep(1000); - $this->tickLoop($loop); - usleep(1000); - $this->tickLoop($loop); + $periodic = $loop->addPeriodicTimer(0.1, $this->expectCallableExactly(3)); - $loop->cancelTimer($timer); + // 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->cancelTimer($periodic); + }); - usleep(1000); - $this->tickLoop($loop); + $loop->run(); } - public function testAddPeriodicTimerCancelsItself() + public function testAddPeriodicTimerWillBeInvokedWithMaximumAccuracyUntilItIsCancelled() { + $loop = $this->createLoop(); + $i = 0; + $periodic = $loop->addPeriodicTimer(0.001, function () use (&$i) { + ++$i; + }); + + $loop->addTimer(0.02, 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); + } + + public function testAddPeriodicTimerCancelsItself() + { $loop = $this->createLoop(); + $i = 0; $loop->addPeriodicTimer(0.001, function ($timer) use (&$i, $loop) { $i++; - if ($i == 2) { + if ($i === 5) { $loop->cancelTimer($timer); } }); - usleep(1000); - $this->tickLoop($loop); - usleep(1000); - $this->tickLoop($loop); - usleep(1000); - $this->tickLoop($loop); + $start = microtime(true); + $loop->run(); + $end = microtime(true); + + $this->assertEquals(5, $i); - $this->assertSame(2, $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); } public function testMinimumIntervalOneMicrosecond() From 2cbdc04b030d112409dd05088d8f6f89e233d439 Mon Sep 17 00:00:00 2001 From: Zhuk Sergey Date: Wed, 10 Jan 2018 22:51:24 +0300 Subject: [PATCH 085/203] small code cleanup --- src/ExtEventLoop.php | 1 - src/ExtLibevLoop.php | 1 - src/ExtLibeventLoop.php | 4 ++-- src/SignalsHandler.php | 6 ++---- src/StreamSelectLoop.php | 17 ----------------- tests/Timer/TimersTest.php | 4 ---- 6 files changed, 4 insertions(+), 29 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 213cba9c..1bf35605 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -7,7 +7,6 @@ use EventConfig as EventBaseConfig; use React\EventLoop\Tick\FutureTickQueue; use React\EventLoop\Timer\Timer; -use React\EventLoop\TimerInterface; use SplObjectStorage; /** diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php index 19927bb8..06da125b 100644 --- a/src/ExtLibevLoop.php +++ b/src/ExtLibevLoop.php @@ -8,7 +8,6 @@ use libev\TimerEvent; use React\EventLoop\Tick\FutureTickQueue; use React\EventLoop\Timer\Timer; -use React\EventLoop\TimerInterface; use SplObjectStorage; /** diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index 2a5fc984..db1bb65a 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -6,7 +6,6 @@ use EventBase; use React\EventLoop\Tick\FutureTickQueue; use React\EventLoop\Timer\Timer; -use React\EventLoop\TimerInterface; use SplObjectStorage; /** @@ -245,9 +244,10 @@ private function createTimerCallback() // Timer already cancelled ... if (!$this->timerEvents->contains($timer)) { return; + } // Reschedule periodic timers ... - } elseif ($timer->isPeriodic()) { + if ($timer->isPeriodic()) { event_add( $this->timerEvents[$timer], $timer->getInterval() * self::MICROSECONDS_PER_SECOND diff --git a/src/SignalsHandler.php b/src/SignalsHandler.php index 73ff768f..cbf1bf31 100644 --- a/src/SignalsHandler.php +++ b/src/SignalsHandler.php @@ -2,8 +2,6 @@ namespace React\EventLoop; -use React\EventLoop\TimerInterface; - /** * @internal */ @@ -32,7 +30,7 @@ public function __destruct() public function add($signal, $listener) { - if (count($this->signals) == 0 && $this->timer === null) { + if (empty($this->signals) && $this->timer === null) { /** * Timer to keep the loop alive as long as there are any signal handlers registered */ @@ -69,7 +67,7 @@ public function remove($signal, $listener) $off($signal); } - if (count($this->signals) == 0 && $this->timer instanceof TimerInterface) { + if (empty($this->signals) && $this->timer instanceof TimerInterface) { $this->loop->cancelTimer($this->timer); $this->timer = null; } diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 9596fa56..ed672ce7 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -5,7 +5,6 @@ use React\EventLoop\Signal\Pcntl; use React\EventLoop\Tick\FutureTickQueue; use React\EventLoop\Timer\Timer; -use React\EventLoop\TimerInterface; use React\EventLoop\Timer\Timers; /** @@ -277,20 +276,4 @@ private function streamSelect(array &$read, array &$write, $timeout) return 0; } - - /** - * Iterate over signal listeners for the given signal - * and call each of them with the signal as first - * argument. - * - * @param int $signal - * - * @return void - */ - private function handleSignal($signal) - { - foreach ($this->signals[$signal] as $listener) { - \call_user_func($listener, $signal); - } - } } diff --git a/tests/Timer/TimersTest.php b/tests/Timer/TimersTest.php index a2cb134b..b279478c 100644 --- a/tests/Timer/TimersTest.php +++ b/tests/Timer/TimersTest.php @@ -10,10 +10,6 @@ class TimersTest extends TestCase { public function testBlockedTimer() { - $loop = $this - ->getMockBuilder('React\EventLoop\LoopInterface') - ->getMock(); - $timers = new Timers(); $timers->tick(); From 7539cf9e1125bbc11e5e163e2ba4930d9f2b4ff2 Mon Sep 17 00:00:00 2001 From: nawarian Date: Sun, 14 Jan 2018 14:33:17 +0100 Subject: [PATCH 086/203] Reduces fwrite() call length to one chunk --- tests/AbstractLoopTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 296bdc7e..ec8dafe4 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -11,6 +11,8 @@ abstract class AbstractLoopTest extends TestCase private $tickTimeout; + const PHP_DEFAULT_CHUNK_SIZE = 8192; + public function setUp() { // HHVM is a bit slow, so give it more time @@ -228,7 +230,7 @@ public function testRemoveReadAndWriteStreamFromLoopOnceResourceClosesOnEndOfFil }); // send data and close stream - fwrite($other, str_repeat('.', 60000)); + fwrite($other, str_repeat('.', static::PHP_DEFAULT_CHUNK_SIZE)); $this->loop->addTimer(0.01, function () use ($other) { fclose($other); }); From 0f8c6b415d10a6c993717aff760c8407fec8dd17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 17 Jan 2018 18:59:34 +0100 Subject: [PATCH 087/203] Properly clean up event watchers for ext-event and ext-libev --- src/ExtEventLoop.php | 2 +- src/ExtLibevLoop.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 1bf35605..9db1de9d 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -57,7 +57,7 @@ function ($signal) { }, function ($signal) { if ($this->signals->count($signal) === 0) { - $this->signalEvents[$signal]->del(); + $this->signalEvents[$signal]->free(); unset($this->signalEvents[$signal]); } } diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php index 06da125b..58e75b9f 100644 --- a/src/ExtLibevLoop.php +++ b/src/ExtLibevLoop.php @@ -55,6 +55,7 @@ function ($signal) { }, function ($signal) { if ($this->signals->count($signal) === 0) { + $this->signalEvents[$signal]->stop(); $this->loop->remove($this->signalEvents[$signal]); unset($this->signalEvents[$signal]); } @@ -100,6 +101,7 @@ public function removeReadStream($stream) if (isset($this->readEvents[$key])) { $this->readEvents[$key]->stop(); + $this->loop->remove($this->readEvents[$key]); unset($this->readEvents[$key]); } } @@ -110,6 +112,7 @@ public function removeWriteStream($stream) if (isset($this->writeEvents[$key])) { $this->writeEvents[$key]->stop(); + $this->loop->remove($this->writeEvents[$key]); unset($this->writeEvents[$key]); } } From e6c7785fb278fda573f856be6417345bad8b3901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 15 Jan 2018 18:21:41 +0100 Subject: [PATCH 088/203] Simplify removing signal handlers --- src/ExtEventLoop.php | 11 +++++------ src/ExtLibevLoop.php | 13 ++++++------- src/ExtLibeventLoop.php | 13 ++++++------- src/SignalsHandler.php | 15 +-------------- src/StreamSelectLoop.php | 13 ++++++++----- tests/AbstractLoopTest.php | 6 ++++++ tests/SignalsHandlerTest.php | 17 ----------------- 7 files changed, 32 insertions(+), 56 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 9db1de9d..2f6e31c4 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -54,12 +54,6 @@ function ($signal) { $f = $g; }); $this->signalEvents[$signal]->add(); - }, - function ($signal) { - if ($this->signals->count($signal) === 0) { - $this->signalEvents[$signal]->free(); - unset($this->signalEvents[$signal]); - } } ); @@ -172,6 +166,11 @@ public function addSignal($signal, $listener) public function removeSignal($signal, $listener) { $this->signals->remove($signal, $listener); + + if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) { + $this->signalEvents[$signal]->free(); + unset($this->signalEvents[$signal]); + } } public function run() diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php index 58e75b9f..9cf49922 100644 --- a/src/ExtLibevLoop.php +++ b/src/ExtLibevLoop.php @@ -52,13 +52,6 @@ function ($signal) { $f = $g; }, $signal); $this->loop->add($this->signalEvents[$signal]); - }, - function ($signal) { - if ($this->signals->count($signal) === 0) { - $this->signalEvents[$signal]->stop(); - $this->loop->remove($this->signalEvents[$signal]); - unset($this->signalEvents[$signal]); - } } ); } @@ -172,6 +165,12 @@ public function addSignal($signal, $listener) 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() diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index db1bb65a..30d178ca 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -70,13 +70,6 @@ function ($signal) { }); event_base_set($this->signalEvents[$signal], $this->eventBase); event_add($this->signalEvents[$signal]); - }, - function ($signal) { - if ($this->signals->count($signal) === 0) { - event_del($this->signalEvents[$signal]); - event_free($this->signalEvents[$signal]); - unset($this->signalEvents[$signal]); - } } ); @@ -190,6 +183,12 @@ public function addSignal($signal, $listener) 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() diff --git a/src/SignalsHandler.php b/src/SignalsHandler.php index cbf1bf31..4b5441f8 100644 --- a/src/SignalsHandler.php +++ b/src/SignalsHandler.php @@ -11,21 +11,11 @@ final class SignalsHandler private $timer; private $signals = []; private $on; - private $off; - public function __construct(LoopInterface $loop, $on, $off) + public function __construct(LoopInterface $loop, $on) { $this->loop = $loop; $this->on = $on; - $this->off = $off; - } - - public function __destruct() - { - $off = $this->off; - foreach ($this->signals as $signal => $listeners) { - $off($signal); - } } public function add($signal, $listener) @@ -62,9 +52,6 @@ public function remove($signal, $listener) if (isset($this->signals[$signal]) && \count($this->signals[$signal]) === 0) { unset($this->signals[$signal]); - - $off = $this->off; - $off($signal); } if (empty($this->signals) && $this->timer instanceof TimerInterface) { diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index ed672ce7..4aeafe38 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -80,11 +80,6 @@ function ($signal) { $g = $f; $f = $g; }); - }, - function ($signal) { - if ($this->signals->count($signal) === 0) { - \pcntl_signal($signal, SIG_DFL); - } } ); } @@ -168,7 +163,15 @@ public function addSignal($signal, $listener) public function removeSignal($signal, $listener) { + if (!$this->signals->count($signal)) { + return; + } + $this->signals->remove($signal, $listener); + + if ($this->signals->count($signal) === 0) { + \pcntl_signal($signal, SIG_DFL); + } } public function run() diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 296bdc7e..6cac6eb1 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -475,6 +475,12 @@ function () { $this->loop->run(); } + public function testRemoveSignalNotRegisteredIsNoOp() + { + $this->loop->removeSignal(SIGINT, function () { }); + $this->assertTrue(true); + } + public function testSignal() { if (!function_exists('posix_kill') || !function_exists('posix_getpid')) { diff --git a/tests/SignalsHandlerTest.php b/tests/SignalsHandlerTest.php index 04f37151..bd594f71 100644 --- a/tests/SignalsHandlerTest.php +++ b/tests/SignalsHandlerTest.php @@ -11,7 +11,6 @@ public function testEmittedEventsAndCallHandling() { $callCount = 0; $onCount = 0; - $offCount = 0; $func = function () use (&$callCount) { $callCount++; }; @@ -19,74 +18,58 @@ public function testEmittedEventsAndCallHandling() Factory::create(), function () use (&$onCount) { $onCount++; - }, - function () use (&$offCount) { - $offCount++; } ); $this->assertSame(0, $callCount); $this->assertSame(0, $onCount); - $this->assertSame(0, $offCount); $signals->add(SIGUSR1, $func); $this->assertSame(0, $callCount); $this->assertSame(1, $onCount); - $this->assertSame(0, $offCount); $signals->add(SIGUSR1, $func); $this->assertSame(0, $callCount); $this->assertSame(1, $onCount); - $this->assertSame(0, $offCount); $signals->add(SIGUSR1, $func); $this->assertSame(0, $callCount); $this->assertSame(1, $onCount); - $this->assertSame(0, $offCount); $signals->call(SIGUSR1); $this->assertSame(1, $callCount); $this->assertSame(1, $onCount); - $this->assertSame(0, $offCount); $signals->add(SIGUSR2, $func); $this->assertSame(1, $callCount); $this->assertSame(2, $onCount); - $this->assertSame(0, $offCount); $signals->add(SIGUSR2, $func); $this->assertSame(1, $callCount); $this->assertSame(2, $onCount); - $this->assertSame(0, $offCount); $signals->call(SIGUSR2); $this->assertSame(2, $callCount); $this->assertSame(2, $onCount); - $this->assertSame(0, $offCount); $signals->remove(SIGUSR2, $func); $this->assertSame(2, $callCount); $this->assertSame(2, $onCount); - $this->assertSame(1, $offCount); $signals->remove(SIGUSR2, $func); $this->assertSame(2, $callCount); $this->assertSame(2, $onCount); - $this->assertSame(1, $offCount); $signals->call(SIGUSR2); $this->assertSame(2, $callCount); $this->assertSame(2, $onCount); - $this->assertSame(1, $offCount); $signals->remove(SIGUSR1, $func); $this->assertSame(2, $callCount); $this->assertSame(2, $onCount); - $this->assertSame(2, $offCount); $signals->call(SIGUSR1); $this->assertSame(2, $callCount); $this->assertSame(2, $onCount); - $this->assertSame(2, $offCount); } } From 8afd1f3c3de4b79238782e6556ac1a96fbd0b87f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 15 Jan 2018 18:37:40 +0100 Subject: [PATCH 089/203] Simplify adding signal handlers --- src/ExtEventLoop.php | 28 +++++++++++++--------------- src/ExtLibevLoop.php | 28 +++++++++++++--------------- src/ExtLibeventLoop.php | 32 +++++++++++++++----------------- src/SignalsHandler.php | 7 +------ src/StreamSelectLoop.php | 26 +++++++++++++------------- tests/SignalsHandlerTest.php | 19 +------------------ 6 files changed, 56 insertions(+), 84 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 2f6e31c4..ed9edfc7 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -41,21 +41,7 @@ public function __construct(EventBaseConfig $config = null) $this->eventBase = new EventBase($config); $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); - - $this->signals = new SignalsHandler( - $this, - function ($signal) { - $this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, $f = function () use ($signal, &$f) { - $this->signals->call($signal); - // Ensure there are two copies of the callable around until it has been executed. - // For more information see: https://bugs.php.net/bug.php?id=62452 - // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. - $g = $f; - $f = $g; - }); - $this->signalEvents[$signal]->add(); - } - ); + $this->signals = new SignalsHandler($this); $this->createTimerCallback(); $this->createStreamCallback(); @@ -161,6 +147,18 @@ public function futureTick($listener) public function addSignal($signal, $listener) { $this->signals->add($signal, $listener); + + if (!isset($this->signalEvents[$signal])) { + $this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, $f = function () use ($signal, &$f) { + $this->signals->call($signal); + // Ensure there are two copies of the callable around until it has been executed. + // For more information see: https://bugs.php.net/bug.php?id=62452 + // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. + $g = $f; + $f = $g; + }); + $this->signalEvents[$signal]->add(); + } } public function removeSignal($signal, $listener) diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php index 9cf49922..1dd5518b 100644 --- a/src/ExtLibevLoop.php +++ b/src/ExtLibevLoop.php @@ -39,21 +39,7 @@ public function __construct() $this->loop = new EventLoop(); $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); - - $this->signals = new SignalsHandler( - $this, - function ($signal) { - $this->signalEvents[$signal] = new SignalEvent($f = function () use ($signal, &$f) { - $this->signals->call($signal); - // Ensure there are two copies of the callable around until it has been executed. - // For more information see: https://bugs.php.net/bug.php?id=62452 - // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. - $g = $f; - $f = $g; - }, $signal); - $this->loop->add($this->signalEvents[$signal]); - } - ); + $this->signals = new SignalsHandler($this); } public function addReadStream($stream, $listener) @@ -160,6 +146,18 @@ public function futureTick($listener) public function addSignal($signal, $listener) { $this->signals->add($signal, $listener); + + if (!isset($this->signalEvents[$signal])) { + $this->signalEvents[$signal] = new SignalEvent($f = function () use ($signal, &$f) { + $this->signals->call($signal); + // Ensure there are two copies of the callable around until it has been executed. + // For more information see: https://bugs.php.net/bug.php?id=62452 + // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. + $g = $f; + $f = $g; + }, $signal); + $this->loop->add($this->signalEvents[$signal]); + } } public function removeSignal($signal, $listener) diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index 30d178ca..d9dc2066 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -55,23 +55,7 @@ public function __construct() $this->eventBase = event_base_new(); $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); - - $this->signals = new SignalsHandler( - $this, - function ($signal) { - $this->signalEvents[$signal] = event_new(); - event_set($this->signalEvents[$signal], $signal, EV_PERSIST | EV_SIGNAL, $f = function () use ($signal, &$f) { - $this->signals->call($signal); - // Ensure there are two copies of the callable around until it has been executed. - // For more information see: https://bugs.php.net/bug.php?id=62452 - // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. - $g = $f; - $f = $g; - }); - event_base_set($this->signalEvents[$signal], $this->eventBase); - event_add($this->signalEvents[$signal]); - } - ); + $this->signals = new SignalsHandler($this); $this->createTimerCallback(); $this->createStreamCallback(); @@ -178,6 +162,20 @@ public function futureTick($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, $f = function () use ($signal, &$f) { + $this->signals->call($signal); + // Ensure there are two copies of the callable around until it has been executed. + // For more information see: https://bugs.php.net/bug.php?id=62452 + // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. + $g = $f; + $f = $g; + }); + event_base_set($this->signalEvents[$signal], $this->eventBase); + event_add($this->signalEvents[$signal]); + } } public function removeSignal($signal, $listener) diff --git a/src/SignalsHandler.php b/src/SignalsHandler.php index 4b5441f8..187965ab 100644 --- a/src/SignalsHandler.php +++ b/src/SignalsHandler.php @@ -10,12 +10,10 @@ final class SignalsHandler private $loop; private $timer; private $signals = []; - private $on; - public function __construct(LoopInterface $loop, $on) + public function __construct(LoopInterface $loop) { $this->loop = $loop; - $this->on = $on; } public function add($signal, $listener) @@ -29,9 +27,6 @@ public function add($signal, $listener) if (!isset($this->signals[$signal])) { $this->signals[$signal] = []; - - $on = $this->on; - $on($signal); } if (in_array($listener, $this->signals[$signal])) { diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 4aeafe38..6a46008f 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -69,19 +69,7 @@ public function __construct() $this->futureTickQueue = new FutureTickQueue(); $this->timers = new Timers(); $this->pcntl = extension_loaded('pcntl'); - $this->signals = new SignalsHandler( - $this, - function ($signal) { - \pcntl_signal($signal, $f = function ($signal) use (&$f) { - $this->signals->call($signal); - // Ensure there are two copies of the callable around until it has been executed. - // For more information see: https://bugs.php.net/bug.php?id=62452 - // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. - $g = $f; - $f = $g; - }); - } - ); + $this->signals = new SignalsHandler($this); } public function addReadStream($stream, $listener) @@ -158,7 +146,19 @@ public function addSignal($signal, $listener) throw new \BadMethodCallException('Event loop feature "signals" isn\'t supported by the "StreamSelectLoop"'); } + $first = $this->signals->count($signal) === 0; $this->signals->add($signal, $listener); + + if ($first) { + \pcntl_signal($signal, $f = function ($signal) use (&$f) { + $this->signals->call($signal); + // Ensure there are two copies of the callable around until it has been executed. + // For more information see: https://bugs.php.net/bug.php?id=62452 + // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. + $g = $f; + $f = $g; + }); + } } public function removeSignal($signal, $listener) diff --git a/tests/SignalsHandlerTest.php b/tests/SignalsHandlerTest.php index bd594f71..cbea8870 100644 --- a/tests/SignalsHandlerTest.php +++ b/tests/SignalsHandlerTest.php @@ -10,66 +10,49 @@ final class SignalsHandlerTest extends TestCase public function testEmittedEventsAndCallHandling() { $callCount = 0; - $onCount = 0; $func = function () use (&$callCount) { $callCount++; }; $signals = new SignalsHandler( - Factory::create(), - function () use (&$onCount) { - $onCount++; - } + Factory::create() ); $this->assertSame(0, $callCount); - $this->assertSame(0, $onCount); $signals->add(SIGUSR1, $func); $this->assertSame(0, $callCount); - $this->assertSame(1, $onCount); $signals->add(SIGUSR1, $func); $this->assertSame(0, $callCount); - $this->assertSame(1, $onCount); $signals->add(SIGUSR1, $func); $this->assertSame(0, $callCount); - $this->assertSame(1, $onCount); $signals->call(SIGUSR1); $this->assertSame(1, $callCount); - $this->assertSame(1, $onCount); $signals->add(SIGUSR2, $func); $this->assertSame(1, $callCount); - $this->assertSame(2, $onCount); $signals->add(SIGUSR2, $func); $this->assertSame(1, $callCount); - $this->assertSame(2, $onCount); $signals->call(SIGUSR2); $this->assertSame(2, $callCount); - $this->assertSame(2, $onCount); $signals->remove(SIGUSR2, $func); $this->assertSame(2, $callCount); - $this->assertSame(2, $onCount); $signals->remove(SIGUSR2, $func); $this->assertSame(2, $callCount); - $this->assertSame(2, $onCount); $signals->call(SIGUSR2); $this->assertSame(2, $callCount); - $this->assertSame(2, $onCount); $signals->remove(SIGUSR1, $func); $this->assertSame(2, $callCount); - $this->assertSame(2, $onCount); $signals->call(SIGUSR1); $this->assertSame(2, $callCount); - $this->assertSame(2, $onCount); } } From 3e4421bf6437e5aeda762d9d452e1799c8190fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 20 Jan 2018 10:25:25 +0100 Subject: [PATCH 090/203] Simplify checking signal watchers and remove circular reference --- src/ExtEventLoop.php | 4 ++-- src/ExtLibevLoop.php | 4 ++-- src/ExtLibeventLoop.php | 4 ++-- src/SignalsHandler.php | 24 +++++------------------- src/StreamSelectLoop.php | 6 +++--- tests/SignalsHandlerTest.php | 5 +---- 6 files changed, 15 insertions(+), 32 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index ed9edfc7..2c7d61e4 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -41,7 +41,7 @@ public function __construct(EventBaseConfig $config = null) $this->eventBase = new EventBase($config); $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); - $this->signals = new SignalsHandler($this); + $this->signals = new SignalsHandler(); $this->createTimerCallback(); $this->createStreamCallback(); @@ -181,7 +181,7 @@ public function run() $flags = EventBase::LOOP_ONCE; if (!$this->running || !$this->futureTickQueue->isEmpty()) { $flags |= EventBase::LOOP_NONBLOCK; - } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count()) { + } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) { break; } diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php index 1dd5518b..adc3d9b7 100644 --- a/src/ExtLibevLoop.php +++ b/src/ExtLibevLoop.php @@ -39,7 +39,7 @@ public function __construct() $this->loop = new EventLoop(); $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); - $this->signals = new SignalsHandler($this); + $this->signals = new SignalsHandler(); } public function addReadStream($stream, $listener) @@ -181,7 +181,7 @@ public function run() $flags = EventLoop::RUN_ONCE; if (!$this->running || !$this->futureTickQueue->isEmpty()) { $flags |= EventLoop::RUN_NOWAIT; - } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count()) { + } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) { break; } diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index d9dc2066..5b3e6392 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -55,7 +55,7 @@ public function __construct() $this->eventBase = event_base_new(); $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); - $this->signals = new SignalsHandler($this); + $this->signals = new SignalsHandler(); $this->createTimerCallback(); $this->createStreamCallback(); @@ -199,7 +199,7 @@ public function run() $flags = EVLOOP_ONCE; if (!$this->running || !$this->futureTickQueue->isEmpty()) { $flags |= EVLOOP_NONBLOCK; - } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count()) { + } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) { break; } diff --git a/src/SignalsHandler.php b/src/SignalsHandler.php index 187965ab..2614e03c 100644 --- a/src/SignalsHandler.php +++ b/src/SignalsHandler.php @@ -7,24 +7,10 @@ */ final class SignalsHandler { - private $loop; - private $timer; private $signals = []; - public function __construct(LoopInterface $loop) - { - $this->loop = $loop; - } - public function add($signal, $listener) { - if (empty($this->signals) && $this->timer === null) { - /** - * Timer to keep the loop alive as long as there are any signal handlers registered - */ - $this->timer = $this->loop->addPeriodicTimer(300, function () {}); - } - if (!isset($this->signals[$signal])) { $this->signals[$signal] = []; } @@ -48,11 +34,6 @@ public function remove($signal, $listener) if (isset($this->signals[$signal]) && \count($this->signals[$signal]) === 0) { unset($this->signals[$signal]); } - - if (empty($this->signals) && $this->timer instanceof TimerInterface) { - $this->loop->cancelTimer($this->timer); - $this->timer = null; - } } public function call($signal) @@ -74,4 +55,9 @@ public function count($signal) return \count($this->signals[$signal]); } + + public function isEmpty() + { + return !$this->signals; + } } diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 6a46008f..7d3013b7 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -69,7 +69,7 @@ public function __construct() $this->futureTickQueue = new FutureTickQueue(); $this->timers = new Timers(); $this->pcntl = extension_loaded('pcntl'); - $this->signals = new SignalsHandler($this); + $this->signals = new SignalsHandler(); } public function addReadStream($stream, $listener) @@ -200,8 +200,8 @@ public function run() $timeout = $timeout > PHP_INT_MAX ? PHP_INT_MAX : (int)$timeout; } - // The only possible event is stream activity, so wait forever ... - } elseif ($this->readStreams || $this->writeStreams) { + // The only possible event is stream or signal activity, so wait forever ... + } elseif ($this->readStreams || $this->writeStreams || !$this->signals->isEmpty()) { $timeout = null; // There's nothing left to do ... diff --git a/tests/SignalsHandlerTest.php b/tests/SignalsHandlerTest.php index cbea8870..f8b7df3d 100644 --- a/tests/SignalsHandlerTest.php +++ b/tests/SignalsHandlerTest.php @@ -2,7 +2,6 @@ namespace React\Tests\EventLoop; -use React\EventLoop\Factory; use React\EventLoop\SignalsHandler; final class SignalsHandlerTest extends TestCase @@ -13,9 +12,7 @@ public function testEmittedEventsAndCallHandling() $func = function () use (&$callCount) { $callCount++; }; - $signals = new SignalsHandler( - Factory::create() - ); + $signals = new SignalsHandler(); $this->assertSame(0, $callCount); From f261f475cc19a5c668fc51717004555d316c3f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 28 Jan 2018 16:34:56 +0100 Subject: [PATCH 091/203] Avoid cyclic callback references --- src/ExtEventLoop.php | 9 +-------- src/ExtLibevLoop.php | 7 +------ src/ExtLibeventLoop.php | 9 +-------- src/StreamSelectLoop.php | 9 +-------- 4 files changed, 4 insertions(+), 30 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 2c7d61e4..53bbc974 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -149,14 +149,7 @@ public function addSignal($signal, $listener) $this->signals->add($signal, $listener); if (!isset($this->signalEvents[$signal])) { - $this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, $f = function () use ($signal, &$f) { - $this->signals->call($signal); - // Ensure there are two copies of the callable around until it has been executed. - // For more information see: https://bugs.php.net/bug.php?id=62452 - // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. - $g = $f; - $f = $g; - }); + $this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, array($this->signals, 'call')); $this->signalEvents[$signal]->add(); } } diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php index adc3d9b7..587bb044 100644 --- a/src/ExtLibevLoop.php +++ b/src/ExtLibevLoop.php @@ -148,13 +148,8 @@ public function addSignal($signal, $listener) $this->signals->add($signal, $listener); if (!isset($this->signalEvents[$signal])) { - $this->signalEvents[$signal] = new SignalEvent($f = function () use ($signal, &$f) { + $this->signalEvents[$signal] = new SignalEvent(function () use ($signal) { $this->signals->call($signal); - // Ensure there are two copies of the callable around until it has been executed. - // For more information see: https://bugs.php.net/bug.php?id=62452 - // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. - $g = $f; - $f = $g; }, $signal); $this->loop->add($this->signalEvents[$signal]); } diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index 5b3e6392..da181a28 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -165,14 +165,7 @@ public function addSignal($signal, $listener) if (!isset($this->signalEvents[$signal])) { $this->signalEvents[$signal] = event_new(); - event_set($this->signalEvents[$signal], $signal, EV_PERSIST | EV_SIGNAL, $f = function () use ($signal, &$f) { - $this->signals->call($signal); - // Ensure there are two copies of the callable around until it has been executed. - // For more information see: https://bugs.php.net/bug.php?id=62452 - // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. - $g = $f; - $f = $g; - }); + 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]); } diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 7d3013b7..55bb1b8a 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -150,14 +150,7 @@ public function addSignal($signal, $listener) $this->signals->add($signal, $listener); if ($first) { - \pcntl_signal($signal, $f = function ($signal) use (&$f) { - $this->signals->call($signal); - // Ensure there are two copies of the callable around until it has been executed. - // For more information see: https://bugs.php.net/bug.php?id=62452 - // Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped. - $g = $f; - $f = $g; - }); + \pcntl_signal($signal, array($this->signals, 'call')); } } From c4f0e4511571260b9c61afd8dbc6ad073af91910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 14 Jan 2018 14:52:47 +0100 Subject: [PATCH 092/203] [RFC] Improve compatibility with legacy versions --- .travis.yml | 4 ++ README.md | 4 +- composer.json | 2 +- src/ExtEventLoop.php | 31 +++++----- src/ExtLibevLoop.php | 19 +++--- src/ExtLibeventLoop.php | 34 ++++++----- src/SignalsHandler.php | 4 +- src/StreamSelectLoop.php | 8 +-- tests/AbstractLoopTest.php | 105 +++++++++++++++++++-------------- tests/StreamSelectLoopTest.php | 20 ++++--- travis-init.sh | 4 +- 11 files changed, 135 insertions(+), 100 deletions(-) diff --git a/.travis.yml b/.travis.yml index b51dcb9b..7af713a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: php php: +# - 5.3 # requires old distro, see below - 5.4 - 5.5 - 5.6 @@ -13,6 +14,9 @@ php: dist: trusty matrix: + include: + - php: 5.3 + dist: precise allow_failures: - php: hhvm diff --git a/README.md b/README.md index 7d7e0f38..7a30ec15 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ A `stream_select()` based event loop. This uses the [`stream_select()`](http://php.net/manual/en/function.stream-select.php) function and is the only implementation which works out of the box with PHP. -This event loop works out of the box on PHP 5.4 through PHP 7+ and HHVM. +This 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 @@ -574,7 +574,7 @@ $ composer require react/event-loop ``` This project aims to run on any platform and thus does not require any PHP -extensions and supports running on legacy PHP 5.4 through current PHP 7+ and +extensions and supports running on legacy PHP 5.3 through current PHP 7+ and HHVM. It's *highly recommended to use PHP 7+* for this project. diff --git a/composer.json b/composer.json index ad14cf4b..05afe65a 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "keywords": ["event-loop", "asynchronous"], "license": "MIT", "require": { - "php": ">=5.4.0" + "php": ">=5.3.0" }, "require-dev": { "phpunit/phpunit": "~4.8.35 || ^5.7 || ^6.4" diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 53bbc974..4146fc18 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -26,15 +26,15 @@ final class ExtEventLoop implements LoopInterface private $timerCallback; private $timerEvents; private $streamCallback; - private $readEvents = []; - private $writeEvents = []; - private $readListeners = []; - private $writeListeners = []; - private $readRefs = []; - private $writeRefs = []; + private $readEvents = array(); + private $writeEvents = array(); + private $readListeners = array(); + private $writeListeners = array(); + private $readRefs = array(); + private $writeRefs = array(); private $running; private $signals; - private $signalEvents = []; + private $signalEvents = array(); public function __construct(EventBaseConfig $config = null) { @@ -215,10 +215,11 @@ private function scheduleTimer(TimerInterface $timer) */ private function createTimerCallback() { - $this->timerCallback = function ($_, $__, $timer) { + $timers = $this->timerEvents; + $this->timerCallback = function ($_, $__, $timer) use ($timers) { call_user_func($timer->getCallback(), $timer); - if (!$timer->isPeriodic() && $this->timerEvents->contains($timer)) { + if (!$timer->isPeriodic() && $timers->contains($timer)) { $this->cancelTimer($timer); } }; @@ -233,15 +234,17 @@ private function createTimerCallback() */ private function createStreamCallback() { - $this->streamCallback = function ($stream, $flags) { + $read =& $this->readListeners; + $write =& $this->writeListeners; + $this->streamCallback = function ($stream, $flags) use (&$read, &$write) { $key = (int) $stream; - if (Event::READ === (Event::READ & $flags) && isset($this->readListeners[$key])) { - call_user_func($this->readListeners[$key], $stream); + if (Event::READ === (Event::READ & $flags) && isset($read[$key])) { + call_user_func($read[$key], $stream); } - if (Event::WRITE === (Event::WRITE & $flags) && isset($this->writeListeners[$key])) { - call_user_func($this->writeListeners[$key], $stream); + if (Event::WRITE === (Event::WRITE & $flags) && isset($write[$key])) { + call_user_func($write[$key], $stream); } }; } diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php index 587bb044..9c679895 100644 --- a/src/ExtLibevLoop.php +++ b/src/ExtLibevLoop.php @@ -28,11 +28,11 @@ final class ExtLibevLoop implements LoopInterface private $loop; private $futureTickQueue; private $timerEvents; - private $readEvents = []; - private $writeEvents = []; + private $readEvents = array(); + private $writeEvents = array(); private $running; private $signals; - private $signalEvents = []; + private $signalEvents = array(); public function __construct() { @@ -100,11 +100,13 @@ public function addTimer($interval, $callback) { $timer = new Timer( $interval, $callback, false); - $callback = function () use ($timer) { + $that = $this; + $timers = $this->timerEvents; + $callback = function () use ($timer, $timers, $that) { call_user_func($timer->getCallback(), $timer); - if ($this->timerEvents->contains($timer)) { - $this->cancelTimer($timer); + if ($timers->contains($timer)) { + $that->cancelTimer($timer); } }; @@ -148,8 +150,9 @@ public function addSignal($signal, $listener) $this->signals->add($signal, $listener); if (!isset($this->signalEvents[$signal])) { - $this->signalEvents[$signal] = new SignalEvent(function () use ($signal) { - $this->signals->call($signal); + $signals = $this->signals; + $this->signalEvents[$signal] = new SignalEvent(function () use ($signals, $signal) { + $signals->call($signal); }, $signal); $this->loop->add($this->signalEvents[$signal]); } diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index da181a28..5bc265e2 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -42,13 +42,13 @@ final class ExtLibeventLoop implements LoopInterface private $timerCallback; private $timerEvents; private $streamCallback; - private $readEvents = []; - private $writeEvents = []; - private $readListeners = []; - private $writeListeners = []; + private $readEvents = array(); + private $writeEvents = array(); + private $readListeners = array(); + private $writeListeners = array(); private $running; private $signals; - private $signalEvents = []; + private $signalEvents = array(); public function __construct() { @@ -228,24 +228,26 @@ private function scheduleTimer(TimerInterface $timer) */ private function createTimerCallback() { - $this->timerCallback = function ($_, $__, $timer) { + $that = $this; + $timers = $this->timerEvents; + $this->timerCallback = function ($_, $__, $timer) use ($timers, $that) { call_user_func($timer->getCallback(), $timer); // Timer already cancelled ... - if (!$this->timerEvents->contains($timer)) { + if (!$timers->contains($timer)) { return; } // Reschedule periodic timers ... if ($timer->isPeriodic()) { event_add( - $this->timerEvents[$timer], - $timer->getInterval() * self::MICROSECONDS_PER_SECOND + $timers[$timer], + $timer->getInterval() * ExtLibeventLoop::MICROSECONDS_PER_SECOND ); // Clean-up one shot timers ... } else { - $this->cancelTimer($timer); + $that->cancelTimer($timer); } }; } @@ -259,15 +261,17 @@ private function createTimerCallback() */ private function createStreamCallback() { - $this->streamCallback = function ($stream, $flags) { + $read =& $this->readListeners; + $write =& $this->writeListeners; + $this->streamCallback = function ($stream, $flags) use (&$read, &$write) { $key = (int) $stream; - if (EV_READ === (EV_READ & $flags) && isset($this->readListeners[$key])) { - call_user_func($this->readListeners[$key], $stream); + if (EV_READ === (EV_READ & $flags) && isset($read[$key])) { + call_user_func($read[$key], $stream); } - if (EV_WRITE === (EV_WRITE & $flags) && isset($this->writeListeners[$key])) { - call_user_func($this->writeListeners[$key], $stream); + if (EV_WRITE === (EV_WRITE & $flags) && isset($write[$key])) { + call_user_func($write[$key], $stream); } }; } diff --git a/src/SignalsHandler.php b/src/SignalsHandler.php index 2614e03c..523e1ca1 100644 --- a/src/SignalsHandler.php +++ b/src/SignalsHandler.php @@ -7,12 +7,12 @@ */ final class SignalsHandler { - private $signals = []; + private $signals = array(); public function add($signal, $listener) { if (!isset($this->signals[$signal])) { - $this->signals[$signal] = []; + $this->signals[$signal] = array(); } if (in_array($listener, $this->signals[$signal])) { diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 55bb1b8a..e82e9e47 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -56,10 +56,10 @@ final class StreamSelectLoop implements LoopInterface private $futureTickQueue; private $timers; - private $readStreams = []; - private $readListeners = []; - private $writeStreams = []; - private $writeListeners = []; + private $readStreams = array(); + private $readListeners = array(); + private $writeStreams = array(); + private $writeListeners = array(); private $running; private $pcntl = false; private $signals; diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 6cac6eb1..2a30b48c 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -82,14 +82,16 @@ private function subAddReadStreamReceivesDataFromStreamReference() fwrite($input, 'hello'); fclose($input); - $this->loop->addReadStream($output, function ($output) { + $loop = $this->loop; + $received =& $this->received; + $loop->addReadStream($output, function ($output) use ($loop, &$received) { $chunk = fread($output, 1024); if ($chunk === '') { - $this->received .= 'X'; - $this->loop->removeReadStream($output); + $received .= 'X'; + $loop->removeReadStream($output); fclose($output); } else { - $this->received .= '[' . $chunk . ']'; + $received .= '[' . $chunk . ']'; } }); } @@ -194,9 +196,10 @@ public function testRemoveReadAndWriteStreamFromLoopOnceResourceClosesEndsLoop() $this->loop->addWriteStream($stream, function () { }); // remove stream when the stream is readable (closes) - $this->loop->addReadStream($stream, function ($stream) { - $this->loop->removeReadStream($stream); - $this->loop->removeWriteStream($stream); + $loop = $this->loop; + $loop->addReadStream($stream, function ($stream) use ($loop) { + $loop->removeReadStream($stream); + $loop->removeWriteStream($stream); fclose($stream); }); @@ -216,14 +219,15 @@ public function testRemoveReadAndWriteStreamFromLoopOnceResourceClosesOnEndOfFil $this->loop->addWriteStream($stream, function () { }); // remove stream when the stream is readable (closes) - $this->loop->addReadStream($stream, function ($stream) { + $loop = $this->loop; + $loop->addReadStream($stream, function ($stream) use ($loop) { $data = fread($stream, 1024); if ($data !== '') { return; } - $this->loop->removeReadStream($stream); - $this->loop->removeWriteStream($stream); + $loop->removeReadStream($stream); + $loop->removeWriteStream($stream); fclose($stream); }); @@ -246,9 +250,10 @@ public function testRemoveReadAndWriteStreamFromLoopWithClosingResourceEndsLoop( $this->loop->addWriteStream($stream, function () { }); // remove stream when the stream is readable (closes) - $this->loop->addReadStream($stream, function ($stream) { - $this->loop->removeReadStream($stream); - $this->loop->removeWriteStream($stream); + $loop = $this->loop; + $loop->addReadStream($stream, function ($stream) use ($loop) { + $loop->removeReadStream($stream); + $loop->removeWriteStream($stream); fclose($stream); }); @@ -304,16 +309,18 @@ public function stopShouldStopRunningLoop() public function testStopShouldPreventRunFromBlocking() { + $that = $this; $this->loop->addTimer( 1, - function () { - $this->fail('Timer was executed.'); + function () use ($that) { + $that->fail('Timer was executed.'); } ); + $loop = $this->loop; $this->loop->futureTick( - function () { - $this->loop->stop(); + function () use ($loop) { + $loop->stop(); } ); @@ -338,9 +345,10 @@ public function testIgnoreRemovedCallback() }); // this callback would have to be called as well, but the first stream already removed us - $loop->addReadStream($input2, function () use (& $called) { + $that = $this; + $loop->addReadStream($input2, function () use (& $called, $that) { if ($called) { - $this->fail('Callback 2 must not be called after callback 1 was called'); + $that->fail('Callback 2 must not be called after callback 1 was called'); } }); @@ -354,9 +362,10 @@ public function testIgnoreRemovedCallback() public function testFutureTickEventGeneratedByFutureTick() { + $loop = $this->loop; $this->loop->futureTick( - function () { - $this->loop->futureTick( + function () use ($loop) { + $loop->futureTick( function () { echo 'future-tick' . PHP_EOL; } @@ -412,18 +421,19 @@ public function testRecursiveFutureTick() { list ($stream) = $this->createSocketPair(); + $loop = $this->loop; $this->loop->addWriteStream( $stream, - function () use ($stream) { + function () use ($stream, $loop) { echo 'stream' . PHP_EOL; - $this->loop->removeWriteStream($stream); + $loop->removeWriteStream($stream); } ); $this->loop->futureTick( - function () { + function () use ($loop) { echo 'future-tick-1' . PHP_EOL; - $this->loop->futureTick( + $loop->futureTick( function () { echo 'future-tick-2' . PHP_EOL; } @@ -440,11 +450,12 @@ public function testRunWaitsForFutureTickEvents() { list ($stream) = $this->createSocketPair(); + $loop = $this->loop; $this->loop->addWriteStream( $stream, - function () use ($stream) { - $this->loop->removeWriteStream($stream); - $this->loop->futureTick( + function () use ($stream, $loop) { + $loop->removeWriteStream($stream); + $loop->futureTick( function () { echo 'future-tick' . PHP_EOL; } @@ -459,10 +470,11 @@ function () { public function testFutureTickEventGeneratedByTimer() { + $loop = $this->loop; $this->loop->addTimer( 0.001, - function () { - $this->loop->futureTick( + function () use ($loop) { + $loop->futureTick( function () { echo 'future-tick' . PHP_EOL; } @@ -496,11 +508,12 @@ public function testSignal() $calledShouldNot = false; }); - $this->loop->addSignal(SIGUSR1, $func1 = function () use (&$func1, &$func2, &$called, $timer) { + $loop = $this->loop; + $this->loop->addSignal(SIGUSR1, $func1 = function () use (&$func1, &$func2, &$called, $timer, $loop) { $called = true; - $this->loop->removeSignal(SIGUSR1, $func1); - $this->loop->removeSignal(SIGUSR2, $func2); - $this->loop->cancelTimer($timer); + $loop->removeSignal(SIGUSR1, $func1); + $loop->removeSignal(SIGUSR2, $func2); + $loop->cancelTimer($timer); }); $this->loop->futureTick(function () { @@ -527,8 +540,9 @@ public function testSignalMultipleUsagesForTheSameListener() $this->loop->addTimer(0.4, function () { posix_kill(posix_getpid(), SIGUSR1); }); - $this->loop->addTimer(0.9, function () use (&$func) { - $this->loop->removeSignal(SIGUSR1, $func); + $loop = $this->loop; + $this->loop->addTimer(0.9, function () use (&$func, $loop) { + $loop->removeSignal(SIGUSR1, $func); }); $this->loop->run(); @@ -538,11 +552,12 @@ public function testSignalMultipleUsagesForTheSameListener() public function testSignalsKeepTheLoopRunning() { + $loop = $this->loop; $function = function () {}; $this->loop->addSignal(SIGUSR1, $function); - $this->loop->addTimer(1.5, function () use ($function) { - $this->loop->removeSignal(SIGUSR1, $function); - $this->loop->stop(); + $this->loop->addTimer(1.5, function () use ($function, $loop) { + $loop->removeSignal(SIGUSR1, $function); + $loop->stop(); }); $this->assertRunSlowerThan(1.5); @@ -550,10 +565,11 @@ public function testSignalsKeepTheLoopRunning() public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop() { + $loop = $this->loop; $function = function () {}; $this->loop->addSignal(SIGUSR1, $function); - $this->loop->addTimer(1.5, function () use ($function) { - $this->loop->removeSignal(SIGUSR1, $function); + $this->loop->addTimer(1.5, function () use ($function, $loop) { + $loop->removeSignal(SIGUSR1, $function); }); $this->assertRunFasterThan(1.6); @@ -568,9 +584,10 @@ public function testTimerIntervalCanBeFarInFuture() $timer = $this->loop->addTimer(PHP_INT_MAX, function () { }); // remove stream and timer when the stream is readable (closes) - $this->loop->addReadStream($stream, function ($stream) use ($timer) { - $this->loop->removeReadStream($stream); - $this->loop->cancelTimer($timer); + $loop = $this->loop; + $this->loop->addReadStream($stream, function ($stream) use ($timer, $loop) { + $loop->removeReadStream($stream); + $loop->cancelTimer($timer); }); $this->assertRunFasterThan($this->tickTimeout); diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index 87bbbe69..bd19e1cd 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -39,11 +39,11 @@ public function testStreamSelectTimeoutEmulation() public function signalProvider() { - return [ - ['SIGUSR1'], - ['SIGHUP'], - ['SIGTERM'], - ]; + return array( + array('SIGUSR1'), + array('SIGHUP'), + array('SIGTERM'), + ); } /** @@ -60,8 +60,9 @@ public function testSignalInterruptNoStream($signal) $check = $this->loop->addPeriodicTimer(0.01, function() { pcntl_signal_dispatch(); }); - $this->loop->addTimer(0.1, function () use ($check) { - $this->loop->cancelTimer($check); + $loop = $this->loop; + $loop->addTimer(0.1, function () use ($check, $loop) { + $loop->cancelTimer($check); }); $handled = false; @@ -92,12 +93,13 @@ public function testSignalInterruptWithStream($signal) }); // add stream to the loop + $loop = $this->loop; list($writeStream, $readStream) = $this->createSocketPair(); - $this->loop->addReadStream($readStream, function ($stream) { + $loop->addReadStream($readStream, function ($stream) use ($loop) { /** @var $loop LoopInterface */ $read = fgets($stream); if ($read === "end loop\n") { - $this->loop->stop(); + $loop->stop(); } }); $this->loop->addTimer(0.1, function() use ($writeStream) { diff --git a/travis-init.sh b/travis-init.sh index c8c8b55d..06f853fe 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -6,7 +6,9 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]]; then # install 'event' PHP extension - echo "yes" | pecl install event + if [[ "$TRAVIS_PHP_VERSION" != "5.3" ]]; then + echo "yes" | pecl install event + fi # install 'libevent' PHP extension (does not support php 7) if [[ "$TRAVIS_PHP_VERSION" != "7.0" && From b325a88cc8ddd14594fa6fbc92cc73f7bf97ae6f Mon Sep 17 00:00:00 2001 From: Ivan Kalita Date: Mon, 15 Jan 2018 23:48:11 +0300 Subject: [PATCH 093/203] Implement ExtEvLoop. ExtEvLoop implements event loop based on PECL ev extension. --- README.md | 10 ++ src/ExtEvLoop.php | 255 ++++++++++++++++++++++++++++++ src/Factory.php | 2 + tests/ExtEvLoopTest.php | 17 ++ tests/Timer/AbstractTimerTest.php | 4 + tests/Timer/ExtEvTimerTest.php | 17 ++ travis-init.sh | 3 + 7 files changed, 308 insertions(+) create mode 100644 src/ExtEvLoop.php create mode 100644 tests/ExtEvLoopTest.php create mode 100644 tests/Timer/ExtEvTimerTest.php diff --git a/README.md b/README.md index 7a30ec15..b2a47887 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,16 @@ It supports the same backends as libevent. This loop is known to work with PHP 5.4 through PHP 7+. +#### ExtEvLoop + +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 is known to work with PHP 5.4 through PHP 7+. + + #### ExtLibeventLoop An `ext-libevent` based event loop. diff --git a/src/ExtEvLoop.php b/src/ExtEvLoop.php new file mode 100644 index 00000000..2be45c34 --- /dev/null +++ b/src/ExtEvLoop.php @@ -0,0 +1,255 @@ +loop = new EvLoop(); + $this->futureTickQueue = new FutureTickQueue(); + $this->timers = new SplObjectStorage(); + $this->signals = new SignalsHandler(); + } + + public function addReadStream($stream, $listener) + { + $key = (int)$stream; + + if (isset($this->readStreams[$key])) { + return; + } + + $callback = $this->getStreamListenerClosure($stream, $listener); + $event = $this->loop->io($stream, Ev::READ, $callback); + $this->readStreams[$key] = $event; + } + + /** + * @param resource $stream + * @param callable $listener + * + * @return \Closure + */ + private function getStreamListenerClosure($stream, $listener) + { + return function () use ($stream, $listener) { + call_user_func($listener, $stream); + }; + } + + public function addWriteStream($stream, $listener) + { + $key = (int)$stream; + + if (isset($this->writeStreams[$key])) { + return; + } + + $callback = $this->getStreamListenerClosure($stream, $listener); + $event = $this->loop->io($stream, Ev::WRITE, $callback); + $this->writeStreams[$key] = $event; + } + + public function removeReadStream($stream) + { + $key = (int)$stream; + + if (!isset($this->readStreams[$key])) { + return; + } + + $this->readStreams[$key]->stop(); + unset($this->readStreams[$key]); + } + + public function removeWriteStream($stream) + { + $key = (int)$stream; + + if (!isset($this->writeStreams[$key])) { + return; + } + + $this->writeStreams[$key]->stop(); + unset($this->writeStreams[$key]); + } + + public function addTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, false); + + $callback = function () use ($timer) { + call_user_func($timer->getCallback(), $timer); + + if ($this->isTimerActive($timer)) { + $this->cancelTimer($timer); + } + }; + + $event = $this->loop->timer($timer->getInterval(), 0.0, $callback); + $this->timers->attach($timer, $event); + + return $timer; + } + + public function addPeriodicTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, true); + + $callback = function () use ($timer) { + call_user_func($timer->getCallback(), $timer); + }; + + $event = $this->loop->timer($interval, $interval, $callback); + $this->timers->attach($timer, $event); + + return $timer; + } + + public function cancelTimer(TimerInterface $timer) + { + if (!isset($this->timers[$timer])) { + return; + } + + $event = $this->timers[$timer]; + $event->stop(); + $this->timers->detach($timer); + } + + public function isTimerActive(TimerInterface $timer) + { + return $this->timers->contains($timer); + } + + public function futureTick($listener) + { + $this->futureTickQueue->add($listener); + } + + public function run() + { + $this->running = true; + + while ($this->running) { + $this->futureTickQueue->tick(); + + $hasPendingCallbacks = !$this->futureTickQueue->isEmpty(); + $wasJustStopped = !$this->running; + $nothingLeftToDo = !$this->readStreams + && !$this->writeStreams + && !$this->timers->count() + && $this->signals->isEmpty(); + + $flags = Ev::RUN_ONCE; + if ($wasJustStopped || $hasPendingCallbacks) { + $flags |= Ev::RUN_NOWAIT; + } elseif ($nothingLeftToDo) { + break; + } + + $this->loop->run($flags); + } + } + + public function stop() + { + $this->running = false; + } + + public function __destruct() + { + /** @var TimerInterface $timer */ + foreach ($this->timers as $timer) { + $this->cancelTimer($timer); + } + + foreach ($this->readStreams as $key => $stream) { + $this->removeReadStream($key); + } + + foreach ($this->writeStreams as $key => $stream) { + $this->removeWriteStream($key); + } + } + + public function addSignal($signal, $listener) + { + $this->signals->add($signal, $listener); + + if (!isset($this->signalEvents[$signal])) { + $this->signalEvents[$signal] = $this->loop->signal($signal, function() use ($signal) { + $this->signals->call($signal); + }); + } + } + + public function removeSignal($signal, $listener) + { + $this->signals->remove($signal, $listener); + + if (isset($this->signalEvents[$signal])) { + $this->signalEvents[$signal]->stop(); + unset($this->signalEvents[$signal]); + } + } +} diff --git a/src/Factory.php b/src/Factory.php index 1a56877d..b46fc074 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -26,6 +26,8 @@ public static function create() // @codeCoverageIgnoreStart if (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) { diff --git a/tests/ExtEvLoopTest.php b/tests/ExtEvLoopTest.php new file mode 100644 index 00000000..ab41c9f3 --- /dev/null +++ b/tests/ExtEvLoopTest.php @@ -0,0 +1,17 @@ +markTestSkipped('ExtEvLoop tests skipped because ext-ev extension is not installed.'); + } + + return new ExtEvLoop(); + } +} diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index 15202e41..294e683f 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -2,10 +2,14 @@ namespace React\Tests\EventLoop\Timer; +use React\EventLoop\LoopInterface; use React\Tests\EventLoop\TestCase; abstract class AbstractTimerTest extends TestCase { + /** + * @return LoopInterface + */ abstract public function createLoop(); public function testAddTimerReturnsNonPeriodicTimerInstance() diff --git a/tests/Timer/ExtEvTimerTest.php b/tests/Timer/ExtEvTimerTest.php new file mode 100644 index 00000000..bfa91861 --- /dev/null +++ b/tests/Timer/ExtEvTimerTest.php @@ -0,0 +1,17 @@ +markTestSkipped('ExtEvLoop tests skipped because ext-ev extension is not installed.'); + } + + return new ExtEvLoop(); + } +} diff --git a/travis-init.sh b/travis-init.sh index 06f853fe..c835d0e6 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -10,6 +10,9 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && echo "yes" | pecl install event fi + # install 'ev' PHP extension + echo "yes" | pecl install ev + # install 'libevent' PHP extension (does not support php 7) if [[ "$TRAVIS_PHP_VERSION" != "7.0" && "$TRAVIS_PHP_VERSION" != "7.1" && From 43dc6aca1000f2b1fb0f3769dc7820a4b1822bbe Mon Sep 17 00:00:00 2001 From: Ivan Kalita Date: Wed, 28 Feb 2018 20:52:15 +0300 Subject: [PATCH 094/203] Fix travis: skip ev extension installation if PHP version is 5.3. --- travis-init.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/travis-init.sh b/travis-init.sh index c835d0e6..29ce884a 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -5,14 +5,12 @@ set -o pipefail if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]]; then - # install 'event' PHP extension + # install 'event' and 'ev' PHP extension if [[ "$TRAVIS_PHP_VERSION" != "5.3" ]]; then echo "yes" | pecl install event + echo "yes" | pecl install ev fi - # install 'ev' PHP extension - echo "yes" | pecl install ev - # install 'libevent' PHP extension (does not support php 7) if [[ "$TRAVIS_PHP_VERSION" != "7.0" && "$TRAVIS_PHP_VERSION" != "7.1" && From 5ddfc7b84a68f4735b1f9196060c6a58a3e51bae Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sun, 11 Mar 2018 17:41:53 +0100 Subject: [PATCH 095/203] ExtEventLoop make FEATURE_FDS enabled by default --- src/ExtEventLoop.php | 5 +++++ tests/ExtEventLoopTest.php | 8 +------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 4146fc18..779080c8 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -38,6 +38,11 @@ final class ExtEventLoop implements LoopInterface public function __construct(EventBaseConfig $config = null) { + if ($config === null) { + $config = new EventBaseConfig(); + $config->requireFeatures(EventBaseConfig::FEATURE_FDS); + } + $this->eventBase = new EventBase($config); $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); diff --git a/tests/ExtEventLoopTest.php b/tests/ExtEventLoopTest.php index 52c65309..2f88d184 100644 --- a/tests/ExtEventLoopTest.php +++ b/tests/ExtEventLoopTest.php @@ -16,13 +16,7 @@ public function createLoop($readStreamCompatible = false) $this->markTestSkipped('ext-event tests skipped because ext-event is not installed.'); } - $cfg = null; - if ($readStreamCompatible) { - $cfg = new \EventConfig(); - $cfg->requireFeatures(\EventConfig::FEATURE_FDS); - } - - return new ExtEventLoop($cfg); + return new ExtEventLoop(); } public function createStream() From 1b5e9398791551f666356c783ba0baed10695599 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 13 Mar 2018 09:39:52 +0100 Subject: [PATCH 096/203] Removed config as constructor argument --- src/ExtEventLoop.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 779080c8..4582a38a 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -36,12 +36,10 @@ final class ExtEventLoop implements LoopInterface private $signals; private $signalEvents = array(); - public function __construct(EventBaseConfig $config = null) + public function __construct() { - if ($config === null) { - $config = new EventBaseConfig(); - $config->requireFeatures(EventBaseConfig::FEATURE_FDS); - } + $config = new EventBaseConfig(); + $config->requireFeatures(EventBaseConfig::FEATURE_FDS); $this->eventBase = new EventBase($config); $this->futureTickQueue = new FutureTickQueue(); From bf2e14b540ce2c6d1e525b3904a6d8dabac6999a Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 27 Feb 2018 22:07:28 +0100 Subject: [PATCH 097/203] Throw BadMethodCallException on loop creating when required extension isn't installed --- README.md | 2 +- src/ExtEventLoop.php | 5 +++++ src/ExtLibevLoop.php | 5 +++++ src/ExtLibeventLoop.php | 5 +++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a30ec15..5ee4d8e5 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ You should use the [`Factory`](#factory) 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. Note that you may have to install the required PHP extensions for the respective -event loop implementation first or this may result in a fatal error. +event loop implementation first or they will throw a `BadMethodCallException` on creation. #### StreamSelectLoop diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 4582a38a..622dd472 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -2,6 +2,7 @@ namespace React\EventLoop; +use BadMethodCallException; use Event; use EventBase; use EventConfig as EventBaseConfig; @@ -38,6 +39,10 @@ final class ExtEventLoop implements LoopInterface public function __construct() { + if (!class_exists('EventBase', false)) { + throw new BadMethodCallException('Cannot create ExtEventLoop, ext-event extension missing'); + } + $config = new EventBaseConfig(); $config->requireFeatures(EventBaseConfig::FEATURE_FDS); diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php index 9c679895..d3b0df81 100644 --- a/src/ExtLibevLoop.php +++ b/src/ExtLibevLoop.php @@ -2,6 +2,7 @@ namespace React\EventLoop; +use BadMethodCallException; use libev\EventLoop; use libev\IOEvent; use libev\SignalEvent; @@ -36,6 +37,10 @@ final class ExtLibevLoop implements LoopInterface public function __construct() { + if (!class_exists('libev\EventLoop', false)) { + throw new BadMethodCallException('Cannot create ExtLibevLoop, ext-libev extension missing'); + } + $this->loop = new EventLoop(); $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index 5bc265e2..427f8db0 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -2,6 +2,7 @@ namespace React\EventLoop; +use BadMethodCallException; use Event; use EventBase; use React\EventLoop\Tick\FutureTickQueue; @@ -52,6 +53,10 @@ final class ExtLibeventLoop implements LoopInterface public function __construct() { + if (!function_exists('event_base_new')) { + throw new BadMethodCallException('Cannot create ExtLibeventLoop, ext-libevent extension missing'); + } + $this->eventBase = event_base_new(); $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); From a3d4bdd5f64736c8ed30e2d54cc2713456745fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 25 Mar 2018 19:23:00 +0200 Subject: [PATCH 098/203] Documentation for millisecond timer accuracy --- README.md | 24 ++++++++++++++++++++---- src/LoopInterface.php | 24 ++++++++++++++++++++---- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5ee4d8e5..beb5e6cd 100644 --- a/README.md +++ b/README.md @@ -281,8 +281,16 @@ function hello($name, LoopInterface $loop) hello('Tester', $loop); ``` -The execution order of timers scheduled to execute at the same time is -not guaranteed. +This interface does not enforce any particular timer resolution, so +special care may have to be taken if you rely on very high precision with +millisecond accuracy or below. Event loop implementations SHOULD work on +a best effort basis and SHOULD provide at least millisecond accuracy +unless otherwise noted. Many existing event loop implementations are +known to provide microsecond accuracy, but it's generally not recommended +to rely on this high precision. + +Similarly, the execution order of timers scheduled to execute at the +same time (within its possible accuracy) is not guaranteed. This interface suggests that event loop implementations SHOULD use a monotic time source if available. Given that a monotonic time source is @@ -346,8 +354,16 @@ function hello($name, LoopInterface $loop) hello('Tester', $loop); ``` -The execution order of timers scheduled to execute at the same time is -not guaranteed. +This interface does not enforce any particular timer resolution, so +special care may have to be taken if you rely on very high precision with +millisecond accuracy or below. Event loop implementations SHOULD work on +a best effort basis and SHOULD provide at least millisecond accuracy +unless otherwise noted. Many existing event loop implementations are +known to provide microsecond accuracy, but it's generally not recommended +to rely on this high precision. + +Similarly, the execution order of timers scheduled to execute at the +same time (within its possible accuracy) is not guaranteed. #### cancelTimer() diff --git a/src/LoopInterface.php b/src/LoopInterface.php index ebf58755..9401922f 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -168,8 +168,16 @@ public function removeWriteStream($stream); * hello('Tester', $loop); * ``` * - * The execution order of timers scheduled to execute at the same time is - * not guaranteed. + * This interface does not enforce any particular timer resolution, so + * special care may have to be taken if you rely on very high precision with + * millisecond accuracy or below. Event loop implementations SHOULD work on + * a best effort basis and SHOULD provide at least millisecond accuracy + * unless otherwise noted. Many existing event loop implementations are + * known to provide microsecond accuracy, but it's generally not recommended + * to rely on this high precision. + * + * Similarly, the execution order of timers scheduled to execute at the + * same time (within its possible accuracy) is not guaranteed. * * @param int|float $interval The number of seconds to wait before execution. * @param callable $callback The callback to invoke. @@ -227,8 +235,16 @@ public function addTimer($interval, $callback); * hello('Tester', $loop); * ``` * - * The execution order of timers scheduled to execute at the same time is - * not guaranteed. + * This interface does not enforce any particular timer resolution, so + * special care may have to be taken if you rely on very high precision with + * millisecond accuracy or below. Event loop implementations SHOULD work on + * a best effort basis and SHOULD provide at least millisecond accuracy + * unless otherwise noted. Many existing event loop implementations are + * known to provide microsecond accuracy, but it's generally not recommended + * to rely on this high precision. + * + * Similarly, the execution order of timers scheduled to execute at the + * same time (within its possible accuracy) is not guaranteed. * * This interface suggests that event loop implementations SHOULD use a * monotic time source if available. Given that a monotonic time source is From f432daa1c8795ae4a257eac69d9c29cd4f850e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 25 Mar 2018 22:02:17 +0200 Subject: [PATCH 099/203] Documentation for timer drift for periodic timers --- README.md | 18 +++++++++++++++++- src/LoopInterface.php | 18 +++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index beb5e6cd..6912e021 100644 --- a/README.md +++ b/README.md @@ -293,7 +293,7 @@ Similarly, the execution order of timers scheduled to execute at the same time (within its possible accuracy) is not guaranteed. This interface suggests that event loop implementations SHOULD use a -monotic time source if available. Given that a monotonic time source is +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. While this does not affect many common use cases, this is an important @@ -365,6 +365,22 @@ to rely on this high precision. Similarly, the execution order of timers scheduled to execute at the same time (within its possible accuracy) is not guaranteed. +This interface suggests that event loop implementations SHOULD use a +monotonic time source if available. Given that a monotonic time source is +not available on PHP by default, event loop implementations MAY fall back +to using wall-clock time. +While this does not affect many common use cases, this is an important +distinction for programs that rely on a high time precision or on systems +that are subject to discontinuous time adjustments (time jumps). +This means that if you schedule a timer to trigger in 30s and then adjust +your system time forward by 20s, the timer SHOULD still trigger in 30s. +See also [event loop implementations](#loop-implementations) for more details. + +Additionally, periodic timers may be subject to timer drift due to +re-scheduling after each invocation. As such, it's generally not +recommended to rely on this for high precision intervals with millisecond +accuracy or below. + #### cancelTimer() The `cancelTimer(TimerInterface $timer): void` method can be used to diff --git a/src/LoopInterface.php b/src/LoopInterface.php index 9401922f..868d0a0c 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -179,6 +179,17 @@ public function removeWriteStream($stream); * Similarly, the execution order of timers scheduled to execute at the * same time (within its possible accuracy) is not guaranteed. * + * This interface suggests that event loop implementations SHOULD use a + * monotonic time source if available. Given that a monotonic time source is + * not available on PHP by default, event loop implementations MAY fall back + * to using wall-clock time. + * While this does not affect many common use cases, this is an important + * distinction for programs that rely on a high time precision or on systems + * that are subject to discontinuous time adjustments (time jumps). + * This means that if you schedule a timer to trigger in 30s and then adjust + * your system time forward by 20s, the timer SHOULD still trigger in 30s. + * See also [event loop implementations](#loop-implementations) for more details. + * * @param int|float $interval The number of seconds to wait before execution. * @param callable $callback The callback to invoke. * @@ -247,7 +258,7 @@ public function addTimer($interval, $callback); * same time (within its possible accuracy) is not guaranteed. * * This interface suggests that event loop implementations SHOULD use a - * monotic time source if available. Given that a monotonic time source is + * 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. * While this does not affect many common use cases, this is an important @@ -257,6 +268,11 @@ public function addTimer($interval, $callback); * your system time forward by 20s, the timer SHOULD still trigger in 30s. * See also [event loop implementations](#loop-implementations) for more details. * + * Additionally, periodic timers may be subject to timer drift due to + * re-scheduling after each invocation. As such, it's generally not + * recommended to rely on this for high precision intervals with millisecond + * accuracy or below. + * * @param int|float $interval The number of seconds to wait before execution. * @param callable $callback The callback to invoke. * From 73933b666734ce31ec056fbd45b619a06f3ffed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 22 Mar 2018 19:42:04 +0100 Subject: [PATCH 100/203] Allow throwing Exception if stream resource is not supported --- README.md | 6 ++++-- src/LoopInterface.php | 8 ++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6912e021..f8dfe3f6 100644 --- a/README.md +++ b/README.md @@ -499,7 +499,8 @@ checking whether it is ready to read by this loop implementation. A single stream resource MUST NOT be added more than once. Instead, either call [`removeReadStream()`](#removereadstream) first or react to this event with a single listener and then dispatch from this -listener. +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 @@ -550,7 +551,8 @@ checking whether it is ready to write by this loop implementation. A single stream resource MUST NOT be added more than once. Instead, either call [`removeWriteStream()`](#removewritestream) first or react to this event with a single listener and then dispatch from this -listener. +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 diff --git a/src/LoopInterface.php b/src/LoopInterface.php index 868d0a0c..c3c7219c 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -17,7 +17,8 @@ interface LoopInterface * A single stream resource MUST NOT be added more than once. * Instead, either call [`removeReadStream()`](#removereadstream) first or * react to this event with a single listener and then dispatch from this - * listener. + * 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 @@ -47,6 +48,7 @@ interface LoopInterface * * @param resource $stream The PHP stream resource to check. * @param callable $listener Invoked when the stream is ready. + * @throws \Exception if the given resource type is not supported by this loop implementation * @see self::removeReadStream() */ public function addReadStream($stream, $listener); @@ -64,7 +66,8 @@ public function addReadStream($stream, $listener); * A single stream resource MUST NOT be added more than once. * Instead, either call [`removeWriteStream()`](#removewritestream) first or * react to this event with a single listener and then dispatch from this - * listener. + * 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 @@ -102,6 +105,7 @@ public function addReadStream($stream, $listener); * * @param resource $stream The PHP stream resource to check. * @param callable $listener Invoked when the stream is ready. + * @throws \Exception if the given resource type is not supported by this loop implementation * @see self::removeWriteStream() */ public function addWriteStream($stream, $listener); From fbb60885c0fb108f265a1130eee12492a502bd49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 19 Mar 2018 09:57:55 +0100 Subject: [PATCH 101/203] Do not suggest installing outdated event loop extensions --- composer.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/composer.json b/composer.json index 05afe65a..f4ae30f9 100644 --- a/composer.json +++ b/composer.json @@ -11,8 +11,6 @@ }, "suggest": { "ext-event": "~1.0 for ExtEventLoop", - "ext-libevent": ">=0.1.0 for ExtLibeventLoop and PHP 5 only", - "ext-libev": "for ExtLibevLoop and PHP 5 only", "ext-pcntl": "For signal handling support when using the StreamSelectLoop" }, "autoload": { From 43a0addd575a3f204c5a6fc8950aba7cb617a33c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 3 Apr 2018 23:45:07 +0200 Subject: [PATCH 102/203] Documentation for run() and stop() --- README.md | 57 ++++++++++++++++++++++++++++++++++++++++--- src/LoopInterface.php | 43 ++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f8dfe3f6..8d4cc68a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Event loop abstraction layer that libraries can use for evented I/O. In order for async based libraries to be interoperable, they need to use the same event loop. This component provides a common `LoopInterface` that any library can target. This allows them to be used in the same loop, with one -single `run()` call that is controlled by the user. +single [`run()`](#run) call that is controlled by the user. > The master branch contains the code for the upcoming 0.5 release. For the code of the current stable 0.4.x release, checkout the @@ -26,6 +26,8 @@ For the code of the current stable 0.4.x release, checkout the * [ExtLibeventLoop](#extlibeventloop) * [ExtLibevLoop](#extlibevloop) * [LoopInterface](#loopinterface) + * [run()](#run) + * [stop()](#stop) * [addTimer()](#addtimer) * [addPeriodicTimer()](#addperiodictimer) * [cancelTimer()](#canceltimer) @@ -100,7 +102,7 @@ $loop->run(); ``` 1. The loop instance is created at the beginning of the program. A convenience - factory `React\EventLoop\Factory::create()` is provided by this library which + 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 @@ -109,7 +111,7 @@ $loop->run(); is created by using ReactPHP's [stream component](https://github.com/reactphp/stream) for demonstration purposes. -3. The loop is run with a single `$loop->run()` call at the end of the program. +3. The loop is run with a single [`$loop->run()`](#run) call at the end of the program. ### Factory @@ -237,6 +239,55 @@ to happen any time soon. ### LoopInterface +#### run() + +The `run(): void` method can be used to +run the event loop until there are no more tasks to perform. + +For many applications, this method is the only directly visible +invocation on the event loop. +As a rule of thumb, it is usally recommended to attach everything to the +same loop instance and then run the loop once at the bottom end of the +application. + +```php +$loop->run(); +``` + +This method will keep the loop running until there are no more tasks +to perform. In other words: This method will block until the last +timer, stream and/or signal has been removed. + +Likewise, it is imperative to ensure the application actually invokes +this method once. Adding listeners to the loop and missing to actually +run it will result in the application exiting without actually waiting +for any of the attached listeners. + +This method MUST NOT be called while the loop is already running. +This method MAY be called more than once after it has explicity been +[`stop()`ped](#stop) or after it automatically stopped because it +previously did no longer have anything to do. + +#### stop() + +The `stop(): void` method can be used to +instruct a running event loop to stop. + +This method is considered advanced usage and should be used with care. +As a rule of thumb, it is usually recommended to let the loop stop +only automatically when it no longer has anything to do. + +This method can be used to explicitly instruct the event loop to stop: + +```php +$loop->addTimer(3.0, function () use ($loop) { + $loop->stop(); +}); +``` + +Calling this method on a loop instance that is not currently running or +on a loop instance that has already been stopped has no effect. + #### addTimer() The `addTimer(float $interval, callable $callback): TimerInterface` method can be used to diff --git a/src/LoopInterface.php b/src/LoopInterface.php index c3c7219c..1cc8640f 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -410,11 +410,54 @@ public function removeSignal($signal, $listener); /** * Run the event loop until there are no more tasks to perform. + * + * For many applications, this method is the only directly visible + * invocation on the event loop. + * As a rule of thumb, it is usally recommended to attach everything to the + * same loop instance and then run the loop once at the bottom end of the + * application. + * + * ```php + * $loop->run(); + * ``` + * + * This method will keep the loop running until there are no more tasks + * to perform. In other words: This method will block until the last + * timer, stream and/or signal has been removed. + * + * Likewise, it is imperative to ensure the application actually invokes + * this method once. Adding listeners to the loop and missing to actually + * run it will result in the application exiting without actually waiting + * for any of the attached listeners. + * + * This method MUST NOT be called while the loop is already running. + * This method MAY be called more than once after it has explicity been + * [`stop()`ped](#stop) or after it automatically stopped because it + * previously did no longer have anything to do. + * + * @return void */ public function run(); /** * Instruct a running event loop to stop. + * + * This method is considered advanced usage and should be used with care. + * As a rule of thumb, it is usually recommended to let the loop stop + * only automatically when it no longer has anything to do. + * + * This method can be used to explicitly instruct the event loop to stop: + * + * ```php + * $loop->addTimer(3.0, function () use ($loop) { + * $loop->stop(); + * }); + * ``` + * + * Calling this method on a loop instance that is not currently running or + * on a loop instance that has already been stopped has no effect. + * + * @return void */ public function stop(); } From 4ea7246e4e412623ec2bfd5453cf48b76b6490a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 4 Apr 2018 09:58:25 +0200 Subject: [PATCH 103/203] Add legacy v0.3.5 and v0.4.3 to CHANGELOG --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79bf001a..d4454013 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ * BC break: Remove `LoopInterface::tick()` (@jsor, #72) +## 0.4.3 (2017-04-27) + +* Bug fix: Bugfix in the usage sample code #57 (@dandelionred) +* Improvement: Remove branch-alias definition #53 (@WyriHaximus) +* Improvement: StreamSelectLoop: Use fresh time so Timers added during stream events are accurate #51 (@andrewminerd) +* Improvement: Avoid deprecation warnings in test suite due to deprecation of getMock() in PHPUnit #68 (@martinschroeder) +* Improvement: Add PHPUnit 4.8 to require-dev #69 (@shaunbramley) +* Improvement: Increase test timeouts for HHVM and unify timeout handling #70 (@clue) +* Improvement: Travis improvements (backported from #74) #75 (@clue) +* Improvement: Test suite now uses socket pairs instead of memory streams #66 (@martinschroeder) +* Improvement: StreamSelectLoop: Test suite uses signal constant names in data provider #67 (@martinschroeder) +* Improvement: ExtEventLoop: No longer suppress all errors #65 (@mamciek) +* Improvement: Readme cleanup #89 (@jsor) +* Improvement: Restructure and improve README #90 (@jsor) +* Bug fix: StreamSelectLoop: Fix erroneous zero-time sleep (backport to 0.4) #94 (@jsor) + ## 0.4.2 (2016-03-07) * Bug fix: No longer error when signals sent to StreamSelectLoop @@ -16,10 +32,6 @@ * Bug fix: null timeout in StreamSelectLoop causing 100% CPU usage (@clue) * Bug fix: v0.3.4 changes merged for v0.4.1 -## 0.3.4 (2014-03-30) - -* Changed StreamSelectLoop to use non-blocking behavior on tick() (@astephens25) - ## 0.4.0 (2014-02-02) * Feature: Added `EventLoopInterface::nextTick()`, implemented in all event loops (@jmalloc) @@ -30,6 +42,18 @@ * BC break: New method: `EventLoopInterface::futureTick()` * Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0 +## 0.3.5 (2016-12-28) + +This is a compatibility release that eases upgrading to the v0.4 release branch. +You should consider upgrading to the v0.4 release branch. + +* Feature: Cap min timer interval at 1µs, thus improving compatibility with v0.4 + (#47 by @clue) + +## 0.3.4 (2014-03-30) + +* Bug fix: Changed StreamSelectLoop to use non-blocking behavior on tick() (@astephens25) + ## 0.3.3 (2013-07-08) * Bug fix: No error on removing non-existent streams (@clue) From f3ab8edeb6416466a73b1217b3af475ce9e3b087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 5 Apr 2018 13:53:43 +0200 Subject: [PATCH 104/203] Prepare v0.5.0 release --- CHANGELOG.md | 225 +++++++++++++++++++++++++++++++++++++++++++++++++- README.md | 17 ++-- composer.json | 2 +- 3 files changed, 231 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4454013..a9e10411 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,229 @@ # Changelog -## 0.5.0 (xxxx-xx-xx) +## 0.5.0 (2018-04-05) -* BC break: Remove `LoopInterface::tick()` (@jsor, #72) +A major feature release with a significant documentation overhaul and long overdue API cleanup! + +This update involves a number of BC breaks due to dropped support for deprecated +functionality. We've tried hard to avoid BC breaks where possible and minimize +impact otherwise. We expect that most consumers of this package will actually +not be affected by any BC breaks, see below for more details. + +We realize that the changes listed below may seem overwhelming, but we've tried +to be very clear about any possible BC breaks. Don't worry: In fact, all ReactPHP +components are already compatible and support both this new release as well as +providing backwards compatibility with the last release. + +* Feature / BC break: Add support for signal handling via new + `LoopInterface::addSignal()` and `LoopInterface::removeSignal()` methods. + (#104 by @WyriHaximus and #111 and #150 by @clue) + + ```php + $loop->addSignal(SIGINT, function () { + echo 'CTRL-C'; + }); + ``` + +* Feature: Significant documentation updates for `LoopInterface` and `Factory`. + (#100, #119, #126, #127, #159 and #160 by @clue, #113 by @WyriHaximus and #81 and #91 by @jsor) + +* Feature: Add examples to ease getting started + (#99, #100 and #125 by @clue, #59 by @WyriHaximus and #143 by @jsor) + +* Feature: Documentation for advanced timer concepts, such as monotonic time source vs wall-clock time + and high precision timers with millisecond accuracy or below. + (#130 and #157 by @clue) + +* Feature: Documentation for advanced stream concepts, such as edge-triggered event listeners + and stream buffers and allow throwing Exception if stream resource is not supported. + (#129 and #158 by @clue) + +* Feature: Throw `BadMethodCallException` on manual loop creation when required extension isn't installed. + (#153 by @WyriHaximus) + +* Feature / BC break: First class support for legacy PHP 5.3 through PHP 7.2 and HHVM + and remove all `callable` type hints for consistency reasons. + (#141 and #151 by @clue) + +* BC break: Documentation for timer API and clean up unneeded timer API. + (#102 by @clue) + + Remove `TimerInterface::cancel()`, use `LoopInterface::cancelTimer()` instead: + + ```php + // old (method invoked on timer instance) + $timer->cancel(); + + // already supported before: invoke method on loop instance + $loop->cancelTimer($timer); + ``` + + Remove unneeded `TimerInterface::setData()` and `TimerInterface::getData()`, + use closure binding to add arbitrary data to timer instead: + + ```php + // old (limited setData() and getData() only allows single variable) + $name = 'Tester'; + $timer = $loop->addTimer(1.0, function ($timer) { + echo 'Hello ' . $timer->getData() . PHP_EOL; + }); + $timer->setData($name); + + // already supported before: closure binding allows any number of variables + $name = 'Tester'; + $loop->addTimer(1.0, function () use ($name) { + echo 'Hello ' . $name . PHP_EOL; + }); + ``` + + Remove unneeded `TimerInterface::getLoop()`, use closure binding instead: + + ```php + // old (getLoop() called on timer instance) + $loop->addTimer(0.1, function ($timer) { + $timer->getLoop()->stop(); + }); + + // already supported before: use closure binding as usual + $loop->addTimer(0.1, function () use ($loop) { + $loop->stop(); + }); + ``` + +* BC break: Remove unneeded `LoopInterface::isTimerActive()` and + `TimerInterface::isActive()` to reduce API surface. + (#133 by @clue) + + ```php + // old (method on timer instance or on loop instance) + $timer->isActive(); + $loop->isTimerActive($timer); + ``` + +* BC break: Move `TimerInterface` one level up to `React\EventLoop\TimerInterface`. + (#138 by @WyriHaximus) + + ```php + // old (notice obsolete "Timer" namespace) + assert($timer instanceof React\EventLoop\Timer\TimerInterface); + + // new + assert($timer instanceof React\EventLoop\TimerInterface); + ``` + +* BC break: Remove unneeded `LoopInterface::nextTick()` (and internal `NextTickQueue`), + use `LoopInterface::futureTick()` instead. + (#30 by @clue) + + ```php + // old (removed) + $loop->nextTick(function () { + echo 'tick'; + }); + + // already supported before + $loop->futureTick(function () { + echo 'tick'; + }); + ``` + +* BC break: Remove unneeded `$loop` argument for `LoopInterface::futureTick()` + (and fix internal cyclic dependency). + (#103 by @clue) + + ```php + // old ($loop gets passed by default) + $loop->futureTick(function ($loop) { + $loop->stop(); + }); + + // already supported before: use closure binding as usual + $loop->futureTick(function () use ($loop) { + $loop->stop(); + }); + ``` + +* BC break: Remove unneeded `LoopInterface::tick()`. + (#72 by @jsor) + + ```php + // old (removed) + $loop->tick(); + + // suggested work around for testing purposes only + $loop->futureTick(function () use ($loop) { + $loop->stop(); + }); + ``` + +* BC break: Documentation for advanced stream API and clean up unneeded stream API. + (#110 by @clue) + + Remove unneeded `$loop` argument for `LoopInterface::addReadStream()` + and `LoopInterface::addWriteStream()`, use closure binding instead: + + ```php + // old ($loop gets passed by default) + $loop->addReadStream($stream, function ($stream, $loop) { + $loop->removeReadStream($stream); + }); + + // already supported before: use closure binding as usual + $loop->addReadStream($stream, function ($stream) use ($loop) { + $loop->removeReadStream($stream); + }); + ``` + +* BC break: Remove unneeded `LoopInterface::removeStream()` method, + use `LoopInterface::removeReadStream()` and `LoopInterface::removeWriteStream()` instead. + (#118 by @clue) + + ```php + // old + $loop->removeStream($stream); + + // already supported before + $loop->removeReadStream($stream); + $loop->removeWriteStream($stream); + ``` + +* BC break: Rename `LibEventLoop` to `ExtLibeventLoop` and `LibEvLoop` to `ExtLibevLoop` + for consistent naming for event loop implementations. + (#128 by @clue) + +* BC break: Remove optional `EventBaseConfig` argument from `ExtEventLoop` + and make its `FEATURE_FDS` enabled by default. + (#156 by @WyriHaximus) + +* BC break: Mark all classes as final to discourage inheritance. + (#131 by @clue) + +* Fix: Fix `ExtEventLoop` to keep track of stream resources (refcount) + (#123 by @clue) + +* Fix: Ensure large timer interval does not overflow on 32bit systems + (#132 by @clue) + +* Fix: Fix separately removing readable and writable side of stream when closing + (#139 by @clue) + +* Fix: Properly clean up event watchers for `ext-event` and `ext-libev` + (#149 by @clue) + +* Fix: Minor code cleanup and remove unneeded references + (#145 by @seregazhuk) + +* Fix: Discourage outdated `ext-libevent` on PHP 7 + (#62 by @cboden) + +* Improve test suite by adding forward compatibility with PHPUnit 6 and PHPUnit 5, + lock Travis distro so new defaults will not break the build, + improve test suite to be less fragile and increase test timeouts, + test against PHP 7.2 and reduce fwrite() call length to one chunk. + (#106 and #144 by @clue, #120 and #124 by @carusogabriel, #147 by nawarian and #92 by @kelunik) + +* A number of changes were originally planned for this release but have been backported + to the last `v0.4.3` already: #74, #76, #79, #81 (refs #65, #66, #67), #88 and #93 ## 0.4.3 (2017-04-27) diff --git a/README.md b/README.md index 8d4cc68a..2fb7ee36 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,14 @@ # EventLoop Component [![Build Status](https://travis-ci.org/reactphp/event-loop.svg?branch=master)](https://travis-ci.org/reactphp/event-loop) -[![Code Climate](https://codeclimate.com/github/reactphp/event-loop/badges/gpa.svg)](https://codeclimate.com/github/reactphp/event-loop) -Event loop abstraction layer that libraries can use for evented I/O. +[ReactPHP](https://reactphp.org/)'s core reactor event loop that libraries can use for evented I/O. In order for async based libraries to be interoperable, they need to use the same event loop. This component provides a common `LoopInterface` that any library can target. This allows them to be used in the same loop, with one single [`run()`](#run) call that is controlled by the user. -> The master branch contains the code for the upcoming 0.5 release. -For the code of the current stable 0.4.x release, checkout the -[0.4 branch](https://github.com/reactphp/event-loop/tree/0.4). - **Table of Contents** * [Quickstart example](#quickstart-example) @@ -649,15 +644,17 @@ 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](http://getcomposer.org). -[New to Composer?](http://getcomposer.org/doc/00-intro.md) +The recommended way to install this library is [through Composer](https://getcomposer.org). +[New to Composer?](https://getcomposer.org/doc/00-intro.md) This will install the latest supported version: ```bash -$ composer require react/event-loop +$ composer require react/event-loop:^0.5 ``` +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 HHVM. @@ -669,7 +666,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](http://getcomposer.org): +dependencies [through Composer](https://getcomposer.org): ```bash $ composer install diff --git a/composer.json b/composer.json index f4ae30f9..24974ece 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "react/event-loop", - "description": "Event loop abstraction layer that libraries can use for evented I/O.", + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", "keywords": ["event-loop", "asynchronous"], "license": "MIT", "require": { From 882597b2c966a2bf236628942e37495830766c88 Mon Sep 17 00:00:00 2001 From: Ivan Kalita Date: Fri, 6 Apr 2018 18:28:34 +0300 Subject: [PATCH 105/203] Remove ExtEvLoop::isTimerActive. --- src/ExtEvLoop.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/ExtEvLoop.php b/src/ExtEvLoop.php index 2be45c34..74db6d02 100644 --- a/src/ExtEvLoop.php +++ b/src/ExtEvLoop.php @@ -137,11 +137,13 @@ public function addTimer($interval, $callback) { $timer = new Timer($interval, $callback, false); - $callback = function () use ($timer) { + $that = $this; + $timers = $this->timers; + $callback = function () use ($timer, $timers, $that) { call_user_func($timer->getCallback(), $timer); - if ($this->isTimerActive($timer)) { - $this->cancelTimer($timer); + if ($timers->contains($timer)) { + $that->cancelTimer($timer); } }; @@ -176,11 +178,6 @@ public function cancelTimer(TimerInterface $timer) $this->timers->detach($timer); } - public function isTimerActive(TimerInterface $timer) - { - return $this->timers->contains($timer); - } - public function futureTick($listener) { $this->futureTickQueue->add($listener); From cedf94181bb35774a593b2543b1294ead750e268 Mon Sep 17 00:00:00 2001 From: Ivan Kalita Date: Fri, 6 Apr 2018 18:37:13 +0300 Subject: [PATCH 106/203] Fix README: add ExtEvLoop to TOC. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b2a47887..65f4695f 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ For the code of the current stable 0.4.x release, checkout the * [ExtEventLoop](#exteventloop) * [ExtLibeventLoop](#extlibeventloop) * [ExtLibevLoop](#extlibevloop) + * [ExtEvLoop](#extevloop) * [LoopInterface](#loopinterface) * [addTimer()](#addtimer) * [addPeriodicTimer()](#addperiodictimer) From e1e0647a5c6e2c86013a24e9c8252113df86105a Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 9 Apr 2018 13:59:21 +0200 Subject: [PATCH 107/203] Prepare v0.5.1 release --- CHANGELOG.md | 4 ++++ README.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9e10411..c291840c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.5.1 (2018-04-09) + +* New loop: ExtEvLoop (PECL ext-ev) (#148 by @kaduev13) + ## 0.5.0 (2018-04-05) A major feature release with a significant documentation overhaul and long overdue API cleanup! diff --git a/README.md b/README.md index 0ca8f1f0..207e7f4e 100644 --- a/README.md +++ b/README.md @@ -661,7 +661,7 @@ The recommended way to install this library is [through Composer](https://getcom This will install the latest supported version: ```bash -$ composer require react/event-loop:^0.5 +$ composer require react/event-loop:^0.5.1 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 1ee5a236d88eecce4606e6f865ac3c857456c0e8 Mon Sep 17 00:00:00 2001 From: nawarian Date: Mon, 9 Apr 2018 17:24:21 +0200 Subject: [PATCH 108/203] Removes I/O dependency at StreamSelectLoopTest --- tests/AbstractLoopTest.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index dbfc91ed..3d844382 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -579,16 +579,11 @@ public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop() public function testTimerIntervalCanBeFarInFuture() { - // get only one part of the pair to ensure the other side will close immediately - list($stream) = $this->createSocketPair(); - + $loop = $this->loop; // start a timer very far in the future $timer = $this->loop->addTimer(PHP_INT_MAX, function () { }); - // remove stream and timer when the stream is readable (closes) - $loop = $this->loop; - $this->loop->addReadStream($stream, function ($stream) use ($timer, $loop) { - $loop->removeReadStream($stream); + $this->loop->futureTick(function () use ($timer, $loop) { $loop->cancelTimer($timer); }); From efd037cd34f4d174d936b3ad764b0ba9d71c6416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 20 Apr 2018 22:21:59 +0200 Subject: [PATCH 109/203] Use sorted array to store timers and remove cancelled ones --- src/Timer/Timers.php | 68 +++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 45 deletions(-) diff --git a/src/Timer/Timers.php b/src/Timer/Timers.php index 17bbdac8..813072d4 100644 --- a/src/Timer/Timers.php +++ b/src/Timer/Timers.php @@ -3,8 +3,6 @@ namespace React\EventLoop\Timer; use React\EventLoop\TimerInterface; -use SplObjectStorage; -use SplPriorityQueue; /** * A scheduler implementation that can hold multiple timer instances @@ -17,14 +15,8 @@ final class Timers { private $time; - private $timers; - private $scheduler; - - public function __construct() - { - $this->timers = new SplObjectStorage(); - $this->scheduler = new SplPriorityQueue(); - } + private $timers = array(); + private $schedule = array(); public function updateTime() { @@ -38,36 +30,26 @@ public function getTime() public function add(TimerInterface $timer) { - $interval = $timer->getInterval(); - $scheduledAt = $interval + microtime(true); - - $this->timers->attach($timer, $scheduledAt); - $this->scheduler->insert($timer, -$scheduledAt); + $id = spl_object_hash($timer); + $this->timers[$id] = $timer; + $this->schedule[$id] = $timer->getInterval() + microtime(true); + asort($this->schedule); } public function contains(TimerInterface $timer) { - return $this->timers->contains($timer); + return isset($this->timers[spl_oject_hash($timer)]); } public function cancel(TimerInterface $timer) { - $this->timers->detach($timer); + $id = spl_object_hash($timer); + unset($this->timers[$id], $this->schedule[$id]); } public function getFirst() { - while ($this->scheduler->count()) { - $timer = $this->scheduler->top(); - - if ($this->timers->contains($timer)) { - return $this->timers[$timer]; - } - - $this->scheduler->extract(); - } - - return null; + return reset($this->schedule); } public function isEmpty() @@ -78,31 +60,27 @@ public function isEmpty() public function tick() { $time = $this->updateTime(); - $timers = $this->timers; - $scheduler = $this->scheduler; - - while (!$scheduler->isEmpty()) { - $timer = $scheduler->top(); - if (!isset($timers[$timer])) { - $scheduler->extract(); - $timers->detach($timer); - - continue; + foreach ($this->schedule as $id => $scheduled) { + // schedule is ordered, so loop until first timer that is not scheduled for execution now + if ($scheduled >= $time) { + break; } - if ($timers[$timer] >= $time) { - break; + // skip any timers that are removed while we process the current schedule + if (!isset($this->schedule[$id]) || $this->schedule[$id] !== $scheduled) { + continue; } - $scheduler->extract(); + $timer = $this->timers[$id]; call_user_func($timer->getCallback(), $timer); - if ($timer->isPeriodic() && isset($timers[$timer])) { - $timers[$timer] = $scheduledAt = $timer->getInterval() + $time; - $scheduler->insert($timer, -$scheduledAt); + // re-schedule if this is a periodic timer and it has not been cancelled explicitly already + if ($timer->isPeriodic() && isset($this->timers[$id])) { + $this->schedule[$id] = $timer->getInterval() + $time; + asort($this->schedule); } else { - $timers->detach($timer); + unset($this->timers[$id], $this->schedule[$id]); } } } From be70d8c65f9224ba1765e6761389bf593896114a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 21 Apr 2018 10:11:09 +0200 Subject: [PATCH 110/203] Improve performance by sorting timers only on demand --- src/Timer/Timers.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Timer/Timers.php b/src/Timer/Timers.php index 813072d4..7944b4c1 100644 --- a/src/Timer/Timers.php +++ b/src/Timer/Timers.php @@ -17,6 +17,7 @@ final class Timers private $time; private $timers = array(); private $schedule = array(); + private $sorted = true; public function updateTime() { @@ -33,7 +34,7 @@ public function add(TimerInterface $timer) $id = spl_object_hash($timer); $this->timers[$id] = $timer; $this->schedule[$id] = $timer->getInterval() + microtime(true); - asort($this->schedule); + $this->sorted = false; } public function contains(TimerInterface $timer) @@ -49,6 +50,12 @@ public function cancel(TimerInterface $timer) public function getFirst() { + // ensure timers are sorted to simply accessing next (first) one + if (!$this->sorted) { + $this->sorted = true; + asort($this->schedule); + } + return reset($this->schedule); } @@ -59,6 +66,12 @@ public function isEmpty() public function tick() { + // ensure timers are sorted so we can execute in order + if (!$this->sorted) { + $this->sorted = true; + asort($this->schedule); + } + $time = $this->updateTime(); foreach ($this->schedule as $id => $scheduled) { @@ -78,7 +91,7 @@ public function tick() // re-schedule if this is a periodic timer and it has not been cancelled explicitly already if ($timer->isPeriodic() && isset($this->timers[$id])) { $this->schedule[$id] = $timer->getInterval() + $time; - asort($this->schedule); + $this->sorted = false; } else { unset($this->timers[$id], $this->schedule[$id]); } From e94985d93c689c554265b01014f8c3064921ca27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 24 Apr 2018 13:23:06 +0200 Subject: [PATCH 111/203] Prepare v0.5.2 release --- CHANGELOG.md | 10 +++++++++- README.md | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c291840c..0eb41703 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ # Changelog +## 0.5.2 (2018-04-24) + +* Feature: Improve memory consumption and runtime performance for `StreamSelectLoop` timers. + (#164 by @clue) + +* Improve test suite by removing I/O dependency at `StreamSelectLoopTest` to fix Mac OS X tests. + (#161 by @nawarian) + ## 0.5.1 (2018-04-09) -* New loop: ExtEvLoop (PECL ext-ev) (#148 by @kaduev13) +* Feature: New `ExtEvLoop` (PECL ext-ev) (#148 by @kaduev13) ## 0.5.0 (2018-04-05) diff --git a/README.md b/README.md index 207e7f4e..6233b37c 100644 --- a/README.md +++ b/README.md @@ -661,7 +661,7 @@ The recommended way to install this library is [through Composer](https://getcom This will install the latest supported version: ```bash -$ composer require react/event-loop:^0.5.1 +$ composer require react/event-loop:^0.5.2 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 603dc40e5e3160926b742448f842b1a4476813c2 Mon Sep 17 00:00:00 2001 From: Donatello-za Date: Thu, 10 May 2018 09:28:36 +0200 Subject: [PATCH 112/203] Issue #165: Improved backward compatibility with PHP 5.3 --- src/ExtEvLoop.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ExtEvLoop.php b/src/ExtEvLoop.php index 74db6d02..4347263c 100644 --- a/src/ExtEvLoop.php +++ b/src/ExtEvLoop.php @@ -40,12 +40,12 @@ class ExtEvLoop implements LoopInterface /** * @var EvIo[] */ - private $readStreams = []; + private $readStreams = array(); /** * @var EvIo[] */ - private $writeStreams = []; + private $writeStreams = array(); /** * @var bool @@ -60,7 +60,7 @@ class ExtEvLoop implements LoopInterface /** * @var \EvSignal[] */ - private $signalEvents = []; + private $signalEvents = array(); public function __construct() { From 427853c16a6425d86b89f40067752864d488afa2 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Wed, 13 Jun 2018 12:27:13 +0200 Subject: [PATCH 113/203] Found zeh bug: `spl_objet_hash()` was used instead of `spl_object_hash()` --- src/Timer/Timers.php | 2 +- tests/Timer/TimersTest.php | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Timer/Timers.php b/src/Timer/Timers.php index 7944b4c1..fa4bd628 100644 --- a/src/Timer/Timers.php +++ b/src/Timer/Timers.php @@ -21,7 +21,7 @@ final class Timers public function updateTime() { - return $this->time = microtime(true); + return $this->time = \microtime(true); } public function getTime() diff --git a/tests/Timer/TimersTest.php b/tests/Timer/TimersTest.php index b279478c..d4e6987d 100644 --- a/tests/Timer/TimersTest.php +++ b/tests/Timer/TimersTest.php @@ -2,6 +2,7 @@ namespace React\Tests\EventLoop\Timer; +use React\EventLoop\TimerInterface; use React\Tests\EventLoop\TestCase; use React\EventLoop\Timer\Timer; use React\EventLoop\Timer\Timers; @@ -24,4 +25,19 @@ public function testBlockedTimer() $this->assertTrue(true); } + + public function testContains() + { + $timers = new Timers(); + + /** @var TimerInterface $timer1 */ + $timer1 = $this->createMock('React\EventLoop\TimerInterface'); + /** @var TimerInterface $timer2 */ + $timer2 = $this->createMock('React\EventLoop\TimerInterface'); + + $timers->add($timer1); + + self::assertTrue($timers->contains($timer1)); + self::assertFalse($timers->contains($timer2)); + } } From 75bead60043668902e39bbfc6bd5135161a18f6c Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Wed, 13 Jun 2018 12:27:35 +0200 Subject: [PATCH 114/203] Importing global functions - micro-optimisation, but relevant for this high-throughput component --- src/ExtEvLoop.php | 6 ++-- src/ExtEventLoop.php | 8 ++--- src/ExtLibevLoop.php | 10 +++--- src/ExtLibeventLoop.php | 62 ++++++++++++++++++------------------ src/Factory.php | 8 ++--- src/SignalsHandler.php | 2 +- src/StreamSelectLoop.php | 10 +++--- src/Tick/FutureTickQueue.php | 2 +- src/Timer/Timers.php | 18 +++++------ 9 files changed, 63 insertions(+), 63 deletions(-) diff --git a/src/ExtEvLoop.php b/src/ExtEvLoop.php index 4347263c..fedd5884 100644 --- a/src/ExtEvLoop.php +++ b/src/ExtEvLoop.php @@ -92,7 +92,7 @@ public function addReadStream($stream, $listener) private function getStreamListenerClosure($stream, $listener) { return function () use ($stream, $listener) { - call_user_func($listener, $stream); + \call_user_func($listener, $stream); }; } @@ -140,7 +140,7 @@ public function addTimer($interval, $callback) $that = $this; $timers = $this->timers; $callback = function () use ($timer, $timers, $that) { - call_user_func($timer->getCallback(), $timer); + \call_user_func($timer->getCallback(), $timer); if ($timers->contains($timer)) { $that->cancelTimer($timer); @@ -158,7 +158,7 @@ public function addPeriodicTimer($interval, $callback) $timer = new Timer($interval, $callback, true); $callback = function () use ($timer) { - call_user_func($timer->getCallback(), $timer); + \call_user_func($timer->getCallback(), $timer); }; $event = $this->loop->timer($interval, $interval, $callback); diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 622dd472..a6153505 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -39,7 +39,7 @@ final class ExtEventLoop implements LoopInterface public function __construct() { - if (!class_exists('EventBase', false)) { + if (!\class_exists('EventBase', false)) { throw new BadMethodCallException('Cannot create ExtEventLoop, ext-event extension missing'); } @@ -225,7 +225,7 @@ private function createTimerCallback() { $timers = $this->timerEvents; $this->timerCallback = function ($_, $__, $timer) use ($timers) { - call_user_func($timer->getCallback(), $timer); + \call_user_func($timer->getCallback(), $timer); if (!$timer->isPeriodic() && $timers->contains($timer)) { $this->cancelTimer($timer); @@ -248,11 +248,11 @@ private function createStreamCallback() $key = (int) $stream; if (Event::READ === (Event::READ & $flags) && isset($read[$key])) { - call_user_func($read[$key], $stream); + \call_user_func($read[$key], $stream); } if (Event::WRITE === (Event::WRITE & $flags) && isset($write[$key])) { - call_user_func($write[$key], $stream); + \call_user_func($write[$key], $stream); } }; } diff --git a/src/ExtLibevLoop.php b/src/ExtLibevLoop.php index d3b0df81..193c6c0d 100644 --- a/src/ExtLibevLoop.php +++ b/src/ExtLibevLoop.php @@ -37,7 +37,7 @@ final class ExtLibevLoop implements LoopInterface public function __construct() { - if (!class_exists('libev\EventLoop', false)) { + if (!\class_exists('libev\EventLoop', false)) { throw new BadMethodCallException('Cannot create ExtLibevLoop, ext-libev extension missing'); } @@ -54,7 +54,7 @@ public function addReadStream($stream, $listener) } $callback = function () use ($stream, $listener) { - call_user_func($listener, $stream); + \call_user_func($listener, $stream); }; $event = new IOEvent($callback, $stream, IOEvent::READ); @@ -70,7 +70,7 @@ public function addWriteStream($stream, $listener) } $callback = function () use ($stream, $listener) { - call_user_func($listener, $stream); + \call_user_func($listener, $stream); }; $event = new IOEvent($callback, $stream, IOEvent::WRITE); @@ -108,7 +108,7 @@ public function addTimer($interval, $callback) $that = $this; $timers = $this->timerEvents; $callback = function () use ($timer, $timers, $that) { - call_user_func($timer->getCallback(), $timer); + \call_user_func($timer->getCallback(), $timer); if ($timers->contains($timer)) { $that->cancelTimer($timer); @@ -127,7 +127,7 @@ public function addPeriodicTimer($interval, $callback) $timer = new Timer($interval, $callback, true); $callback = function () use ($timer) { - call_user_func($timer->getCallback(), $timer); + \call_user_func($timer->getCallback(), $timer); }; $event = new TimerEvent($callback, $interval, $interval); diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index 427f8db0..ddd66569 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -53,11 +53,11 @@ final class ExtLibeventLoop implements LoopInterface public function __construct() { - if (!function_exists('event_base_new')) { + if (!\function_exists('event_base_new')) { throw new BadMethodCallException('Cannot create ExtLibeventLoop, ext-libevent extension missing'); } - $this->eventBase = event_base_new(); + $this->eventBase = \event_base_new(); $this->futureTickQueue = new FutureTickQueue(); $this->timerEvents = new SplObjectStorage(); $this->signals = new SignalsHandler(); @@ -73,10 +73,10 @@ public function addReadStream($stream, $listener) return; } - $event = event_new(); - event_set($event, $stream, EV_PERSIST | EV_READ, $this->streamCallback); - event_base_set($event, $this->eventBase); - event_add($event); + $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; @@ -89,10 +89,10 @@ public function addWriteStream($stream, $listener) return; } - $event = event_new(); - event_set($event, $stream, EV_PERSIST | EV_WRITE, $this->streamCallback); - event_base_set($event, $this->eventBase); - event_add($event); + $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; @@ -104,8 +104,8 @@ public function removeReadStream($stream) if (isset($this->readListeners[$key])) { $event = $this->readEvents[$key]; - event_del($event); - event_free($event); + \event_del($event); + \event_free($event); unset( $this->readEvents[$key], @@ -120,8 +120,8 @@ public function removeWriteStream($stream) if (isset($this->writeListeners[$key])) { $event = $this->writeEvents[$key]; - event_del($event); - event_free($event); + \event_del($event); + \event_free($event); unset( $this->writeEvents[$key], @@ -152,8 +152,8 @@ public function cancelTimer(TimerInterface $timer) { if ($this->timerEvents->contains($timer)) { $event = $this->timerEvents[$timer]; - event_del($event); - event_free($event); + \event_del($event); + \event_free($event); $this->timerEvents->detach($timer); } @@ -169,10 +169,10 @@ 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]); + $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]); } } @@ -181,8 +181,8 @@ 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]); + \event_del($this->signalEvents[$signal]); + \event_free($this->signalEvents[$signal]); unset($this->signalEvents[$signal]); } } @@ -201,7 +201,7 @@ public function run() break; } - event_base_loop($this->eventBase, $flags); + \event_base_loop($this->eventBase, $flags); } } @@ -217,11 +217,11 @@ public function stop() */ private function scheduleTimer(TimerInterface $timer) { - $this->timerEvents[$timer] = $event = event_timer_new(); + $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); + \event_timer_set($event, $this->timerCallback, $timer); + \event_base_set($event, $this->eventBase); + \event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND); } /** @@ -236,7 +236,7 @@ private function createTimerCallback() $that = $this; $timers = $this->timerEvents; $this->timerCallback = function ($_, $__, $timer) use ($timers, $that) { - call_user_func($timer->getCallback(), $timer); + \call_user_func($timer->getCallback(), $timer); // Timer already cancelled ... if (!$timers->contains($timer)) { @@ -245,7 +245,7 @@ private function createTimerCallback() // Reschedule periodic timers ... if ($timer->isPeriodic()) { - event_add( + \event_add( $timers[$timer], $timer->getInterval() * ExtLibeventLoop::MICROSECONDS_PER_SECOND ); @@ -272,11 +272,11 @@ private function createStreamCallback() $key = (int) $stream; if (EV_READ === (EV_READ & $flags) && isset($read[$key])) { - call_user_func($read[$key], $stream); + \call_user_func($read[$key], $stream); } if (EV_WRITE === (EV_WRITE & $flags) && isset($write[$key])) { - call_user_func($write[$key], $stream); + \call_user_func($write[$key], $stream); } }; } diff --git a/src/Factory.php b/src/Factory.php index b46fc074..48801466 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -24,13 +24,13 @@ final class Factory public static function create() { // @codeCoverageIgnoreStart - if (class_exists('libev\EventLoop', false)) { + if (\class_exists('libev\EventLoop', false)) { return new ExtLibevLoop(); - } elseif (class_exists('EvLoop', false)) { + } elseif (\class_exists('EvLoop', false)) { return new ExtEvLoop(); - } elseif (class_exists('EventBase', false)) { + } elseif (\class_exists('EventBase', false)) { return new ExtEventLoop(); - } elseif (function_exists('event_base_new') && PHP_VERSION_ID < 70000) { + } elseif (\function_exists('event_base_new') && PHP_VERSION_ID < 70000) { // only use ext-libevent on PHP < 7 for now return new ExtLibeventLoop(); } diff --git a/src/SignalsHandler.php b/src/SignalsHandler.php index 523e1ca1..10d125df 100644 --- a/src/SignalsHandler.php +++ b/src/SignalsHandler.php @@ -15,7 +15,7 @@ public function add($signal, $listener) $this->signals[$signal] = array(); } - if (in_array($listener, $this->signals[$signal])) { + if (\in_array($listener, $this->signals[$signal])) { return; } diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index e82e9e47..7b822e01 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -68,7 +68,7 @@ public function __construct() { $this->futureTickQueue = new FutureTickQueue(); $this->timers = new Timers(); - $this->pcntl = extension_loaded('pcntl'); + $this->pcntl = \extension_loaded('pcntl'); $this->signals = new SignalsHandler(); } @@ -235,7 +235,7 @@ private function waitForStreamActivity($timeout) $key = (int) $stream; if (isset($this->readListeners[$key])) { - call_user_func($this->readListeners[$key], $stream); + \call_user_func($this->readListeners[$key], $stream); } } @@ -243,7 +243,7 @@ private function waitForStreamActivity($timeout) $key = (int) $stream; if (isset($this->writeListeners[$key])) { - call_user_func($this->writeListeners[$key], $stream); + \call_user_func($this->writeListeners[$key], $stream); } } } @@ -265,10 +265,10 @@ private function streamSelect(array &$read, array &$write, $timeout) $except = null; // suppress warnings that occur, when stream_select is interrupted by a signal - return @stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); + return @\stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); } - $timeout && usleep($timeout); + $timeout && \usleep($timeout); return 0; } diff --git a/src/Tick/FutureTickQueue.php b/src/Tick/FutureTickQueue.php index c79afc56..efabcbc5 100644 --- a/src/Tick/FutureTickQueue.php +++ b/src/Tick/FutureTickQueue.php @@ -42,7 +42,7 @@ public function tick() $count = $this->queue->count(); while ($count--) { - call_user_func( + \call_user_func( $this->queue->dequeue() ); } diff --git a/src/Timer/Timers.php b/src/Timer/Timers.php index fa4bd628..1d4ac9b7 100644 --- a/src/Timer/Timers.php +++ b/src/Timer/Timers.php @@ -31,20 +31,20 @@ public function getTime() public function add(TimerInterface $timer) { - $id = spl_object_hash($timer); + $id = \spl_object_hash($timer); $this->timers[$id] = $timer; - $this->schedule[$id] = $timer->getInterval() + microtime(true); + $this->schedule[$id] = $timer->getInterval() + \microtime(true); $this->sorted = false; } public function contains(TimerInterface $timer) { - return isset($this->timers[spl_oject_hash($timer)]); + return isset($this->timers[\spl_object_hash($timer)]); } public function cancel(TimerInterface $timer) { - $id = spl_object_hash($timer); + $id = \spl_object_hash($timer); unset($this->timers[$id], $this->schedule[$id]); } @@ -53,15 +53,15 @@ public function getFirst() // ensure timers are sorted to simply accessing next (first) one if (!$this->sorted) { $this->sorted = true; - asort($this->schedule); + \asort($this->schedule); } - return reset($this->schedule); + return \reset($this->schedule); } public function isEmpty() { - return count($this->timers) === 0; + return \count($this->timers) === 0; } public function tick() @@ -69,7 +69,7 @@ public function tick() // ensure timers are sorted so we can execute in order if (!$this->sorted) { $this->sorted = true; - asort($this->schedule); + \asort($this->schedule); } $time = $this->updateTime(); @@ -86,7 +86,7 @@ public function tick() } $timer = $this->timers[$id]; - call_user_func($timer->getCallback(), $timer); + \call_user_func($timer->getCallback(), $timer); // re-schedule if this is a periodic timer and it has not been cancelled explicitly already if ($timer->isPeriodic() && isset($this->timers[$id])) { From b048bd729a5e462c5352127bd3cd63ed95dfc8ae Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Wed, 13 Jun 2018 15:02:17 +0200 Subject: [PATCH 115/203] Using a real `Timer` instance, since pre-historic PHPUnit versions are being used in CI --- tests/Timer/TimersTest.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/Timer/TimersTest.php b/tests/Timer/TimersTest.php index d4e6987d..e7681c96 100644 --- a/tests/Timer/TimersTest.php +++ b/tests/Timer/TimersTest.php @@ -2,7 +2,6 @@ namespace React\Tests\EventLoop\Timer; -use React\EventLoop\TimerInterface; use React\Tests\EventLoop\TestCase; use React\EventLoop\Timer\Timer; use React\EventLoop\Timer\Timers; @@ -30,10 +29,8 @@ public function testContains() { $timers = new Timers(); - /** @var TimerInterface $timer1 */ - $timer1 = $this->createMock('React\EventLoop\TimerInterface'); - /** @var TimerInterface $timer2 */ - $timer2 = $this->createMock('React\EventLoop\TimerInterface'); + $timer1 = new Timer(0.1, function () {}); + $timer2 = new Timer(0.1, function () {}); $timers->add($timer1); From 88d35a67c1b4cde4cea28643c1f4a6a52697a1c9 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Thu, 14 Jun 2018 03:01:01 +0200 Subject: [PATCH 116/203] Replaced constant references with fully qualified constant references --- src/ExtEventLoop.php | 4 ++-- src/ExtLibeventLoop.php | 14 +++++++------- src/Factory.php | 2 +- src/StreamSelectLoop.php | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index a6153505..fd403d4a 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -69,7 +69,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) { + if (\PHP_VERSION_ID >= 70000) { $this->readRefs[$key] = $stream; } } @@ -88,7 +88,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) { + if (\PHP_VERSION_ID >= 70000) { $this->writeRefs[$key] = $stream; } } diff --git a/src/ExtLibeventLoop.php b/src/ExtLibeventLoop.php index ddd66569..55c2fca0 100644 --- a/src/ExtLibeventLoop.php +++ b/src/ExtLibeventLoop.php @@ -74,7 +74,7 @@ public function addReadStream($stream, $listener) } $event = \event_new(); - \event_set($event, $stream, EV_PERSIST | EV_READ, $this->streamCallback); + \event_set($event, $stream, \EV_PERSIST | \EV_READ, $this->streamCallback); \event_base_set($event, $this->eventBase); \event_add($event); @@ -90,7 +90,7 @@ public function addWriteStream($stream, $listener) } $event = \event_new(); - \event_set($event, $stream, EV_PERSIST | EV_WRITE, $this->streamCallback); + \event_set($event, $stream, \EV_PERSIST | \EV_WRITE, $this->streamCallback); \event_base_set($event, $this->eventBase); \event_add($event); @@ -170,7 +170,7 @@ public function addSignal($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_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]); } @@ -194,9 +194,9 @@ public function run() while ($this->running) { $this->futureTickQueue->tick(); - $flags = EVLOOP_ONCE; + $flags = \EVLOOP_ONCE; if (!$this->running || !$this->futureTickQueue->isEmpty()) { - $flags |= EVLOOP_NONBLOCK; + $flags |= \EVLOOP_NONBLOCK; } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) { break; } @@ -271,11 +271,11 @@ private function createStreamCallback() $this->streamCallback = function ($stream, $flags) use (&$read, &$write) { $key = (int) $stream; - if (EV_READ === (EV_READ & $flags) && isset($read[$key])) { + if (\EV_READ === (\EV_READ & $flags) && isset($read[$key])) { \call_user_func($read[$key], $stream); } - if (EV_WRITE === (EV_WRITE & $flags) && isset($write[$key])) { + 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 48801466..763c077b 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -30,7 +30,7 @@ public static function create() return new ExtEvLoop(); } elseif (\class_exists('EventBase', false)) { return new ExtEventLoop(); - } elseif (\function_exists('event_base_new') && PHP_VERSION_ID < 70000) { + } elseif (\function_exists('event_base_new') && \PHP_VERSION_ID < 70000) { // only use ext-libevent on PHP < 7 for now return new ExtLibeventLoop(); } diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 7b822e01..625b6fb6 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -163,7 +163,7 @@ public function removeSignal($signal, $listener) $this->signals->remove($signal, $listener); if ($this->signals->count($signal) === 0) { - \pcntl_signal($signal, SIG_DFL); + \pcntl_signal($signal, \SIG_DFL); } } @@ -190,7 +190,7 @@ public function run() // Ensure we do not exceed maximum integer size, which may // cause the loop to tick once every ~35min on 32bit systems. $timeout *= self::MICROSECONDS_PER_SECOND; - $timeout = $timeout > PHP_INT_MAX ? PHP_INT_MAX : (int)$timeout; + $timeout = $timeout > \PHP_INT_MAX ? \PHP_INT_MAX : (int)$timeout; } // The only possible event is stream or signal activity, so wait forever ... From 4f2108e95a512426f39330b4bafb3a6c90aa21ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Cobucci?= Date: Mon, 25 Jun 2018 23:37:46 +0200 Subject: [PATCH 117/203] Simplify test bootstrap by using dev autoloader This is already being done in other repos of the organisation, let's make it more consistent then =) --- composer.json | 5 +++++ tests/bootstrap.php | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 24974ece..c8ff91ec 100644 --- a/composer.json +++ b/composer.json @@ -17,5 +17,10 @@ "psr-4": { "React\\EventLoop\\": "src" } + }, + "autoload-dev": { + "psr-4": { + "React\\Tests\\EventLoop\\": "tests" + } } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index ea7dd4cc..aeb44352 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,11 +1,5 @@ addPsr4('React\\Tests\\EventLoop\\', __DIR__); - if (!defined('SIGUSR1')) { define('SIGUSR1', 1); } From 228178a947de1f7cd9296d691878569628288c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 9 Jul 2018 15:51:04 +0200 Subject: [PATCH 118/203] Prepare v0.5.3 release --- CHANGELOG.md | 11 +++++++++++ README.md | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eb41703..d98bd69f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 0.5.3 (2018-07-09) + +* Improve performance by importing global functions. + (#167 by @Ocramius) + +* Improve test suite by simplifying test bootstrap by using dev autoloader. + (#169 by @lcobucci) + +* Minor internal changes to improved backward compatibility with PHP 5.3. + (#166 by @Donatello-za) + ## 0.5.2 (2018-04-24) * Feature: Improve memory consumption and runtime performance for `StreamSelectLoop` timers. diff --git a/README.md b/README.md index 6233b37c..62650b7c 100644 --- a/README.md +++ b/README.md @@ -661,7 +661,7 @@ The recommended way to install this library is [through Composer](https://getcom This will install the latest supported version: ```bash -$ composer require react/event-loop:^0.5.2 +$ composer require react/event-loop:^0.5.3 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 0266aff7aa7b0613b1f38a723e14a0ebc55cfca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 11 Jul 2018 16:37:46 +0200 Subject: [PATCH 119/203] Prepare v1.0.0 release --- CHANGELOG.md | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d98bd69f..1bd7f5f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 1.0.0 (2018-07-11) + +* First stable LTS release, now following [SemVer](https://semver.org/). + We'd like to emphasize that this component is production ready and battle-tested. + We plan to support all long-term support (LTS) releases for at least 24 months, + so you have a rock-solid foundation to build on top of. + +> Contains no other changes, so it's actually fully compatible with the v0.5.3 release. + ## 0.5.3 (2018-07-09) * Improve performance by importing global functions. diff --git a/README.md b/README.md index 62650b7c..40cb822a 100644 --- a/README.md +++ b/README.md @@ -658,10 +658,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: ```bash -$ composer require react/event-loop:^0.5.3 +$ composer require react/event-loop:^1.0 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 79ca43cc00272160eed6c21cc1c0c89fc99ae5d2 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Fri, 26 Oct 2018 17:23:32 +0200 Subject: [PATCH 120/203] 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 121/203] 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 122/203] 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 123/203] 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 124/203] 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 125/203] 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 126/203] 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 127/203] 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 128/203] 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 129/203] 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 130/203] 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 131/203] 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 132/203] 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 133/203] 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 134/203] 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 135/203] 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 136/203] 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 137/203] 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 138/203] 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 139/203] 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 140/203] 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 141/203] 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 142/203] 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 143/203] 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 144/203] 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 145/203] 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 146/203] 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 147/203] 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 148/203] 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 149/203] 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 150/203] 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 151/203] 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 152/203] 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 153/203] 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 154/203] 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 155/203] 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 156/203] 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 157/203] 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 158/203] 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 159/203] 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 160/203] 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 161/203] 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 162/203] 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 163/203] 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 164/203] 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 165/203] 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 166/203] 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 167/203] 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 168/203] 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 169/203] 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 170/203] 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 171/203] 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 172/203] 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 173/203] 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 174/203] 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 175/203] 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 176/203] 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 177/203] 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 178/203] 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 179/203] 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 180/203] 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 181/203] 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 182/203] 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 183/203] 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 184/203] 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 185/203] 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 186/203] 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 187/203] 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 188/203] 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 189/203] 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 190/203] 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 191/203] 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 192/203] 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 193/203] 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 194/203] [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 195/203] 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 196/203] 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 197/203] 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 198/203] 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 199/203] 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 200/203] 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 201/203] 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 202/203] 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 203/203] 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()); }