unix — процесс возрождения и обработки сигналов в переполнении стека

конкретика

У меня есть проблема в 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, Теперь вот два блока.

  1. Когда работник еще не появился — прямой выход. Посмотрите на линии 4 а также 5, это когда я отправляю SIGCONTтаким образом kill -18 к процессу. И тогда он запускает всю цепочку: запись в файл, system() вызов и выход из текущего процесса.
  2. Когда рабочий уже был возрожден сам по себе — прямой выход. Здесь, посмотрите на линии 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 указывает на какую-то неправильную вещь, но я не могу сказать наверняка сейчас.

Кроме того, я пробовал любые другие виды сигналов (на самом деле, неважно, что это за код сигнала, результат всегда был одинаковым)

Вопрос

Что может быть причиной такого поведения? Является ли способ, которым я возрождаю процесс, правильный? Если нет, каковы другие параметры, которые позволят вновь порожденному процессу правильно использовать определяемые пользователем обработчики сигналов?

3

Решение

Решение : В конце концов, 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 был заблокирован, что указывает на то, что именно это является причиной отказа от отправки сигналов.

разрешение : по какой-то причине, когда процесс порождает себя с установленными обработчиками сигналов, в новом экземпляре эти сигналы маскируются для игнорирования. Маскировка их решительно решает проблему, но почему маскируются сигналы в новом экземпляре — это пока открытый вопрос.

1

Другие решения

Это связано с тем, что вы порождаете дочерний процесс, выполняя 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« принимает »каждый бесхозный процесс в системе».

Для вашей ситуации я бы предложил два варианта:

  • Используйте два сценария: один для управления ребенком, а второй — «работник», чтобы фактически выполнить работу,
  • или, используйте один сценарий, который будет включать в себя оба: внешняя часть будет управлять, внутренняя часть, разветвленная от внешней, выполнит работу.
0

По вопросам рекламы [email protected]