-
-
Notifications
You must be signed in to change notification settings - Fork 132
[RFC] Global Loop #77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
"autoload": { | ||
"psr-4": { | ||
"React\\EventLoop\\": "src" | ||
} | ||
}, | ||
"files": ["src/functions.php"] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
<?php | ||
|
||
namespace React\EventLoop; | ||
|
||
final class GlobalLoop | ||
{ | ||
/** | ||
* @internal | ||
* | ||
* @var LoopInterface | ||
*/ | ||
public static $loop; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not always force There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is currently public for 2 reasons:
Note, that the property is marked Again, this is just a POC, so we might decide to make it private, always use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jsor @WyriHaximus This is one reason why https://github.com/async-interop/event-loop uses a static class instead of functions and has the clear |
||
|
||
private static $factory = ['React\EventLoop\Factory', 'create']; | ||
|
||
private static $disableRunOnShutdown = false; | ||
|
||
public static function setFactory(callable $factory) | ||
{ | ||
if (self::$loop) { | ||
throw new \LogicException( | ||
'Setting a factory after the global loop has been created is not allowed.' | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How can it be reset to work in tests? |
||
} | ||
|
||
self::$factory = $factory; | ||
} | ||
|
||
public function disableRunOnShutdown() | ||
{ | ||
self::$disableRunOnShutdown = true; | ||
} | ||
|
||
/** | ||
* @return LoopInterface | ||
*/ | ||
public static function get() | ||
{ | ||
if (self::$loop) { | ||
return self::$loop; | ||
} | ||
|
||
register_shutdown_function(function () { | ||
if (self::$disableRunOnShutdown || !self::$loop) { | ||
return; | ||
} | ||
|
||
self::$loop->run(); | ||
}); | ||
|
||
return self::$loop = self::create(); | ||
} | ||
|
||
/** | ||
* @return LoopInterface | ||
*/ | ||
public static function create() | ||
{ | ||
$loop = call_user_func(self::$factory); | ||
|
||
if (!$loop instanceof LoopInterface) { | ||
throw new \LogicException( | ||
sprintf( | ||
'The GlobalLoop factory must return an instance of LoopInterface but returned %s.', | ||
is_object($loop) ? get_class($loop) : gettype($loop) | ||
) | ||
); | ||
} | ||
|
||
return $loop; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
<?php | ||
|
||
namespace React\EventLoop; | ||
|
||
use React\EventLoop\Timer\TimerInterface; | ||
|
||
/** | ||
* Register a listener to the global event loop to be notified when a stream is | ||
* ready to read. | ||
* | ||
* @param resource $stream The PHP stream resource to check. | ||
* @param callable $listener Invoked when the stream is ready. | ||
*/ | ||
function addReadStream($stream, callable $listener) | ||
{ | ||
$loop = GlobalLoop::$loop ?: GlobalLoop::get(); | ||
|
||
$loop->addReadStream($stream, $listener); | ||
} | ||
|
||
/** | ||
* Register a listener to the global event loop to be notified when a stream is | ||
* ready to write. | ||
* | ||
* @param resource $stream The PHP stream resource to check. | ||
* @param callable $listener Invoked when the stream is ready. | ||
*/ | ||
function addWriteStream($stream, callable $listener) | ||
{ | ||
$loop = GlobalLoop::$loop ?: GlobalLoop::get(); | ||
|
||
$loop->addWriteStream($stream, $listener); | ||
} | ||
|
||
/** | ||
* Remove the read event listener from the global event loop for the given | ||
* stream. | ||
* | ||
* @param resource $stream The PHP stream resource. | ||
*/ | ||
function removeReadStream($stream) | ||
{ | ||
$loop = GlobalLoop::$loop ?: GlobalLoop::get(); | ||
|
||
$loop->removeReadStream($stream); | ||
} | ||
|
||
/** | ||
* Remove the write event listener from the global event loop for the given | ||
* stream. | ||
* | ||
* @param resource $stream The PHP stream resource. | ||
*/ | ||
function removeWriteStream($stream) | ||
{ | ||
$loop = GlobalLoop::$loop ?: GlobalLoop::get(); | ||
|
||
$loop->removeWriteStream($stream); | ||
} | ||
|
||
/** | ||
* Remove all listeners from the global event loop for the given stream. | ||
* | ||
* @param resource $stream The PHP stream resource. | ||
*/ | ||
function removeStream($stream) | ||
{ | ||
$loop = GlobalLoop::$loop ?: GlobalLoop::get(); | ||
|
||
$loop->removeStream($stream); | ||
} | ||
|
||
/** | ||
* Enqueue a callback to the global event loop to be invoked once after the | ||
* given interval. | ||
* | ||
* The execution order of timers scheduled to execute at the same time is | ||
* not guaranteed. | ||
* | ||
* @param int|float $interval The number of seconds to wait before execution. | ||
* @param callable $callback The callback to invoke. | ||
* | ||
* @return TimerInterface | ||
*/ | ||
function addTimer($interval, callable $callback) | ||
{ | ||
$loop = GlobalLoop::$loop ?: GlobalLoop::get(); | ||
|
||
return $loop->addTimer($interval, $callback); | ||
} | ||
|
||
/** | ||
* Enqueue a callback to the global event loop to be invoked repeatedly after | ||
* the given interval. | ||
* | ||
* The execution order of timers scheduled to execute at the same time is | ||
* not guaranteed. | ||
* | ||
* @param int|float $interval The number of seconds to wait before execution. | ||
* @param callable $callback The callback to invoke. | ||
* | ||
* @return TimerInterface | ||
*/ | ||
function addPeriodicTimer($interval, callable $callback) | ||
{ | ||
$loop = GlobalLoop::$loop ?: GlobalLoop::get(); | ||
|
||
return $loop->addPeriodicTimer($interval, $callback); | ||
} | ||
|
||
/** | ||
* Schedule a callback to be invoked on a future tick of the global event loop. | ||
* | ||
* Callbacks are guaranteed to be executed in the order they are enqueued. | ||
* | ||
* @param callable $listener The callback to invoke. | ||
*/ | ||
function futureTick(callable $listener) | ||
{ | ||
$loop = GlobalLoop::$loop ?: GlobalLoop::get(); | ||
|
||
$loop->futureTick($listener); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
<?php | ||
|
||
namespace React\Tests\EventLoop; | ||
|
||
use React\EventLoop; | ||
use React\EventLoop\GlobalLoop; | ||
|
||
class FunctionTest extends TestCase | ||
{ | ||
private static $state; | ||
private $globalLoop; | ||
|
||
public function setUp() | ||
{ | ||
$globalLoop = $this->getMockBuilder('React\EventLoop\LoopInterface') | ||
->getMock(); | ||
|
||
self::$state = GlobalLoop::$loop; | ||
GlobalLoop::$loop = $this->globalLoop = $globalLoop; | ||
} | ||
|
||
public function tearDown() | ||
{ | ||
$this->globalLoop = null; | ||
|
||
GlobalLoop::$loop = self::$state; | ||
} | ||
|
||
public function createStream() | ||
{ | ||
return fopen('php://temp', 'r+'); | ||
} | ||
|
||
public function testAddReadStream() | ||
{ | ||
$stream = $this->createStream(); | ||
$listener = function() {}; | ||
|
||
$this->globalLoop | ||
->expects($this->once()) | ||
->method('addReadStream') | ||
->with($stream, $listener); | ||
|
||
EventLoop\addReadStream($stream, $listener); | ||
} | ||
|
||
public function testAddWriteStream() | ||
{ | ||
$stream = $this->createStream(); | ||
$listener = function() {}; | ||
|
||
$this->globalLoop | ||
->expects($this->once()) | ||
->method('addWriteStream') | ||
->with($stream, $listener); | ||
|
||
EventLoop\addWriteStream($stream, $listener); | ||
} | ||
|
||
public function testRemoveReadStream() | ||
{ | ||
$stream = $this->createStream(); | ||
|
||
$this->globalLoop | ||
->expects($this->once()) | ||
->method('removeReadStream') | ||
->with($stream); | ||
|
||
EventLoop\removeReadStream($stream); | ||
} | ||
|
||
public function testRemoveWriteStream() | ||
{ | ||
$stream = $this->createStream(); | ||
|
||
$this->globalLoop | ||
->expects($this->once()) | ||
->method('removeWriteStream') | ||
->with($stream); | ||
|
||
EventLoop\removeWriteStream($stream); | ||
} | ||
|
||
public function testRemoveStream() | ||
{ | ||
$stream = $this->createStream(); | ||
|
||
$this->globalLoop | ||
->expects($this->once()) | ||
->method('removeStream') | ||
->with($stream); | ||
|
||
EventLoop\removeStream($stream); | ||
} | ||
|
||
public function testAddTimer() | ||
{ | ||
$interval = 1; | ||
$listener = function() {}; | ||
|
||
$this->globalLoop | ||
->expects($this->once()) | ||
->method('addTimer') | ||
->with($interval, $listener); | ||
|
||
EventLoop\addTimer($interval, $listener); | ||
} | ||
|
||
public function testAddPeriodicTimer() | ||
{ | ||
$interval = 1; | ||
$listener = function() {}; | ||
|
||
$this->globalLoop | ||
->expects($this->once()) | ||
->method('addPeriodicTimer') | ||
->with($interval, $listener); | ||
|
||
EventLoop\addPeriodicTimer($interval, $listener); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I'm fan of using an intermediary
functions_include.php
like this, this avoids weird edge cases when using threads:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course, thanks for the reminder!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@WyriHaximus Which edge cases exist there? Could you elaborate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See reactphp/promise#23 and reactphp/promise#25. Might be fixed in the meantime by composer/composer#4186.