Skip to content

Commit

Permalink
Merge pull request #266 from clue-labs/perf
Browse files Browse the repository at this point in the history
Improve performance of `Loop` by avoiding unneeded method calls
  • Loading branch information
SimonFrings authored May 5, 2023
2 parents a78ae0b + 2f6d3cd commit 1e7460b
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 13 deletions.
67 changes: 54 additions & 13 deletions src/Loop.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
final class Loop
{
/**
* @var LoopInterface
* @var ?LoopInterface
*/
private static $instance;

Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}
}

/**
Expand All @@ -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);
}
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}
}

/**
Expand All @@ -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();
}

/**
Expand All @@ -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();
}
}
}
159 changes: 159 additions & 0 deletions tests/LoopTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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();
Expand All @@ -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 () { };
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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
Expand Down

0 comments on commit 1e7460b

Please sign in to comment.