-
-
Notifications
You must be signed in to change notification settings - Fork 130
[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
Conversation
Consider running the shutdown function only if the loop has not yet been run. Otherwise, if the loop has been explicitly stopped, there may be events and timers that would resume on exit. public static function get()
{
if (self::$loop) {
return self::$loop;
}
register_shutdown_function(function () {
if (self::$disableRunOnShutdown || !self::$loop) {
return;
}
self::$loop->run();
});
self::$loop = self::create();
// disable auto-start if the loop gets run elsewhere
self::$loop->futureTick(function () {
self::$disableRunOnShutdown = true;
});
return self::$loop;
} |
@mbonneau When do you think a global loop should be stopped? In tests, you probably want to call |
@jsor I would suggest that the following code is not intuitive: <?php
use React\EventLoop\Timer\Timer;
require __DIR__ . '/vendor/autoload.php';
$loop = React\EventLoop\GlobalLoop::get();
$timer = $loop->addTimer(1, function (Timer $timer) {
$timer->getLoop()->stop();
});
$periodicTimer = $loop->addPeriodicTimer(0.25, function (Timer $timer) {
echo "Doing something periodically.\n";
});
$loop->run(); Whether this is correct use of the event loop is debatable. |
@mbonneau What is require __DIR__ . '/vendor/autoload.php';
$periodicTimer = React\EventLoop\addPeriodicTimer(0.25, function () {
echo "Doing something periodically.\n";
});
React\EventLoop\addTimer(1, function () use ($periodicTimer) {
$periodicTimer->cancel();
}); Maybe, If you need to control the global loop, eg. in tests, you sould call |
Just discovered reactphp/reactphp#330 again. This is basically the same i'm proposing here and this PR would be the foundation for implementing a functional API. |
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.
Loving this idea, lets make this happen 👍
**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. |
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.
Could you add documentation how this is still the case when using the default factory. And also documentation how this change would work in general.
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.
This RFC ist just a POC to illustrate the idea and is not meant to be merged as is. If this is going to happen, we must if course add additional documentation, not only for how the factory works.
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.
Yes of course, but looking at this POC I'de like, either here or in another PR, get started working this out. My preference would be on this PR though.
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.
What i meant was: i surely add docs, but only after the other reviewers have not rejected this RFC as pure non-sense :P
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.
Yes I gathered that 👍
@@ -17,6 +17,7 @@ | |||
"autoload": { | |||
"psr-4": { | |||
"React\\EventLoop\\": "src" | |||
} | |||
}, | |||
"files": ["src/functions.php"] |
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.
* | ||
* @var LoopInterface | ||
*/ | ||
public static $loop; |
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.
Why not always force GlobalLoop::get
? Making this public could potentially mean someone swaps it out and breaks everything.
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.
This is currently public for 2 reasons:
- It allows to save a function call in the function API, see eg. https://github.com/reactphp/event-loop/pull/77/files#diff-84b928a9905378810b602c8a740b4cbaR16
- It allows to swap out the loop, see eg. https://github.com/reactphp/event-loop/pull/77/files#diff-6872a075db159cbbcfc073b693c7d460R18
Note, that the property is marked @internal
, so it should be pretty clear the messing around with it isn't a good idea ;)
Again, this is just a POC, so we might decide to make it private, always use GlobalLoop::get()
and add a corresponding GlobalLoop::set()
for swapping out the loop, eg. in tests.
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.
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.
@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 Loop::execute
scoping. The current loop can be a private property and swapping out the loop is very easy and not something you have to worry about.
I'm curious about the decision to use functions here instead of static methods on the Loop class. The functionality in those functions is very much dependent on the state of GlobalLoop. |
@joshdifabio Apart from The functional API targets applications. React\EventLoop\futureTick(function() {
}); React\Dns\resolve('reactphp.org')->then(function($ip) {
}); The // bootstrap file
React\EventLoop\GlobalLoop::setFactory($factory); namespace React\Dns;
function resolve($name)
{
$factory = new React\Dns\Resolver\Factory();
return $factory->create('8.8.8.8', React\EventLoop\GlobalLoop::get())->resolve($name);
} |
@jsor I don't agree with the rationale behind using functions, but I appreciate the time taken to explain. Thanks. How do you guys see this interoperating with the interop standard? |
Since this is a RFC, i'm really interested in your thoughts/comments on this regarding a better API.
Something like this probably: React\EventLoop\GlobalLoop::setFactory(function() {
return new WyriHaximus\React\AsyncInteropLoop\AsyncInteropLoop();
}); |
If you really make the change and use a global accessor, why not directly support async-interop? I can see that it makes no sense to support async-interop when you don't have a global accessor, but why have your own? I can also only strongly advise against running an entire application in a shutdown function. Any exit will directly exit the whole application instead of running other shutdown hooks as expected. |
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 comment
The reason will be displayed to describe this comment to others. Learn more.
How can it be reset to work in tests?
Why should libraries use the API differently than application code?
@jsor It's marked as internal, but is basically required by all test suites to fiddle around with? Doesn't seem like a good API decision then. |
In our components, the loop instance gets injected. They do not use the global loop directly and thus do not depend on the global loop in tests. Only the thin functional layer on top, which basically just does some wiring, accesses the gobal loop. |
I've filed #82 as a simplified alternative. |
Closing this as it has been resolved through #226 and follow up PR's |
This is an alternative to #10 introducing a global loop on top of the current API and being backward compatible.
It adds a new API via functions on top of the current API for accessing the methods of the global loop (except
run()
andstop
).This allows components to also introduce their own functions on top of their existing API using the global loop.
By default, the driver is created by
Factory::create()
. This can be overridden by setting a custom factory usingGlobalLoop::setFactory()
which expects a simplecallable
returning aLoopInterface
.The global loop is run automatically in a shutdown function, so no need to call
run()
manually. This behaviour can be disabled by callingGlobalLoop::disableRunOnShutdown()
.This is a RFC, so share your thoughts and comments :)