📜 ⬆️ ⬇️

Signal processing in PHP, or cooking delicious

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?





Arsenal that provides us with php to work with 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 itself
This 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) { } 

Source: https://habr.com/ru/post/355020/


All Articles