SignalHandlerTest.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of the Monolog package.
  4. *
  5. * (c) Jordi Boggiano <j.boggiano@seld.be>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Monolog;
  11. use Monolog\Handler\StreamHandler;
  12. use Monolog\Handler\TestHandler;
  13. use Psr\Log\LogLevel;
  14. use Monolog\Test\TestCase;
  15. /**
  16. * @author Robert Gust-Bardon <robert@gust-bardon.org>
  17. * @covers Monolog\SignalHandler
  18. */
  19. class SignalHandlerTest extends TestCase
  20. {
  21. private $asyncSignalHandling;
  22. private $blockedSignals;
  23. private $signalHandlers;
  24. protected function setUp(): void
  25. {
  26. $this->signalHandlers = array();
  27. if (extension_loaded('pcntl')) {
  28. if (function_exists('pcntl_async_signals')) {
  29. $this->asyncSignalHandling = pcntl_async_signals();
  30. }
  31. if (function_exists('pcntl_sigprocmask')) {
  32. pcntl_sigprocmask(SIG_SETMASK, array(), $this->blockedSignals);
  33. }
  34. }
  35. }
  36. public function tearDown(): void
  37. {
  38. parent::tearDown();
  39. if ($this->asyncSignalHandling !== null) {
  40. pcntl_async_signals($this->asyncSignalHandling);
  41. }
  42. if ($this->blockedSignals !== null) {
  43. pcntl_sigprocmask(SIG_SETMASK, $this->blockedSignals);
  44. }
  45. if ($this->signalHandlers) {
  46. pcntl_signal_dispatch();
  47. foreach ($this->signalHandlers as $signo => $handler) {
  48. pcntl_signal($signo, $handler);
  49. }
  50. }
  51. unset($this->signalHandlers, $this->blockedSignals, $this->asyncSignalHandling);
  52. }
  53. private function setSignalHandler($signo, $handler = SIG_DFL)
  54. {
  55. if (function_exists('pcntl_signal_get_handler')) {
  56. $this->signalHandlers[$signo] = pcntl_signal_get_handler($signo);
  57. } else {
  58. $this->signalHandlers[$signo] = SIG_DFL;
  59. }
  60. $this->assertTrue(pcntl_signal($signo, $handler));
  61. }
  62. public function testHandleSignal()
  63. {
  64. $logger = new Logger('test', array($handler = new TestHandler));
  65. $errHandler = new SignalHandler($logger);
  66. $signo = 2; // SIGINT.
  67. $siginfo = array('signo' => $signo, 'errno' => 0, 'code' => 0);
  68. $errHandler->handleSignal($signo, $siginfo);
  69. $this->assertCount(1, $handler->getRecords());
  70. $this->assertTrue($handler->hasCriticalRecords());
  71. $records = $handler->getRecords();
  72. $this->assertSame($siginfo, $records[0]['context']);
  73. }
  74. /**
  75. * @depends testHandleSignal
  76. * @requires extension pcntl
  77. * @requires extension posix
  78. * @requires function pcntl_signal
  79. * @requires function pcntl_signal_dispatch
  80. * @requires function posix_getpid
  81. * @requires function posix_kill
  82. */
  83. public function testRegisterSignalHandler()
  84. {
  85. // SIGCONT and SIGURG should be ignored by default.
  86. if (!defined('SIGCONT') || !defined('SIGURG')) {
  87. $this->markTestSkipped('This test requires the SIGCONT and SIGURG pcntl constants.');
  88. }
  89. $this->setSignalHandler(SIGCONT, SIG_IGN);
  90. $this->setSignalHandler(SIGURG, SIG_IGN);
  91. $logger = new Logger('test', array($handler = new TestHandler));
  92. $errHandler = new SignalHandler($logger);
  93. $pid = posix_getpid();
  94. $this->assertTrue(posix_kill($pid, SIGURG));
  95. $this->assertTrue(pcntl_signal_dispatch());
  96. $this->assertCount(0, $handler->getRecords());
  97. $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, false);
  98. $this->assertTrue(posix_kill($pid, SIGCONT));
  99. $this->assertTrue(pcntl_signal_dispatch());
  100. $this->assertCount(0, $handler->getRecords());
  101. $this->assertTrue(posix_kill($pid, SIGURG));
  102. $this->assertTrue(pcntl_signal_dispatch());
  103. $this->assertCount(1, $handler->getRecords());
  104. $this->assertTrue($handler->hasInfoThatContains('SIGURG'));
  105. }
  106. /**
  107. * @dataProvider defaultPreviousProvider
  108. * @depends testRegisterSignalHandler
  109. * @requires function pcntl_fork
  110. * @requires function pcntl_sigprocmask
  111. * @requires function pcntl_waitpid
  112. */
  113. public function testRegisterDefaultPreviousSignalHandler($signo, $callPrevious, $expected)
  114. {
  115. $this->setSignalHandler($signo, SIG_DFL);
  116. $path = tempnam(sys_get_temp_dir(), 'monolog-');
  117. $this->assertNotFalse($path);
  118. $pid = pcntl_fork();
  119. if ($pid === 0) { // Child.
  120. $streamHandler = new StreamHandler($path);
  121. $streamHandler->setFormatter($this->getIdentityFormatter());
  122. $logger = new Logger('test', array($streamHandler));
  123. $errHandler = new SignalHandler($logger);
  124. $errHandler->registerSignalHandler($signo, LogLevel::INFO, $callPrevious, false, false);
  125. pcntl_sigprocmask(SIG_SETMASK, array(SIGCONT));
  126. posix_kill(posix_getpid(), $signo);
  127. pcntl_signal_dispatch();
  128. // If $callPrevious is true, SIGINT should terminate by this line.
  129. pcntl_sigprocmask(SIG_SETMASK, array(), $oldset);
  130. file_put_contents($path, implode(' ', $oldset), FILE_APPEND);
  131. posix_kill(posix_getpid(), $signo);
  132. pcntl_signal_dispatch();
  133. exit();
  134. }
  135. $this->assertNotSame(-1, $pid);
  136. $this->assertNotSame(-1, pcntl_waitpid($pid, $status));
  137. $this->assertNotSame(-1, $status);
  138. $this->assertSame($expected, file_get_contents($path));
  139. }
  140. public function defaultPreviousProvider()
  141. {
  142. if (!defined('SIGCONT') || !defined('SIGINT') || !defined('SIGURG')) {
  143. return array();
  144. }
  145. return array(
  146. array(SIGINT, false, 'Program received signal SIGINT'.SIGCONT.'Program received signal SIGINT'),
  147. array(SIGINT, true, 'Program received signal SIGINT'),
  148. array(SIGURG, false, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'),
  149. array(SIGURG, true, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'),
  150. );
  151. }
  152. /**
  153. * @dataProvider callablePreviousProvider
  154. * @depends testRegisterSignalHandler
  155. * @requires function pcntl_signal_get_handler
  156. */
  157. public function testRegisterCallablePreviousSignalHandler($callPrevious)
  158. {
  159. $this->setSignalHandler(SIGURG, SIG_IGN);
  160. $logger = new Logger('test', array($handler = new TestHandler));
  161. $errHandler = new SignalHandler($logger);
  162. $previousCalled = 0;
  163. pcntl_signal(SIGURG, function ($signo, ?array $siginfo = null) use (&$previousCalled) {
  164. ++$previousCalled;
  165. });
  166. $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, $callPrevious, false, false);
  167. $this->assertTrue(posix_kill(posix_getpid(), SIGURG));
  168. $this->assertTrue(pcntl_signal_dispatch());
  169. $this->assertCount(1, $handler->getRecords());
  170. $this->assertTrue($handler->hasInfoThatContains('SIGURG'));
  171. $this->assertSame($callPrevious ? 1 : 0, $previousCalled);
  172. }
  173. public function callablePreviousProvider()
  174. {
  175. return array(
  176. array(false),
  177. array(true),
  178. );
  179. }
  180. /**
  181. * @dataProvider restartSyscallsProvider
  182. * @depends testRegisterDefaultPreviousSignalHandler
  183. * @requires function pcntl_fork
  184. * @requires function pcntl_waitpid
  185. */
  186. public function testRegisterSyscallRestartingSignalHandler($restartSyscalls)
  187. {
  188. $this->setSignalHandler(SIGURG, SIG_IGN);
  189. $parentPid = posix_getpid();
  190. $microtime = microtime(true);
  191. $pid = pcntl_fork();
  192. if ($pid === 0) { // Child.
  193. usleep(100000);
  194. posix_kill($parentPid, SIGURG);
  195. usleep(100000);
  196. exit();
  197. }
  198. $this->assertNotSame(-1, $pid);
  199. $logger = new Logger('test', array($handler = new TestHandler));
  200. $errHandler = new SignalHandler($logger);
  201. $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, $restartSyscalls, false);
  202. if ($restartSyscalls) {
  203. // pcntl_wait is expected to be restarted after the signal handler.
  204. $this->assertNotSame(-1, pcntl_waitpid($pid, $status));
  205. } else {
  206. // pcntl_wait is expected to be interrupted when the signal handler is invoked.
  207. $this->assertSame(-1, pcntl_waitpid($pid, $status));
  208. }
  209. $this->assertSame($restartSyscalls, microtime(true) - $microtime > 0.15);
  210. $this->assertTrue(pcntl_signal_dispatch());
  211. $this->assertCount(1, $handler->getRecords());
  212. if ($restartSyscalls) {
  213. // The child has already exited.
  214. $this->assertSame(-1, pcntl_waitpid($pid, $status));
  215. } else {
  216. // The child has not exited yet.
  217. $this->assertNotSame(-1, pcntl_waitpid($pid, $status));
  218. }
  219. }
  220. public function restartSyscallsProvider()
  221. {
  222. return array(
  223. array(false),
  224. array(true),
  225. array(false),
  226. array(true),
  227. );
  228. }
  229. /**
  230. * @dataProvider asyncProvider
  231. * @depends testRegisterDefaultPreviousSignalHandler
  232. * @requires function pcntl_async_signals
  233. */
  234. public function testRegisterAsyncSignalHandler($initialAsync, $desiredAsync, $expectedBefore, $expectedAfter)
  235. {
  236. $this->setSignalHandler(SIGURG, SIG_IGN);
  237. pcntl_async_signals($initialAsync);
  238. $logger = new Logger('test', array($handler = new TestHandler));
  239. $errHandler = new SignalHandler($logger);
  240. $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, $desiredAsync);
  241. $this->assertTrue(posix_kill(posix_getpid(), SIGURG));
  242. $this->assertCount($expectedBefore, $handler->getRecords());
  243. $this->assertTrue(pcntl_signal_dispatch());
  244. $this->assertCount($expectedAfter, $handler->getRecords());
  245. }
  246. public function asyncProvider()
  247. {
  248. return array(
  249. array(false, false, 0, 1),
  250. array(false, null, 0, 1),
  251. array(false, true, 1, 1),
  252. array(true, false, 0, 1),
  253. array(true, null, 1, 1),
  254. array(true, true, 1, 1),
  255. );
  256. }
  257. }