Обратные вызовы сигналов PHP pcntl не вызываются


Это полный воспроизводимый код.

<?php
class console{
    public static function log($msg, $arr=array()){
        $str = vsprintf($msg, $arr);
        fprintf(STDERR, "$str\n");
    }
}
function cleanup(){
    echo "cleaning up\n";
}
function signal_handler($signo){
    console::log("Caught a signal %d", array($signo));
    switch ($signo) {
        case SIGTERM:
            // handle shutdown tasks
             cleanup();
            break;
        case SIGHUP:
            // handle restart tasks
            cleanup();
            break;
        default:
            fprintf(STDERR, "Unknown signal ". $signo);
    }
}

if(version_compare(PHP_VERSION, "5.3.0", '<')){
    // tick use required as of PHP 4.3.0
    declare(ticks = 1);
}

pcntl_signal(SIGTERM, "signal_handler");
pcntl_signal(SIGHUP, "signal_handler");

if(version_compare(PHP_VERSION, "5.3.0", '>=')){
    pcntl_signal_dispatch();
    console::log("Signal dispatched");
}

echo "Running an infinite loop\n";
while(true){
    sleep(1);
    echo date(DATE_ATOM). "\n";
}

Когда я запускаю это, я вижу значения даты каждую секунду. Теперь, если я нажму Ctrl+Ccleanup, функция не вызывается. На самом деле signal_handler не называется.

Вот пример вывода.

$ php testsignal.php 
Signal dispatched
Running an infinite loop
2012-10-04T13:54:22+06:00
2012-10-04T13:54:23+06:00
2012-10-04T13:54:24+06:00
2012-10-04T13:54:25+06:00
2012-10-04T13:54:26+06:00
2012-10-04T13:54:27+06:00
^C
Author: hakre, 2012-10-04

3 answers

CTRL+C запускает SIGINT, для которого вы не установили обработчик:

<?php
...
function signal_handler($signo){
...
    case SIGINT:
        // handle restart tasks
        cleanup();
        break;
...
}
...
pcntl_signal(SIGINT, "signal_handler");
...

Теперь это работает:

$ php testsignal.php
Signal dispatched
Running an infinite loop
2012-10-08T09:57:51+02:00
2012-10-08T09:57:52+02:00
^CCaught a signal 2
cleaning up
2012-10-08T09:57:52+02:00
2012-10-08T09:57:53+02:00
^\Quit
 9
Author: Matej Kovac, 2012-10-08 08:02:14

Важно понимать, как работает declare: http://www.php.net/manual/en/control-structures.declare.php

Ему либо нужно обернуть то, на что он влияет, чтобы быть объявленным в "корневом" скрипте(index.php или что-то подобное).

Забавный факт; проверка IF вокруг declare(ticks = 1); не имеет значения, если вы удалите эти 3 строки, скрипт перестанет работать, если вы измените проверку на if (false), он будет работать, объявление, похоже, оценивается вне обычного потока кода xD

 4
Author: Ruben de Vries, 2014-05-08 08:31:03

После исправления Рубеном де Врисом использования тиков объявления во время компиляции, в исходном коде есть две ошибки (кроме отсутствия сигнала INT).

Как говорит Рубен:

if(false) declare(ticks=1);

Это ДЕЙСТВИТЕЛЬНО объявляет, что тики должны быть обработаны. Попробуй! В документах далее говорится, как вы не можете использовать переменные и т. Д. Это взлом во время компиляции.

Кроме того, использование pcntl_signal_dispatch() не является прямой заменой для объявления тиков - оно должно вызываться во время выполнения кода - как это делают клещи автоматически. Поэтому вызов его один раз в начале такого сценария будет обрабатывать только любые сигналы, ожидающие в этот момент.

Чтобы заставить pcntl_signal_dispatch() работать так, как если бы для его управления использовались галочки, вам нужно было бы разбрызгать его по всему вашему коду... в зависимости от того, где именно/когда вы хотите, чтобы прерывания вступили в силу.

По крайней мере:

while(true){
    sleep(1);
    echo date(DATE_ATOM). "\n";
    pcntl_signal_dispatch();
}

Таким образом, исходный код фактически использует галочки для всех версий PHP, и дополнительно проверяет только для одного сигнала, полученного во время начального процесса загрузки, если версия больше 5.3. Не то, что предполагал ОП.

Лично я нахожу использование клещей для управления сигналами PCNTL огромной грязной болью. "Цикл" в моем случае находится в коде третьей стороны, поэтому я не могу добавить метод отправки, и его галочки объявления на верхнем уровне не работают на производстве, если я не добавлю его в каждый файл (я думаю, что это проблема с загрузкой, областью действия или версией PHP7).

 0
Author: scipilot, 2017-08-13 08:44:36