diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 48657f96..7160c908 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -294,7 +294,7 @@ private function unsubscribeStreamEvent($stream, $flag) */ private function createTimerCallback() { - $this->timerCallback = function ($_, $_, $timer) { + $this->timerCallback = function ($_, $__, $timer) { call_user_func($timer->getCallback(), $timer); if (!$timer->isPeriodic() && $this->isTimerActive($timer)) { diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 17163e8b..d8457617 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -2,6 +2,8 @@ namespace React\Tests\EventLoop; +use React\EventLoop\LoopInterface; + abstract class AbstractLoopTest extends TestCase { /** @@ -11,9 +13,18 @@ abstract class AbstractLoopTest extends TestCase public function setUp() { + parent::setUp(); $this->loop = $this->createLoop(); } + protected function tearDown() + { + parent::tearDown(); + if (strncmp($this->getName(false), 'testSignal', 10) === 0 && extension_loaded('pcntl')) { + $this->resetSignalHandlers(); + } + } + abstract public function createLoop(); public function createStream() @@ -495,4 +506,113 @@ private function assertRunFasterThan($maxInterval) $this->assertLessThan($maxInterval, $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) + { + 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(); }); + 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); + + // 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) + { + 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(); }); + + // 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); + + $this->loop->run(); + + $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(); + } + } } diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index 61b059c1..55d3d165 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -2,19 +2,10 @@ 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 && extension_loaded('pcntl')) { - $this->resetSignalHandlers(); - } - } - public function createLoop() { return new StreamSelectLoop(); @@ -36,113 +27,4 @@ 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) - { - 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(); }); - 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); - - // 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) - { - 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(); }); - - // 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); - - $this->loop->run(); - - $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(); - } - } }