From 9cf16bc2d973d09b3552f03f80e904252e40a075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 19 Apr 2022 22:54:20 +0200 Subject: [PATCH 1/2] Only report loop restart message until socket is closed --- src/App.php | 10 +++++++--- tests/AppTest.php | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/App.php b/src/App.php index 96fc1fc..a54e6e1 100644 --- a/src/App.php +++ b/src/App.php @@ -210,9 +210,13 @@ private function runLoop() do { Loop::run(); - // Fiber compatibility mode for PHP < 8.1: Restart loop as long as socket is available - $this->sapi->log('Warning: Loop restarted. Upgrade to react/async v4 recommended for production use.'); - } while ($socket->getAddress() !== null); + if ($socket->getAddress() !== null) { + // Fiber compatibility mode for PHP < 8.1: Restart loop as long as socket is available + $this->sapi->log('Warning: Loop restarted. Upgrade to react/async v4 recommended for production use.'); + } else { + break; + } + } while (true); } private function runOnce() diff --git a/tests/AppTest.php b/tests/AppTest.php index 008685b..96770fa 100644 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -215,6 +215,28 @@ public function testRunWillReportListeningAddressFromEnvironmentWithRandomPortAn $app->run(); } + public function testRunWillRestartLoopUntilSocketIsClosed() + { + $_SERVER['X_LISTEN'] = '127.0.0.1:0'; + $app = new App(); + + // lovely: remove socket server on next tick to terminate loop + Loop::futureTick(function () { + $resources = get_resources(); + $socket = end($resources); + + Loop::futureTick(function () use ($socket) { + Loop::removeReadStream($socket); + fclose($socket); + }); + + Loop::stop(); + }); + + $this->expectOutputRegex('/' . preg_quote('Warning: Loop restarted. Upgrade to react/async v4 recommended for production use.' . PHP_EOL, '/') . '$/'); + $app->run(); + } + public function testRunAppWithEmptyAddressThrows() { $_SERVER['X_LISTEN'] = ''; From d9604a2dd295b4370cdb4c4874dd5beebd5750d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 18 Mar 2022 16:25:53 +0100 Subject: [PATCH 2/2] Add signal handling support for `SIGINT` and `SIGTERM` --- src/App.php | 24 ++++++++++++++++++++++++ tests/AppTest.php | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/src/App.php b/src/App.php index a54e6e1..270393e 100644 --- a/src/App.php +++ b/src/App.php @@ -207,6 +207,26 @@ private function runLoop() \fwrite(STDERR, (string)$orig); }); + try { + Loop::addSignal(\defined('SIGINT') ? \SIGINT : 2, $f1 = function () use ($socket) { + if (\PHP_VERSION_ID >= 70200 && \stream_isatty(\STDIN)) { + echo "\r"; + } + $this->sapi->log('Received SIGINT, stopping loop'); + + $socket->close(); + Loop::stop(); + }); + Loop::addSignal(\defined('SIGTERM') ? \SIGTERM : 15, $f2 = function () use ($socket) { + $this->sapi->log('Received SIGTERM, stopping loop'); + + $socket->close(); + Loop::stop(); + }); + } catch (\BadMethodCallException $e) { // @codeCoverageIgnoreStart + $this->sapi->log('Notice: No signal handler support, installing ext-ev or ext-pcntl recommended for production use.'); + } // @codeCoverageIgnoreEnd + do { Loop::run(); @@ -217,6 +237,10 @@ private function runLoop() break; } } while (true); + + // remove signal handlers when loop stops (if registered) + Loop::removeSignal(\defined('SIGINT') ? \SIGINT : 2, $f1); + Loop::removeSignal(\defined('SIGTERM') ? \SIGTERM : 15, $f2 ?? 'printf'); } private function runOnce() diff --git a/tests/AppTest.php b/tests/AppTest.php index 96770fa..845f6e7 100644 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -169,6 +169,8 @@ public function testRunWillReportListeningAddressAndRunLoopWithSocketServer() Loop::removeReadStream($socket); fclose($socket); + + Loop::stop(); }); $this->expectOutputRegex('/' . preg_quote('Listening on http://127.0.0.1:8080' . PHP_EOL, '/') . '.*/'); @@ -191,6 +193,8 @@ public function testRunWillReportListeningAddressFromEnvironmentAndRunLoopWithSo Loop::removeReadStream($socket); fclose($socket); + + Loop::stop(); }); $this->expectOutputRegex('/' . preg_quote('Listening on http://' . $addr . PHP_EOL, '/') . '.*/'); @@ -209,6 +213,8 @@ public function testRunWillReportListeningAddressFromEnvironmentWithRandomPortAn Loop::removeReadStream($socket); fclose($socket); + + Loop::stop(); }); $this->expectOutputRegex('/' . preg_quote('Listening on http://127.0.0.1:', '/') . '\d+' . PHP_EOL . '.*/'); @@ -228,6 +234,8 @@ public function testRunWillRestartLoopUntilSocketIsClosed() Loop::futureTick(function () use ($socket) { Loop::removeReadStream($socket); fclose($socket); + + Loop::stop(); }); Loop::stop(); @@ -236,6 +244,40 @@ public function testRunWillRestartLoopUntilSocketIsClosed() $this->expectOutputRegex('/' . preg_quote('Warning: Loop restarted. Upgrade to react/async v4 recommended for production use.' . PHP_EOL, '/') . '$/'); $app->run(); } + + /** + * @requires function pcntl_signal + * @requires function posix_kill + */ + public function testRunWillStopWhenReceivingSigint() + { + $_SERVER['X_LISTEN'] = '127.0.0.1:0'; + $app = new App(); + + Loop::futureTick(function () { + posix_kill(getmypid(), defined('SIGINT') ? SIGINT : 2); + }); + + $this->expectOutputRegex('/' . preg_quote('Received SIGINT, stopping loop' . PHP_EOL, '/') . '$/'); + $app->run(); + } + + /** + * @requires function pcntl_signal + * @requires function posix_kill + */ + public function testRunWillStopWhenReceivingSigterm() + { + $_SERVER['X_LISTEN'] = '127.0.0.1:0'; + $app = new App(); + + Loop::futureTick(function () { + posix_kill(getmypid(), defined('SIGTERM') ? SIGTERM : 15); + }); + + $this->expectOutputRegex('/' . preg_quote('Received SIGTERM, stopping loop' . PHP_EOL, '/') . '$/'); + $app->run(); + } public function testRunAppWithEmptyAddressThrows() {