From 4b7727e16b74ffd47003d38293114ffc53546f34 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] 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 447ad61c..ad119fff 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,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 @@ +