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),
+ );
+ }
+
+}