On the Internet, including Habra, the topic of signal processing using php has been raised several times, but for the most part they are quite old, contain irrelevant information, and do not answer the frequently asked question: “why?”, We are let's start
When can we need signal processing in php?
- In any loaded project, sooner or later you have to face the need to parallelize the process and the most frequent way is to use a message server, such as RabbitMq, Gearman, Kafka and others. At this moment it becomes necessary to create a so-called consumer. It consists of a cycle, checking for new messages and processing them.
Now, the actual situation: we updated the code and we need to restart the consumer calculator - if you just send a SIGTERM signal, you will be able to get non-consistency of data in the database or other problems due to script breaks in the middle of processing, in which case we will be helped by signal processing. - Also in the era of containerization, it is not bad to be able to correctly extinguish the container with the application without losing the processed information.
- Well, the third option - specific tasks where you need a demon and wrote it in php, here we can use a number of signals to update the configuration, complete the script, and other actions. An example .
Arsenal that provides us with php to work with signals:- pcntl_signal () php> = 4.1.0 is a function for registering a signal handler.
- declare (ticks = 1) php <5.3 - indicates how many ticks the interpreter will check for the presence of a signal.
- pcntl_signal_dispatch () php> = 5.3.0 - manual launch of the raw signal check, as a more productive alternative to declare.
- pcntl_async_signals () php> = 7.1.0 - asynchronous substitution of the signal handler in the call stack.
- pcntl_signal_get_handler () php> = 7.1.0 - getting the function of the signal handler.
- pcntl_alarm () php> = 4.3.0 - send yourself a SIGALARM.
- pcntl_sigprocmask () php> = 5.3.0 - you can block, unblock the processing of specified signals, also delete, replace the stack of blocked signals.
A bit of theory:
For each signal that we want to process, you need to register a handler function via pcntl_signal ().
')
Important note from php.net pcntl_signal () - does not assemble signal handlers into the stack, but replaces them, that is, if you somewhere in the code again define a signal handler of some kind of signal, it will simply override the previous one, this is why libraries
do not use try not to use these features. But at the end of the article there will be a small bonus about this.
After executing the handler function, the interpreter continues its work from the interruption point.
If you certainly did not call die () in the handler .
From previous versions of php, we know about the existence of the declare directive (ticks = 1), which told our script after each operation, see if the signal came to us, respectively, it gave a tangible overhead while executing, especially if there is a lot of code, it is well described
here . But fortunately in the courtyard in 2018 and the language developers added an amazing thing - pcntl_async_signals (), this function allows the interpreter not to be distracted by checking the signal, in fact it puts our signal handler as the next in the call stack for the function being performed.
The synthetic performance test showed no differences with and without pcntl_async_signals ().Now let's talk about handler restrictions, the signal will be processed only after the end of the current function execution, if this call to api or DB will take a while before processing the signal, this should be taken into account, for example, if you use a supervisor or a docker, increase the timeout before sending SIGKILL during your own long blocking call plus stock. I also want to say that at the time of testing I encountered an interesting behavior of the sleep () function, as it turned out to be documented, but I did not expect it - it is interrupted by a signal and returns the number of seconds that it did not sleep, that is, if you suddenly need to use it and be sure of the length of sleep, then this would look like this:
$sleep = 1000; while ($sleep > 0) { $sleep = sleep($sleep); }
I also want to focus on the fact that you will encounter examples of SIGKILL processing on many resources -
but this does not work (it worked on older versions of linux), now this signal kills the process from the operating system and cannot be affected.
And some code
What it will look like in the case of a RabbitMq with a cosyumer:
This is our simplified task manager whose task is to prepare a concierge and follow up on tasks
class QueueManager { private $stopConsume = false; public function stopConsume() { $this->stopConsume = true; } public function consume($consumerAlias) { $consumer = $this->getConsumerBuilder()->create($consumerAlias); $channel = $consumer->getChannel(); while (\count($channel->callbacks) && $this->stopConsume !== true) { $channel->wait(); } } }
And this is all you need in the controller:
class SomeController { private $queueManager; public function __construct() { $this->queueManager = new QueueManager(); pcntl_signal(SIGTERM, [$this->queueManager, 'stopConsume']); } public function consumeSomeQueue() { $this->queueManager->consume('SomeConsumer'); } }
When the signal is received, the stopConsume method of the queueManager object will be called - it in turn will set the stopConsume parameter to true and will only have to go to the end of processing the current message, after which the cycle will end.
The purpose of the article is not to consider side issues of demonizing processes such as maintaining connections, memory leaks, etc.
I hope this information was useful and interesting for you,
if there are any questions left, I will gladly answer in the comments or add to the article as necessary.
Actually the bonus itselfThis class format will give us the opportunity to put several handlers on the signal, if you ask why, simply because we can.
class SigHandler { private $handlers = []; public function handle($sigNumber) { if (!empty($this->handlers[$sigNumber])) { foreach ($this->handlers[$sigNumber] as $signalHandler) { $signalHandler($sigNumber); } } } public function subscribe($sigNumber, $handler) { $this->handlers[$sigNumber][$this->getFunctionHash($handler)] = $handler; } public function unsubscribe($sigNumber, $handler) { unset($this->handlers[$sigNumber][$this->getFunctionHash($handler)]); } private function getFunctionHash($callable) { return spl_object_hash($callable); } }
Try at work:
pcntl_async_signals(true); $sigHandler = new SigHandler(); pcntl_signal(SIGTERM, [$sigHandler, 'handle']); $sigHandler->subscribe(SIGTERM, function () { echo 'sigterm_1', PHP_EOL; }); $sigHandler->subscribe(SIGTERM, function () { echo 'sigterm_2', PHP_EOL; }); while (true) { }