From 1041e3a9d180882e0af9731a6ba33cb602788d6e Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sat, 27 Feb 2016 18:18:08 +0100 Subject: [PATCH 1/5] added unit test for stream_select signal interruption adds a unit test for #20 --- tests/AbstractLoopTest.php | 3 + tests/StreamSelectLoopTest.php | 109 +++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index b4095379..17163e8b 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -4,6 +4,9 @@ abstract class AbstractLoopTest extends TestCase { + /** + * @var \React\EventLoop\LoopInterface + */ protected $loop; public function setUp() diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index 55d3d165..fdb088a8 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -2,10 +2,19 @@ namespace React\Tests\EventLoop; +use React\EventLoop\LoopInterface; use React\EventLoop\StreamSelectLoop; class StreamSelectLoopTest extends AbstractLoopTest { + protected function tearDown() + { + parent::tearDown(); + if (strncmp($this->getName(false), 'testSignal', 10) === 0) { + $this->resetSignalHandlers(); + } + } + public function createLoop() { return new StreamSelectLoop(); @@ -27,4 +36,104 @@ public function testStreamSelectTimeoutEmulation() $this->assertGreaterThan(0.04, $interval); } + + public function signalProvider() + { + return [ + ['SIGUSR1', SIGUSR1], + ['SIGHUP', SIGHUP], + ['SIGTERM', SIGTERM], + ]; + } + + private $_signalHandled = false; + + /** + * Test signal interrupt when no stream is attached to the loop + * @dataProvider signalProvider + */ + public function testSignalInterruptNoStream($sigName, $signal) + { + // dispatch signal handler once before signal is sent and once after + $this->loop->addTimer(0.01, function() { pcntl_signal_dispatch(); }); + $this->loop->addTimer(0.03, function() { pcntl_signal_dispatch(); }); + + $this->setUpSignalHandler($signal); + + // spawn external process to send signal to current process id + $this->forkSendSignal($signal); + $this->loop->run(); + $this->assertTrue($this->_signalHandled); + } + + /** + * Test signal interrupt when a stream is attached to the loop + * @dataProvider signalProvider + */ + public function testSignalInterruptWithStream($sigName, $signal) + { + // dispatch signal handler every 10ms + $this->loop->addPeriodicTimer(0.01, function() { pcntl_signal_dispatch(); }); + + // add stream to the loop + list($writeStream, $readStream) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + $this->loop->addReadStream($readStream, function($stream, $loop) { + /** @var $loop LoopInterface */ + $read = fgets($stream); + if ($read === "end loop\n") { + $loop->stop(); + } + }); + $this->loop->addTimer(0.05, function() use ($writeStream) { + fwrite($writeStream, "end loop\n"); + }); + + $this->setUpSignalHandler($signal); + + // spawn external process to send signal to current process id + $this->forkSendSignal($signal); + + // run loop with suppressed warnings + $oldReporting = error_reporting(E_ALL & ~E_WARNING); + $this->loop->run(); + error_reporting($oldReporting); + + $this->assertTrue($this->_signalHandled); + } + + /** + * add signal handler for signal + */ + protected function setUpSignalHandler($signal) + { + $this->_signalHandled = false; + $this->assertTrue(pcntl_signal($signal, function() { $this->_signalHandled = true; })); + } + + /** + * reset all signal handlers to default + */ + protected function resetSignalHandlers() + { + foreach($this->signalProvider() as $signal) { + pcntl_signal($signal[1], SIG_DFL); + } + } + + /** + * fork child process to send signal to current process id + */ + protected function forkSendSignal($signal) + { + $currentPid = posix_getpid(); + $childPid = pcntl_fork(); + if ($childPid == -1) { + $this->fail("Failed to fork child process!"); + } else if ($childPid === 0) { + // this is executed in the child process + usleep(20000); + posix_kill($currentPid, $signal); + die(); + } + } } From 0c922b95ab711d33f2d6b1d1ca781cd4cc582a8e Mon Sep 17 00:00:00 2001 From: Matthias Krauser Date: Tue, 16 Dec 2014 22:36:45 +0100 Subject: [PATCH 2/5] check return value of stream_select --- src/StreamSelectLoop.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 7d455048..395f7f8b 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -218,7 +218,12 @@ private function waitForStreamActivity($timeout) $read = $this->readStreams; $write = $this->writeStreams; - $this->streamSelect($read, $write, $timeout); + $available = $this->streamSelect($read, $write, $timeout); + if (false === $available) { + // if a system call has been interrupted, + // we cannot rely on it's outcome + return; + } foreach ($read as $stream) { $key = (int) $stream; From 5916176ad06d74a3a55e84b152e49e7e34a73fe6 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 1 Mar 2016 11:27:45 +0100 Subject: [PATCH 3/5] suppress stream_select() warning when interrupted by a signal - error suppression does not hurt much if handled correctly https://github.com/ezzatron/fig-standards/blob/error-handler/proposed/error-handler-meta.md#431-performance-considerations - it make this library work without having to suppress warnings for all the code! - StreamSelect loop is not the loop of choice if you really care about performance anyway. - The "performance overhead" of suppressing the warning will hardly have any effect as stream_select() is supposed to be waiting some amount of time anyway in most cases. In the cases where it returns immediately, the reading and writing of streams will have much bigger impact than the very small amount of time added by @ operator. --- src/StreamSelectLoop.php | 6 ++++-- tests/StreamSelectLoopTest.php | 3 --- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 395f7f8b..cbe841f6 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -250,14 +250,16 @@ private function waitForStreamActivity($timeout) * @param array &$write An array of write streams to select upon. * @param integer|null $timeout Activity timeout in microseconds, or null to wait forever. * - * @return integer The total number of streams that are ready for read/write. + * @return integer|false The total number of streams that are ready for read/write. + * Can return false if stream_select() is interrupted by a signal. */ protected function streamSelect(array &$read, array &$write, $timeout) { if ($read || $write) { $except = null; - return stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); + // suppress warnings that occur, when stream_select is interrupted by a signal + return @stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); } usleep($timeout); diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index fdb088a8..1cfe57d5 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -93,10 +93,7 @@ public function testSignalInterruptWithStream($sigName, $signal) // spawn external process to send signal to current process id $this->forkSendSignal($signal); - // run loop with suppressed warnings - $oldReporting = error_reporting(E_ALL & ~E_WARNING); $this->loop->run(); - error_reporting($oldReporting); $this->assertTrue($this->_signalHandled); } From ef2ced8328fedd6385bd883c50d71097816ea5e0 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 4 Mar 2016 00:26:50 +0100 Subject: [PATCH 4/5] added checks for pcntl extension to StreamSelectLoopTest --- tests/StreamSelectLoopTest.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index 1cfe57d5..e4c7615b 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -10,7 +10,7 @@ class StreamSelectLoopTest extends AbstractLoopTest protected function tearDown() { parent::tearDown(); - if (strncmp($this->getName(false), 'testSignal', 10) === 0) { + if (strncmp($this->getName(false), 'testSignal', 10) === 0 && extension_loaded('pcntl')) { $this->resetSignalHandlers(); } } @@ -54,6 +54,10 @@ public function signalProvider() */ public function testSignalInterruptNoStream($sigName, $signal) { + if (!extension_loaded('pcntl')) { + $this->markTestSkipped('"pcntl" extension is required to run this test.'); + } + // dispatch signal handler once before signal is sent and once after $this->loop->addTimer(0.01, function() { pcntl_signal_dispatch(); }); $this->loop->addTimer(0.03, function() { pcntl_signal_dispatch(); }); @@ -72,6 +76,10 @@ public function testSignalInterruptNoStream($sigName, $signal) */ public function testSignalInterruptWithStream($sigName, $signal) { + if (!extension_loaded('pcntl')) { + $this->markTestSkipped('"pcntl" extension is required to run this test.'); + } + // dispatch signal handler every 10ms $this->loop->addPeriodicTimer(0.01, function() { pcntl_signal_dispatch(); }); From dcbd9097eb16e88c56080095dd10ef08d5744b69 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 4 Mar 2016 00:48:18 +0100 Subject: [PATCH 5/5] fixed HHVM timing issue --- tests/StreamSelectLoopTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index e4c7615b..61b059c1 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -61,6 +61,10 @@ public function testSignalInterruptNoStream($sigName, $signal) // dispatch signal handler once before signal is sent and once after $this->loop->addTimer(0.01, function() { pcntl_signal_dispatch(); }); $this->loop->addTimer(0.03, function() { pcntl_signal_dispatch(); }); + if (defined('HHVM_VERSION')) { + // hhvm startup is slow so we need to add another handler much later + $this->loop->addTimer(0.5, function() { pcntl_signal_dispatch(); }); + } $this->setUpSignalHandler($signal); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy