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] 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 pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy