From bc0bca9c6d88320d552fecd6d5e755ca081fb6e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 17 Feb 2022 15:50:48 +0100 Subject: [PATCH 1/2] Do not suppress warnings for invalid streams in `stream_select()` --- src/StreamSelectLoop.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 4b1f81c1..14a2613f 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -287,8 +287,15 @@ private function streamSelect(array &$read, array &$write, $timeout) } } - // suppress warnings that occur, when stream_select is interrupted by a signal - $ret = @\stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); + // suppress warnings that occur when `stream_select()` is interrupted by a signal + \set_error_handler(function ($errno, $errstr) { + $eintr = \defined('SOCKET_EINTR') ? \SOCKET_EINTR : 4; + return ($errno === \E_WARNING && \strpos($errstr, '[' . $eintr .']: ') !== false); + }); + + $ret = \stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); + + \restore_error_handler(); if ($except) { $write = \array_merge($write, $except); From 2c8533c82a18a206ec829d85497957ccd824c0ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 18 Feb 2022 15:38:25 +0100 Subject: [PATCH 2/2] Explicitly forward warnings to any registered error handlers --- src/StreamSelectLoop.php | 25 ++++++++--- tests/StreamSelectLoopTest.php | 79 ++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 14a2613f..9e78dc03 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -287,15 +287,28 @@ private function streamSelect(array &$read, array &$write, $timeout) } } - // suppress warnings that occur when `stream_select()` is interrupted by a signal - \set_error_handler(function ($errno, $errstr) { + /** @var ?callable $previous */ + $previous = \set_error_handler(function ($errno, $errstr) use (&$previous) { + // suppress warnings that occur when `stream_select()` is interrupted by a signal $eintr = \defined('SOCKET_EINTR') ? \SOCKET_EINTR : 4; - return ($errno === \E_WARNING && \strpos($errstr, '[' . $eintr .']: ') !== false); - }); + if ($errno === \E_WARNING && \strpos($errstr, '[' . $eintr .']: ') !== false) { + return; + } - $ret = \stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); + // forward any other error to registered error handler or print warning + return ($previous !== null) ? \call_user_func_array($previous, \func_get_args()) : false; + }); - \restore_error_handler(); + try { + $ret = \stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); + \restore_error_handler(); + } catch (\Throwable $e) { // @codeCoverageIgnoreStart + \restore_error_handler(); + throw $e; + } catch (\Exception $e) { + \restore_error_handler(); + throw $e; + } // @codeCoverageIgnoreEnd if ($except) { $write = \array_merge($write, $except); diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index d6800c5e..80197468 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -40,6 +40,85 @@ public function testStreamSelectTimeoutEmulation() $this->assertGreaterThan(0.04, $interval); } + public function testStreamSelectReportsWarningForStreamWithFilter() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('Not supported on legacy HHVM'); + } + + $stream = tmpfile(); + stream_filter_append($stream, 'string.rot13'); + + $this->loop->addReadStream($stream, $this->expectCallableNever()); + + $loop = $this->loop; + $this->loop->futureTick(function () use ($loop, $stream) { + $loop->futureTick(function () use ($loop, $stream) { + $loop->removeReadStream($stream); + }); + }); + + $error = null; + $previous = set_error_handler(function ($_, $errstr) use (&$error) { + $error = $errstr; + }); + + try { + $this->loop->run(); + } catch (\ValueError $e) { + // ignore ValueError for PHP 8+ due to empty stream array + } + + restore_error_handler(); + + $this->assertNotNull($error); + + $now = set_error_handler(function () { }); + restore_error_handler(); + $this->assertEquals($previous, $now); + } + + public function testStreamSelectThrowsWhenCustomErrorHandlerThrowsForStreamWithFilter() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('Not supported on legacy HHVM'); + } + + $stream = tmpfile(); + stream_filter_append($stream, 'string.rot13'); + + $this->loop->addReadStream($stream, $this->expectCallableNever()); + + $loop = $this->loop; + $this->loop->futureTick(function () use ($loop, $stream) { + $loop->futureTick(function () use ($loop, $stream) { + $loop->removeReadStream($stream); + }); + }); + + $previous = set_error_handler(function ($_, $errstr) { + throw new \RuntimeException($errstr); + }); + + $e = null; + try { + $this->loop->run(); + restore_error_handler(); + $this->fail(); + } catch (\RuntimeException $e) { + restore_error_handler(); + } catch (\ValueError $e) { + restore_error_handler(); // PHP 8+ + $e = $e->getPrevious(); + } + + $this->assertInstanceOf('RuntimeException', $e); + + $now = set_error_handler(function () { }); + restore_error_handler(); + $this->assertEquals($previous, $now); + } + public function signalProvider() { return array(