конкретика
У меня есть проблема в 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
) процесс не является причиной: когда рабочий запускается с помощью управления приложением, он также вызывает его с system()
команда — так же, как работник возрождается сам. Но после того, как управляющее приложение вызывает работника, оно имеет ppid=1
и правильно реагирует на сигналы, в то время как если работник снова появляется, новая копия не отвечает на них, кроме SIGKILL
, Итак, проблема появляется только тогда, когда работник возрождается сам.
Обновление № 2 : Я пытался проанализировать, что происходит с strace
, Теперь вот два блока.
4
а также 5
, это когда я отправляю SIGCONT
таким образом kill -18
к процессу. И тогда он запускает всю цепочку: запись в файл, system()
вызов и выход из текущего процесса.Когда рабочий уже был возрожден сам по себе — прямой выход. Здесь, посмотрите на линии 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
указывает на какую-то неправильную вещь, но я не могу сказать наверняка сейчас.
Кроме того, я пробовал любые другие виды сигналов (на самом деле, неважно, что это за код сигнала, результат всегда был одинаковым)
Вопрос
Что может быть причиной такого поведения? Является ли способ, которым я возрождаю процесс, правильный? Если нет, каковы другие параметры, которые позволят вновь порожденному процессу правильно использовать определяемые пользователем обработчики сигналов?
Решение : В конце концов, strace
помог понять проблему. Это выглядит следующим образом:
nanosleep({1, 0}, {0, 294396497}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
restart_syscall(<... resuming interrupted call ...>) = 0
Таким образом, он показывает, что сигнал был получен, но игнорируются. Чтобы полностью ответить на вопрос, мне нужно выяснить, почему процесс добавил сигналы для игнорирования списка, но принудительно разблокировал их с помощью pcntl_sigprocmask()
делает вещь:
pcntl_sigprocmask(SIG_UNBLOCK, [SIGTERM, SIGCONT]);
тогда все идет хорошо, и порожденный процесс получает / обрабатывает сигналы, как и предполагалось. Я пытался добавить только SIGCONT
для разблокировки, например — и тогда он был обработан правильно, в то время как SIGTERM
был заблокирован, что указывает на то, что именно это является причиной отказа от отправки сигналов.
разрешение : по какой-то причине, когда процесс порождает себя с установленными обработчиками сигналов, в новом экземпляре эти сигналы маскируются для игнорирования. Маскировка их решительно решает проблему, но почему маскируются сигналы в новом экземпляре — это пока открытый вопрос.
Это связано с тем, что вы порождаете дочерний процесс, выполняя system (foo), а затем продолжаете умирать от текущего процесса. Следовательно, процесс становится сиротой, а его родитель становится PID 1 (init).
Вы можете увидеть изменения, используя pstree
команда.
До:
init─┬─cron
(...)
└─screen─┬─zsh───pstree
├─3*[zsh]
├─zsh───php
└─zsh───vim
После:
init─┬─cron
(...)
└─php
Что в Википедии говорится:
Сиротные процессы — это своего рода противоположная ситуация с процессами зомби, поскольку она относится к случаю, когда родительский процесс завершается до того, как его дочерние процессы, и в этом случае говорят, что эти дети становятся «осиротевшими».
В отличие от асинхронного уведомления от потомка к родителю, которое происходит, когда дочерний процесс завершается (посредством сигнала SIGCHLD), дочерние процессы не уведомляются немедленно, когда заканчивается их родительский процесс. Вместо этого система просто переопределяет поле «parent-pid» в данных дочернего процесса как процесс, являющийся «предком» любого другого процесса в системе, чей pid обычно имеет значение 1 (один), а имя которого традиционно «init». Таким образом, говорится, что «init« принимает »каждый бесхозный процесс в системе».
Для вашей ситуации я бы предложил два варианта: