diff --git a/doc/03-utilities.md b/doc/03-utilities.md index 513d08aa..fd3fd0e7 100644 --- a/doc/03-utilities.md +++ b/doc/03-utilities.md @@ -4,8 +4,9 @@ can then statically access from anywhere. It is not really a best practice but can help in some older codebases or for ease of use. - _ErrorHandler_: The `Monolog\ErrorHandler` class allows you to easily register - a Logger instance as an exception handler, error handler, fatal error handler or - signal handler. + a Logger instance as an exception handler, error handler or fatal error handler. +- _SignalHandler_: The `Monolog\SignalHandler` class allows you to easily register + a Logger instance as a POSIX signal handler. - _ErrorLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain log level is reached. - _ChannelLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain diff --git a/src/Monolog/ErrorHandler.php b/src/Monolog/ErrorHandler.php index b96c58ae..b3025738 100644 --- a/src/Monolog/ErrorHandler.php +++ b/src/Monolog/ErrorHandler.php @@ -14,12 +14,11 @@ namespace Monolog; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use Monolog\Handler\AbstractHandler; -use ReflectionExtension; /** * Monolog error handler * - * A facility to enable logging of runtime errors, exceptions, fatal errors and signals. + * A facility to enable logging of runtime errors, exceptions and fatal errors. * * Quick setup: ErrorHandler::register($logger); * @@ -42,10 +41,6 @@ class ErrorHandler private $lastFatalTrace; private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR); - private $previousSignalHandler = array(); - private $signalLevelMap = array(); - private $signalRestartSyscalls = array(); - public function __construct(LoggerInterface $logger) { $this->logger = $logger; @@ -110,37 +105,6 @@ class ErrorHandler $this->hasFatalErrorHandler = true; } - public function registerSignalHandler($signo, $level = LogLevel::CRITICAL, $callPrevious = true, $restartSyscalls = true, $async = true) - { - if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) { - return $this; - } - - if ($callPrevious) { - if (function_exists('pcntl_signal_get_handler')) { - $handler = pcntl_signal_get_handler($signo); - if ($handler === false) { - return $this; - } - $this->previousSignalHandler[$signo] = $handler; - } else { - $this->previousSignalHandler[$signo] = true; - } - } else { - unset($this->previousSignalHandler[$signo]); - } - $this->signalLevelMap[$signo] = $level; - $this->signalRestartSyscalls[$signo] = $restartSyscalls; - - if (function_exists('pcntl_async_signals') && $async !== null) { - pcntl_async_signals($async); - } - - pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls); - - return $this; - } - protected function defaultErrorLevelMap() { return array( @@ -234,55 +198,6 @@ class ErrorHandler } } - public function handleSignal($signo, array $siginfo = null) - { - static $signals = array(); - - if (!$signals && extension_loaded('pcntl')) { - $pcntl = new ReflectionExtension('pcntl'); - $constants = $pcntl->getConstants(); - if (!$constants) { - // HHVM 3.24.2 returns an empty array. - $constants = get_defined_constants(true); - $constants = $constants['Core']; - } - foreach ($constants as $name => $value) { - if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) { - $signals[$value] = $name; - } - } - unset($constants); - } - - $level = isset($this->signalLevelMap[$signo]) ? $this->signalLevelMap[$signo] : LogLevel::CRITICAL; - $signal = isset($signals[$signo]) ? $signals[$signo] : $signo; - $context = isset($siginfo) ? $siginfo : array(); - $this->logger->log($level, sprintf('Program received signal %s', $signal), $context); - - if (!isset($this->previousSignalHandler[$signo])) { - return; - } - - if ($this->previousSignalHandler[$signo] === true || $this->previousSignalHandler[$signo] === SIG_DFL) { - if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch') - && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill')) { - $restartSyscalls = isset($this->restartSyscalls[$signo]) ? $this->restartSyscalls[$signo] : true; - pcntl_signal($signo, SIG_DFL, $restartSyscalls); - pcntl_sigprocmask(SIG_UNBLOCK, array($signo), $oldset); - posix_kill(posix_getpid(), $signo); - pcntl_signal_dispatch(); - pcntl_sigprocmask(SIG_SETMASK, $oldset); - pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls); - } - } elseif (is_callable($this->previousSignalHandler[$signo])) { - if (PHP_VERSION >= 71000) { - $this->previousSignalHandler[$signo]($signo, $siginfo); - } else { - $this->previousSignalHandler[$signo]($signo); - } - } - } - private static function codeToString($code) { switch ($code) { diff --git a/src/Monolog/SignalHandler.php b/src/Monolog/SignalHandler.php new file mode 100644 index 00000000..d5907805 --- /dev/null +++ b/src/Monolog/SignalHandler.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use ReflectionExtension; + +/** + * Monolog POSIX signal handler + * + * @author Robert Gust-Bardon + */ +class SignalHandler +{ + private $logger; + + private $previousSignalHandler = array(); + private $signalLevelMap = array(); + private $signalRestartSyscalls = array(); + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + public function registerSignalHandler($signo, $level = LogLevel::CRITICAL, $callPrevious = true, $restartSyscalls = true, $async = true) + { + if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) { + return $this; + } + + if ($callPrevious) { + if (function_exists('pcntl_signal_get_handler')) { + $handler = pcntl_signal_get_handler($signo); + if ($handler === false) { + return $this; + } + $this->previousSignalHandler[$signo] = $handler; + } else { + $this->previousSignalHandler[$signo] = true; + } + } else { + unset($this->previousSignalHandler[$signo]); + } + $this->signalLevelMap[$signo] = $level; + $this->signalRestartSyscalls[$signo] = $restartSyscalls; + + if (function_exists('pcntl_async_signals') && $async !== null) { + pcntl_async_signals($async); + } + + pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls); + + return $this; + } + + public function handleSignal($signo, array $siginfo = null) + { + static $signals = array(); + + if (!$signals && extension_loaded('pcntl')) { + $pcntl = new ReflectionExtension('pcntl'); + $constants = $pcntl->getConstants(); + if (!$constants) { + // HHVM 3.24.2 returns an empty array. + $constants = get_defined_constants(true); + $constants = $constants['Core']; + } + foreach ($constants as $name => $value) { + if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) { + $signals[$value] = $name; + } + } + unset($constants); + } + + $level = isset($this->signalLevelMap[$signo]) ? $this->signalLevelMap[$signo] : LogLevel::CRITICAL; + $signal = isset($signals[$signo]) ? $signals[$signo] : $signo; + $context = isset($siginfo) ? $siginfo : array(); + $this->logger->log($level, sprintf('Program received signal %s', $signal), $context); + + if (!isset($this->previousSignalHandler[$signo])) { + return; + } + + if ($this->previousSignalHandler[$signo] === true || $this->previousSignalHandler[$signo] === SIG_DFL) { + if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch') + && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill')) { + $restartSyscalls = isset($this->restartSyscalls[$signo]) ? $this->restartSyscalls[$signo] : true; + pcntl_signal($signo, SIG_DFL, $restartSyscalls); + pcntl_sigprocmask(SIG_UNBLOCK, array($signo), $oldset); + posix_kill(posix_getpid(), $signo); + pcntl_signal_dispatch(); + pcntl_sigprocmask(SIG_SETMASK, $oldset); + pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls); + } + } elseif (is_callable($this->previousSignalHandler[$signo])) { + if (PHP_VERSION_ID >= 70100) { + $this->previousSignalHandler[$signo]($signo, $siginfo); + } else { + $this->previousSignalHandler[$signo]($signo); + } + } + } +} diff --git a/tests/Monolog/ErrorHandlerTest.php b/tests/Monolog/ErrorHandlerTest.php index 00300806..a9a3f301 100644 --- a/tests/Monolog/ErrorHandlerTest.php +++ b/tests/Monolog/ErrorHandlerTest.php @@ -11,55 +11,10 @@ namespace Monolog; -use Monolog\Handler\StreamHandler; use Monolog\Handler\TestHandler; -use Psr\Log\LogLevel; -class ErrorHandlerTest extends TestCase +class ErrorHandlerTest extends \PHPUnit_Framework_TestCase { - - private $asyncSignalHandling; - private $blockedSignals; - private $signalHandlers; - - protected function setUp() - { - $this->signalHandlers = array(); - if (extension_loaded('pcntl')) { - if (function_exists('pcntl_async_signals')) { - $this->asyncSignalHandling = pcntl_async_signals(); - } - if (function_exists('pcntl_sigprocmask')) { - pcntl_sigprocmask(SIG_BLOCK, array(), $this->blockedSignals); - } - } - } - - protected function tearDown() - { - if ($this->asyncSignalHandling !== null) { - pcntl_async_signals($this->asyncSignalHandling); - } - if ($this->blockedSignals !== null) { - pcntl_sigprocmask(SIG_SETMASK, $this->blockedSignals); - } - if ($this->signalHandlers) { - pcntl_signal_dispatch(); - foreach ($this->signalHandlers as $signo => $handler) { - pcntl_signal($signo, $handler); - } - } - } - - private function setSignalHandler($signo, $handler = SIG_DFL) { - if (function_exists('pcntl_signal_get_handler')) { - $this->signalHandlers[$signo] = pcntl_signal_get_handler($signo); - } else { - $this->signalHandlers[$signo] = SIG_DFL; - } - $this->assertTrue(pcntl_signal($signo, $handler)); - } - public function testHandleError() { $logger = new Logger('test', array($handler = new TestHandler)); @@ -73,225 +28,4 @@ class ErrorHandlerTest extends TestCase $this->assertCount(2, $handler->getRecords()); $this->assertTrue($handler->hasEmergencyRecords()); } - - public function testHandleSignal() - { - $logger = new Logger('test', array($handler = new TestHandler)); - $errHandler = new ErrorHandler($logger); - $signo = 2; // SIGINT. - $siginfo = array('signo' => $signo, 'errno' => 0, 'code' => 0); - $errHandler->handleSignal($signo, $siginfo); - $this->assertCount(1, $handler->getRecords()); - $this->assertTrue($handler->hasCriticalRecords()); - $records = $handler->getRecords(); - $this->assertSame($siginfo, $records[0]['context']); - } - - /** - * @depends testHandleSignal - * @requires extension pcntl - * @requires extension posix - * @requires function pcntl_signal - * @requires function pcntl_signal_dispatch - * @requires function posix_getpid - * @requires function posix_kill - */ - public function testRegisterSignalHandler() - { - // SIGCONT and SIGURG should be ignored by default. - if (!defined('SIGCONT') || !defined('SIGURG')) { - $this->markTestSkipped('This test requires the SIGCONT and SIGURG pcntl constants.'); - } - - $this->setSignalHandler(SIGCONT, SIG_IGN); - $this->setSignalHandler(SIGURG, SIG_IGN); - - $logger = new Logger('test', array($handler = new TestHandler)); - $errHandler = new ErrorHandler($logger); - $pid = posix_getpid(); - - $this->assertTrue(posix_kill($pid, SIGURG)); - $this->assertTrue(pcntl_signal_dispatch()); - $this->assertCount(0, $handler->getRecords()); - - $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, false); - - $this->assertTrue(posix_kill($pid, SIGCONT)); - $this->assertTrue(pcntl_signal_dispatch()); - $this->assertCount(0, $handler->getRecords()); - - $this->assertTrue(posix_kill($pid, SIGURG)); - $this->assertTrue(pcntl_signal_dispatch()); - $this->assertCount(1, $handler->getRecords()); - $this->assertTrue($handler->hasInfoThatContains('SIGURG')); - } - - /** - * @dataProvider defaultPreviousProvider - * @depends testRegisterSignalHandler - * @requires function pcntl_fork - * @requires function pcntl_sigprocmask - * @requires function pcntl_waitpid - */ - public function testRegisterDefaultPreviousSignalHandler($signo, $callPrevious, $expected) - { - $this->setSignalHandler($signo, SIG_DFL); - - $path = tempnam(sys_get_temp_dir(), 'monolog-'); - $this->assertNotFalse($path); - - $pid = pcntl_fork(); - if ($pid === 0) { // Child. - $streamHandler = new StreamHandler($path); - $streamHandler->setFormatter($this->getIdentityFormatter()); - $logger = new Logger('test', array($streamHandler)); - $errHandler = new ErrorHandler($logger); - $errHandler->registerSignalHandler($signo, LogLevel::INFO, $callPrevious, false, false); - pcntl_sigprocmask(SIG_SETMASK, array(SIGCONT)); - posix_kill(posix_getpid(), $signo); - pcntl_signal_dispatch(); - // If $callPrevious is true, SIGINT should terminate by this line. - pcntl_sigprocmask(SIG_BLOCK, array(), $oldset); - file_put_contents($path, implode(' ', $oldset), FILE_APPEND); - posix_kill(posix_getpid(), $signo); - pcntl_signal_dispatch(); - exit(); - } - - $this->assertNotSame(-1, $pid); - $this->assertNotSame(-1, pcntl_waitpid($pid, $status)); - $this->assertNotSame(-1, $status); - $this->assertSame($expected, file_get_contents($path)); - } - - public function defaultPreviousProvider() - { - if (!defined('SIGCONT') || !defined('SIGINT') || !defined('SIGURG')) { - return array(); - } - - return array( - array(SIGINT, false, 'Program received signal SIGINT'.SIGCONT.'Program received signal SIGINT'), - array(SIGINT, true, 'Program received signal SIGINT'), - array(SIGURG, false, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'), - array(SIGURG, true, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'), - ); - } - - /** - * @dataProvider callablePreviousProvider - * @depends testRegisterSignalHandler - * @requires function pcntl_signal_get_handler - */ - public function testRegisterCallablePreviousSignalHandler($callPrevious) - { - $this->setSignalHandler(SIGURG, SIG_IGN); - - $logger = new Logger('test', array($handler = new TestHandler)); - $errHandler = new ErrorHandler($logger); - $previousCalled = 0; - pcntl_signal(SIGURG, function ($signo, array $siginfo = null) use (&$previousCalled) { - ++$previousCalled; - }); - $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, $callPrevious, false, false); - $this->assertTrue(posix_kill(posix_getpid(), SIGURG)); - $this->assertTrue(pcntl_signal_dispatch()); - $this->assertCount(1, $handler->getRecords()); - $this->assertTrue($handler->hasInfoThatContains('SIGURG')); - $this->assertSame($callPrevious ? 1 : 0, $previousCalled); - } - - public function callablePreviousProvider() - { - return array( - array(false), - array(true), - ); - } - - /** - * @dataProvider restartSyscallsProvider - * @depends testRegisterDefaultPreviousSignalHandler - * @requires function pcntl_fork - * @requires function pcntl_waitpid - */ - public function testRegisterSyscallRestartingSignalHandler($restartSyscalls) - { - $this->setSignalHandler(SIGURG, SIG_IGN); - - $parentPid = posix_getpid(); - $microtime = microtime(true); - - $pid = pcntl_fork(); - if ($pid === 0) { // Child. - usleep(100000); - posix_kill($parentPid, SIGURG); - usleep(100000); - exit(); - } - - $this->assertNotSame(-1, $pid); - $logger = new Logger('test', array($handler = new TestHandler)); - $errHandler = new ErrorHandler($logger); - $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, $restartSyscalls, false); - if ($restartSyscalls) { - // pcntl_wait is expected to be restarted after the signal handler. - $this->assertNotSame(-1, pcntl_waitpid($pid, $status)); - } else { - // pcntl_wait is expected to be interrupted when the signal handler is invoked. - $this->assertSame(-1, pcntl_waitpid($pid, $status)); - } - $this->assertSame($restartSyscalls, microtime(true) - $microtime > 0.15); - $this->assertTrue(pcntl_signal_dispatch()); - $this->assertCount(1, $handler->getRecords()); - if ($restartSyscalls) { - // The child has already exited. - $this->assertSame(-1, pcntl_waitpid($pid, $status)); - } else { - // The child has not exited yet. - $this->assertNotSame(-1, pcntl_waitpid($pid, $status)); - } - } - - public function restartSyscallsProvider() - { - return array( - array(false), - array(true), - array(false), - array(true), - ); - } - - /** - * @dataProvider asyncProvider - * @depends testRegisterDefaultPreviousSignalHandler - * @requires function pcntl_async_signals - */ - public function testRegisterAsyncSignalHandler($initialAsync, $desiredAsync, $expectedBefore, $expectedAfter) - { - $this->setSignalHandler(SIGURG, SIG_IGN); - pcntl_async_signals($initialAsync); - - $logger = new Logger('test', array($handler = new TestHandler)); - $errHandler = new ErrorHandler($logger); - $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, $desiredAsync); - $this->assertTrue(posix_kill(posix_getpid(), SIGURG)); - $this->assertCount($expectedBefore, $handler->getRecords()); - $this->assertTrue(pcntl_signal_dispatch()); - $this->assertCount($expectedAfter, $handler->getRecords()); - } - - public function asyncProvider() - { - return array( - array(false, false, 0, 1), - array(false, null, 0, 1), - array(false, true, 1, 1), - array(true, false, 0, 1), - array(true, null, 1, 1), - array(true, true, 1, 1), - ); - } - } diff --git a/tests/Monolog/SignalHandlerTest.php b/tests/Monolog/SignalHandlerTest.php new file mode 100644 index 00000000..9fa07929 --- /dev/null +++ b/tests/Monolog/SignalHandlerTest.php @@ -0,0 +1,287 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\StreamHandler; +use Monolog\Handler\TestHandler; +use Psr\Log\LogLevel; + +/** + * @author Robert Gust-Bardon + * @covers Monolog\SignalHandler + */ +class SignalHandlerTest extends TestCase +{ + + private $asyncSignalHandling; + private $blockedSignals; + private $signalHandlers; + + protected function setUp() + { + $this->signalHandlers = array(); + if (extension_loaded('pcntl')) { + if (function_exists('pcntl_async_signals')) { + $this->asyncSignalHandling = pcntl_async_signals(); + } + if (function_exists('pcntl_sigprocmask')) { + pcntl_sigprocmask(SIG_BLOCK, array(), $this->blockedSignals); + } + } + } + + protected function tearDown() + { + if ($this->asyncSignalHandling !== null) { + pcntl_async_signals($this->asyncSignalHandling); + } + if ($this->blockedSignals !== null) { + pcntl_sigprocmask(SIG_SETMASK, $this->blockedSignals); + } + if ($this->signalHandlers) { + pcntl_signal_dispatch(); + foreach ($this->signalHandlers as $signo => $handler) { + pcntl_signal($signo, $handler); + } + } + } + + private function setSignalHandler($signo, $handler = SIG_DFL) { + if (function_exists('pcntl_signal_get_handler')) { + $this->signalHandlers[$signo] = pcntl_signal_get_handler($signo); + } else { + $this->signalHandlers[$signo] = SIG_DFL; + } + $this->assertTrue(pcntl_signal($signo, $handler)); + } + + public function testHandleSignal() + { + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new SignalHandler($logger); + $signo = 2; // SIGINT. + $siginfo = array('signo' => $signo, 'errno' => 0, 'code' => 0); + $errHandler->handleSignal($signo, $siginfo); + $this->assertCount(1, $handler->getRecords()); + $this->assertTrue($handler->hasCriticalRecords()); + $records = $handler->getRecords(); + $this->assertSame($siginfo, $records[0]['context']); + } + + /** + * @depends testHandleSignal + * @requires extension pcntl + * @requires extension posix + * @requires function pcntl_signal + * @requires function pcntl_signal_dispatch + * @requires function posix_getpid + * @requires function posix_kill + */ + public function testRegisterSignalHandler() + { + // SIGCONT and SIGURG should be ignored by default. + if (!defined('SIGCONT') || !defined('SIGURG')) { + $this->markTestSkipped('This test requires the SIGCONT and SIGURG pcntl constants.'); + } + + $this->setSignalHandler(SIGCONT, SIG_IGN); + $this->setSignalHandler(SIGURG, SIG_IGN); + + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new SignalHandler($logger); + $pid = posix_getpid(); + + $this->assertTrue(posix_kill($pid, SIGURG)); + $this->assertTrue(pcntl_signal_dispatch()); + $this->assertCount(0, $handler->getRecords()); + + $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, false); + + $this->assertTrue(posix_kill($pid, SIGCONT)); + $this->assertTrue(pcntl_signal_dispatch()); + $this->assertCount(0, $handler->getRecords()); + + $this->assertTrue(posix_kill($pid, SIGURG)); + $this->assertTrue(pcntl_signal_dispatch()); + $this->assertCount(1, $handler->getRecords()); + $this->assertTrue($handler->hasInfoThatContains('SIGURG')); + } + + /** + * @dataProvider defaultPreviousProvider + * @depends testRegisterSignalHandler + * @requires function pcntl_fork + * @requires function pcntl_sigprocmask + * @requires function pcntl_waitpid + */ + public function testRegisterDefaultPreviousSignalHandler($signo, $callPrevious, $expected) + { + $this->setSignalHandler($signo, SIG_DFL); + + $path = tempnam(sys_get_temp_dir(), 'monolog-'); + $this->assertNotFalse($path); + + $pid = pcntl_fork(); + if ($pid === 0) { // Child. + $streamHandler = new StreamHandler($path); + $streamHandler->setFormatter($this->getIdentityFormatter()); + $logger = new Logger('test', array($streamHandler)); + $errHandler = new SignalHandler($logger); + $errHandler->registerSignalHandler($signo, LogLevel::INFO, $callPrevious, false, false); + pcntl_sigprocmask(SIG_SETMASK, array(SIGCONT)); + posix_kill(posix_getpid(), $signo); + pcntl_signal_dispatch(); + // If $callPrevious is true, SIGINT should terminate by this line. + pcntl_sigprocmask(SIG_BLOCK, array(), $oldset); + file_put_contents($path, implode(' ', $oldset), FILE_APPEND); + posix_kill(posix_getpid(), $signo); + pcntl_signal_dispatch(); + exit(); + } + + $this->assertNotSame(-1, $pid); + $this->assertNotSame(-1, pcntl_waitpid($pid, $status)); + $this->assertNotSame(-1, $status); + $this->assertSame($expected, file_get_contents($path)); + } + + public function defaultPreviousProvider() + { + if (!defined('SIGCONT') || !defined('SIGINT') || !defined('SIGURG')) { + return array(); + } + + return array( + array(SIGINT, false, 'Program received signal SIGINT'.SIGCONT.'Program received signal SIGINT'), + array(SIGINT, true, 'Program received signal SIGINT'), + array(SIGURG, false, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'), + array(SIGURG, true, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'), + ); + } + + /** + * @dataProvider callablePreviousProvider + * @depends testRegisterSignalHandler + * @requires function pcntl_signal_get_handler + */ + public function testRegisterCallablePreviousSignalHandler($callPrevious) + { + $this->setSignalHandler(SIGURG, SIG_IGN); + + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new SignalHandler($logger); + $previousCalled = 0; + pcntl_signal(SIGURG, function ($signo, array $siginfo = null) use (&$previousCalled) { + ++$previousCalled; + }); + $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, $callPrevious, false, false); + $this->assertTrue(posix_kill(posix_getpid(), SIGURG)); + $this->assertTrue(pcntl_signal_dispatch()); + $this->assertCount(1, $handler->getRecords()); + $this->assertTrue($handler->hasInfoThatContains('SIGURG')); + $this->assertSame($callPrevious ? 1 : 0, $previousCalled); + } + + public function callablePreviousProvider() + { + return array( + array(false), + array(true), + ); + } + + /** + * @dataProvider restartSyscallsProvider + * @depends testRegisterDefaultPreviousSignalHandler + * @requires function pcntl_fork + * @requires function pcntl_waitpid + */ + public function testRegisterSyscallRestartingSignalHandler($restartSyscalls) + { + $this->setSignalHandler(SIGURG, SIG_IGN); + + $parentPid = posix_getpid(); + $microtime = microtime(true); + + $pid = pcntl_fork(); + if ($pid === 0) { // Child. + usleep(100000); + posix_kill($parentPid, SIGURG); + usleep(100000); + exit(); + } + + $this->assertNotSame(-1, $pid); + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new SignalHandler($logger); + $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, $restartSyscalls, false); + if ($restartSyscalls) { + // pcntl_wait is expected to be restarted after the signal handler. + $this->assertNotSame(-1, pcntl_waitpid($pid, $status)); + } else { + // pcntl_wait is expected to be interrupted when the signal handler is invoked. + $this->assertSame(-1, pcntl_waitpid($pid, $status)); + } + $this->assertSame($restartSyscalls, microtime(true) - $microtime > 0.15); + $this->assertTrue(pcntl_signal_dispatch()); + $this->assertCount(1, $handler->getRecords()); + if ($restartSyscalls) { + // The child has already exited. + $this->assertSame(-1, pcntl_waitpid($pid, $status)); + } else { + // The child has not exited yet. + $this->assertNotSame(-1, pcntl_waitpid($pid, $status)); + } + } + + public function restartSyscallsProvider() + { + return array( + array(false), + array(true), + array(false), + array(true), + ); + } + + /** + * @dataProvider asyncProvider + * @depends testRegisterDefaultPreviousSignalHandler + * @requires function pcntl_async_signals + */ + public function testRegisterAsyncSignalHandler($initialAsync, $desiredAsync, $expectedBefore, $expectedAfter) + { + $this->setSignalHandler(SIGURG, SIG_IGN); + pcntl_async_signals($initialAsync); + + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new SignalHandler($logger); + $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, $desiredAsync); + $this->assertTrue(posix_kill(posix_getpid(), SIGURG)); + $this->assertCount($expectedBefore, $handler->getRecords()); + $this->assertTrue(pcntl_signal_dispatch()); + $this->assertCount($expectedAfter, $handler->getRecords()); + } + + public function asyncProvider() + { + return array( + array(false, false, 0, 1), + array(false, null, 0, 1), + array(false, true, 1, 1), + array(true, false, 0, 1), + array(true, null, 1, 1), + array(true, true, 1, 1), + ); + } + +}