Особенности
У меня есть проблема в PHP, когда возрожденные процессы не обрабатывают сигналы, а до возрождения обработка работает правильно. Я сузил свой код до самого простого:
declare(ticks=1);
register_shutdown_function(function() {
if ($noRethrow = ob_get_contents()) {
ob_end_clean();
exit;
}
system('/usr/bin/nohup /usr/bin/php '.__FILE__. ' 1>/dev/null 2>/dev/null &');
});
function handler($signal)
{
switch ($signal) {
case SIGTERM:
file_put_contents(__FILE__.'.log', sprintf('Terminated [ppid=%s] [pid=%s]'.PHP_EOL, posix_getppid(), posix_getpid()), FILE_APPEND);
ob_start();
echo($signal);
exit;
case SIGCONT:
file_put_contents(__FILE__.'.log', sprintf('Restarted [ppid=%s] [pid=%s]'.PHP_EOL, posix_getppid(), posix_getpid()), FILE_APPEND);
exit;
}
}
pcntl_signal(SIGTERM, 'handler');
pcntl_signal(SIGCONT, 'handler');
while(1) {
if (time() % 5 == 0) {
file_put_contents(__FILE__.'.log', sprintf('Idle [ppid=%s] [pid=%s]'.PHP_EOL, posix_getppid(), posix_getpid()), FILE_APPEND);
}
sleep(1);
}
Как видите, он делает следующее:
- Регистрация функции выключения, в которой перезапускается процесс с
nohup
(таким образом, чтобы игнорироватьSIGHUP
, когда родительский процесс умирает) - Регистрация обработчика через
pcntl_signal()
дляSIGTERM
иSIGCONT
. Первый просто зарегистрирует сообщение о том, что процесс был завершен, а второй приведет к повторному появлению процесса. Это достигается с помощьюob_*
функций, поэтому для передачи флага, что нужно сделать в функции выключения - либо выход, либо респаун. - Регистрация некоторой информации о том, что сценарий «живой», в файл журнала.
Что происходит
Итак, я начинаю скрипт с:
/usr/bin/nohup /usr/bin/php script.php 1>/dev/null 2>/dev/null &
Затем в файле журнала есть такие записи, как:
Idle [ppid=7171] [pid=8849]
Idle [ppid=7171] [pid=8849]
Скажем, тогда я делаю kill 8849
:
Terminated [ppid=7171] [pid=8849]
Таким образом, это успешная обработка SIGTERM
(и скрипт действительно завершает работу). Теперь, если я вместо этого сделаю kill -18 8849
, то я увижу (18 — числовое значение для SIGCONT
):
Idle [ppid=7171] [pid=8849]
Restarted [ppid=7171] [pid=8849]
Idle [ppid=1] [pid=8875]
Idle [ppid=1] [pid=8875]
А, следовательно: во-первых, SIGCONT
тоже обрабатывался корректно, и, судя по очередным сообщениям "Idle", новоиспеченный экземпляр скрипта работает нормально.
Обновление №1: я думал о вещах с ppid=1
(таким образом, init
глобальный процесс) и обработке сигналов сиротских процессов, но это не тот случай. Вот часть журнала, из которой видно, что потерянный (ppid=1
) процесс не является причиной: когда рабочий процесс запускается путем управления app, он также вызывает его с помощью команды system()
— так же, как рабочий респавнится сам. Но после того, как управляющее приложение вызывает воркер, оно имеет ppid=1
и правильно реагирует на сигналы, а если воркер респавнится сам, то новая копия на них не реагирует, кроме SIGKILL
. Таким образом, проблема появляется только тогда, когда воркер возрождается.
Обновление №2: я попытался проанализировать, что происходит с strace
. Итак, два блока.
- Когда воркер еще не возродился - вывод strace. Взгляните на строки
4
и5
, это когда я отправляюSIGCONT
, таким образом,kill -18
процессу. А дальше срабатывает вся цепочка: запись в файл, вызовsystem()
и выход из текущего процесса. Когда воркер уже респавнился сам по себе - вывод strace. Здесь обратите внимание на строки
8
и9
— они появились после полученияSIGCONT
. Во-первых: похоже, что процесс все еще каким-то образом получает сигнал, а во-вторых, он игнорирует сигнал. Никаких действий не производилось, но процесс был уведомлен системой об отправкеSIGCONT
. Почему тогда процесс его игнорирует - вот вопрос (потому что, если установка пользовательского обработчика дляSIGCONT
не удалась, то он должен завершить выполнение, а процесс не завершен). Что касаетсяSIGKILL
, то вывод для уже возрожденного воркера выглядит так:nanosleep({1, 0}, <unfinished ...> +++ killed by SIGKILL +++
Что указывает на то, что сигнал был получен и сделал то, что должен был сделать.
Проблема
Так как процесс респавнится, то не реагирует ни на SIGTERM
, ни на SIGCONT
. Тем не менее, его все еще можно завершить с помощью SIGKILL
(так что kill -9 PID
действительно завершает процесс). Например, для вышеуказанного процесса и kill 8875
, и kill -18 8875
ничего не сделают (процесс будет игнорировать сигналы и продолжать регистрировать сообщения).
Однако я бы не сказал, что регистрация сигналов полностью проваливается - потому что она переопределяет как минимум SIGTERM
(что обычно приводит к завершению, а в данном случае игнорируется). Также я подозреваю, что ppid = 1
указывает на что-то не то, но сейчас точно сказать не могу.
Кроме того, я пробовал любые другие виды сигналов (на самом деле не имел значения, какой код сигнала, результат всегда был один и тот же)
Вопрос
Что может быть причиной такого поведения? Верен ли способ, которым я респавню процесс? Если нет, то каковы другие параметры, которые позволят вновь созданному процессу правильно использовать пользовательские обработчики сигналов?