Signal processing in PHP has already been
written a few articles . But there this topic is described only indirectly.
At once I will make a reservation that I know that PHP 5.5 and 5.2 has already been released is morally obsolete, but the task needed to be solved in PHP 5.2. For those lucky ones who use a newer version of PHP, I will also write, but closer to the end of the article.
Processing pcntl signals in PHP is done by passing the handler function to the
pcntl_signal () function, and only one handler can be hung for each signal. Each next handler will replace the previous one, and there will not be any notes.
')
Those. if you use several third-party libraries and each need to process some signal, then only one library will handle the signal. It turns out that third-party libraries should only provide callbacks for signal processing, and the library user should call these callbacks in the signal handler.
I will give an example:
Suppose that in two third-party libraries two different libraries are used for logging and both libraries must process the SIGHUP signal in order to rediscover the log file as it rotates. It happens as follows. A daemon that is responsible for the rotate of the logs. Movit log files and sends a SIGHUP signal to all processes that use this log file. After moving the log file, but before processing the signal, the process continues to write to the same file. After correct processing of this signal, the process should start writing to a new file.
The signal handler should not belong to one of these libraries, but should be part of the application. It should call the signal handlers of the libraries themselves.
declare (ticks = 1)
Before version 5.3, in order for a signal handler to be called, it is imperative to use the
declare construction
(ticks = 1) . Normal php-programmer such a design introduces bewilderment. When reading the manual, it becomes not much clearer how it works, especially for those who are not professional in C ++ and other languages ​​that have execution control constructs:
The declare construct can be used in the global scope, affecting all the code following it (however, if the file with declare was included, then it has no effect on the parent file).
There clearly need to consider examples.
Performance
How this design affects the performance is not written, so I made benchmarks. Files:
Example.php:
<?php class Example { public function run() { for($i = 0; $i < 10000000; $i++); } }
testWithTicksSpeed.php:
<?php declare(ticks = 1); require_once __DIR__ . '/Example.php'; $example = new Example(); $example->run();
testWithoutTicksSpeed.php:
<?php require_once __DIR__ . '/Example.php'; $example = new Example(); $example->run();
I must say that the test is synthetic, because it has no access to the database, reading files, etc. performance of which declare (ticks = 1) does not affect.
Results:
mougrim@mougrim-pc:pcntls-signals$ time php testWithTicksSpeed.php complete, process time: 11 real 0m10.186s user 0m4.448s sys 0m5.732s mougrim@mougrim-pc:pcntls-signals$ time php testWithoutTicksSpeed.php complete, process time: 2 real 0m1.515s user 0m1.504s sys 0m0.008s
The difference is ~ 6.7 times. In a real project, the difference will of course be smaller, but I don’t want to waste resources and time where there is no need to catch signals.
To understand how to save resources (not always cause declare) you need to understand how this declare works. During the experiments, it turned out the following.
This code works:
class Test_Signal {
This code does not work:
class Test_Signal { public function run() { $this->declareTicks();
Those. if no specific block of code is specified for declare, then it is valid for the entire code following it. And here it means the code processed by the interpreter and turned into an OP code.
To be clearer, consider the example from the benchmark.
In class Example, signals are processed:
declare(ticks = 1); require_once __DIR__ . '/Example.php'; $example = new Example(); $example->run();
In the class Example, signals are not processed:
require_once __DIR__ . '/Example.php'; declare(ticks = 1); $example = new Example(); $example->run();
Subtleties
When testing signal handlers, strange things came to light. At some point, the daemon started processing the same signal many times so that the main script code was practically not executed. When a single SIGTERM signal was sent, the daemon also began to process it many times. After talking with knowledgeable people, it turned out that the signal handler should be as small as possible and not allocate memory. This is due to the fact that the signal handler can be called during memory allocation and memory allocation in the processor can lead to its damage and unpredictable consequences. A signal handler is obtained when using declare (ticks = 1) should be minimal, for example, put some flag, and direct processing should be in the main script loop.
Check this I do not have enough knowledge, because I do not develop in C / C ++, but when using the Mougrim_Pcntl_SignalHandler described below and which does not allocate memory during signal processing, this problem is no longer reproduced.
Signal processing in PHP 5.3
PHP 5.3 has a great pcntl_signal_dispatch () function. The bottom line is that if you do not declare declare (ticks = 1), then the signals are copied into the queue and if you call the pcntl_signal_dispatch () function, then the accumulated signal handlers will be called. If the same signal was sent several times, the handler will also be called several times. This function solves performance problems and minimizes the signal handler, since processing does not occur anywhere, but only during the pcntl_signal_dispatch () call.
Signalhandler
Example of a signal handler for 5.2, src / lt5.3 / Mougrim / Pcntl / SignalHandler.php file:
<?php declare(ticks = 1); class Mougrim_Pcntl_SignalHandler { private $handlers = array(); private $toDispatch = array(); public function addHandler($signalNumber, $handler, $isAdd = true) { $isHandlerNotAttached = empty($this->handlers[$signalNumber]); if($isAdd) $this->handlers[$signalNumber][] = $handler; else $this->handlers[$signalNumber] = array($handler); if($isHandlerNotAttached && function_exists('pcntl_signal')) { $this->toDispatch[$signalNumber] = false; pcntl_signal($signalNumber, array($this, 'handleSignal')); } } public function dispatch() { foreach($this->toDispatch as $signalNumber => $isNeedDispatch) { if(!$isNeedDispatch) continue; $this->toDispatch[$signalNumber] = false; foreach($this->handlers[$signalNumber] as $handler) call_user_func($handler, $signalNumber); } } public function handleSignal($signalNumber) { $this->toDispatch[$signalNumber] = true; } }
The handler solves two problems:
1) it emulates pcntl_signal_dispatch ();
2) allows the use of multiple handler functions for a single signal.
Example of a signal handler for 5.3 and higher, src / gte5.3 / Mougrim / Pcntl / SignalHandler.php file:
<?php namespace Mougrim\Pcntl; class SignalHandler { private $handlers = array(); private $toDispatch = array(); public function addHandler($signalNumber, $handler, $isAdd = true) { $isHandlerNotAttached = empty($this->handlers[$signalNumber]); if($isAdd) $this->handlers[$signalNumber][] = $handler; else $this->handlers[$signalNumber] = array($handler); if($isHandlerNotAttached && function_exists('pcntl_signal')) { $this->toDispatch[$signalNumber] = false; pcntl_signal($signalNumber, array($this, 'handleSignal')); } } public function dispatch() { pcntl_signal_dispatch(); foreach($this->toDispatch as $signalNumber => $isNeedDispatch) { if(!$isNeedDispatch) continue; $this->toDispatch[$signalNumber] = false; foreach($this->handlers[$signalNumber] as $handler) call_user_func($handler, $signalNumber); } } private function handleSignal($signalNumber) { $this->toDispatch[$signalNumber] = true; } }
This handler solves only one problem - it allows you to use several signal handler functions. At the same time, the class interface is identical to the Mougrim_Pcntl_SignalHandler class interface.
Usage example
Lastly, an example of using files:
signalExampleRun.php:
<?php
SignalExample.php:
<?php class SignalExample { private $signalHandler; public function __construct(Mougrim_Pcntl_SignalHandler $signalHandler) { $this->signalHandler = $signalHandler; } public function run() {
For 5.3 and above, the example is similar, only you need to connect src / gte5.3 / Mougrim / Pcntl / SignalHandler.php and use the class \ Mougrim \ Pcntl \ SignalHandler.
findings
1) If you are using PHP 5.3 or higher and want to avoid implicit problems,
do not use the declare clause (ticks = 1);2) declare (ticks = 1); works independently of conditional constructions and function calls and works in the code that was “loaded” into the interpreter after the declaration declare (ticks = 1);
3) If you use Mougrim_Pcntl_SignalHandler in PHP 5.2, then it should connect to a file with a class or code with the main program loop in which you need to process signals;
4) Since With declare (ticks = 1), the application is slower, so you only need to declare this construct where there is signal processing.
Who cares, the source codes of the classes \ Mougrim \ Pcntl \ SignalHandler and Mougrim_Pcntl_SignalHandler are given on the
github .
* UPD. * In the
first comment, PsychodelEKS suggests that even if the signal handler is called when pcntl_signal_dispatch () is called, it is still a handler and if you run a program from under it, for example through system (), then the running program cannot process signals, .to. It will itself be a signal handler. Therefore, I will slightly change the code of the \ Mougrim \ Pcntl \ SignalHandler class so that the handlers are called separately, as is done in Mougrim_Pcntl_SignalHandler.
* UPD2. * Fixed bugs, as well as according to the
first commentary, corrected the \ Mougrim \ Pcntl \ SignalHandler class so that direct signal processing would occur outside the handler passed to pcntl_signal () (such immediate handlers are re-entry).