2015-03-13 10 views
3

ОсобенностьПроцесс респаун и обработка сигнала в PHP

У меня есть проблема в PHP, когда respawned процессы не обработки сигналов, в то время, прежде чем паки, обработка работают правильно. Я сузил свой код на очень простой:

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

Update # 1: Я думал о вещах с ppid=1 (таким образом, init глобальный процесс) и бесхозных процессов обработки сигналов, но это не так. Вот log part, который показывает, что процесс сироты (ppid=1) не является причиной: когда рабочий запускается с помощью управления приложением, он также вызывает его командой system() - так же, как и сами респауны рабочих. Но после того, как приложение управляет приложением, оно имеет ppid=1 и правильно реагирует на сигналы, а если рабочий респавнирует себя, новая копия не отвечает на них, кроме SIGKILL. Таким образом, проблема появляется только тогда, когда рабочий респавент сам.

Обновление # 2: Я попытался проанализировать, что происходит с strace. Теперь, вот два блока.

  1. Когда работник еще не respawned - strace output. Посмотрите на строки 4 и 5, это когда я отправляю SIGCONT, таким образом kill -18 в процесс. И затем он запускает всю цепочку: записывая файл, system() вызывается и выходит из текущего процесса.
  2. Когда работник уже был респарен сам по себе - strace output. Вот, посмотрите на строки 8 и 9 - они появились после получения SIGCONT. Первое: похоже, что процесс все равно каким-то образом получает сигнал, а во-вторых, он игнорирует сигнал. Никаких действий не было сделано, но система уведомляла о том, что SIGCONT был отправлен. Почему тогда процесс игнорирует его - это вопрос (потому что, если установка обработчика пользователя для SIGCONT не удалась, то он должен завершить выполнение, а процесс не закончился). Что касается SIGKILL, то выход для уже respawned работника, как:

    nanosleep({1, 0}, <unfinished ...> 
    +++ killed by SIGKILL +++ 
    

, который указывает, что сигнал был получен, и сделал то, что он должен делать.

Проблема

В качестве процесса респаун, он не реагирует ни SIGTERM, ни к SIGCONT. Тем не менее, по-прежнему можно завершить его SIGKILL (так, kill -9 PID действительно завершает процесс). Например, для процесса выше как kill 8875, так и kill -18 8875 ничего не будет делать (процесс будет игнорировать сигналы и продолжать вести журнал сообщений).

Однако я бы не сказал, что регистрирующие сигналы терпят неудачу полностью, поскольку он переопределяет не менее SIGTERM (что обычно приводит к завершению, в то время как в этом случае оно игнорируется). Также я подозреваю, что ppid = 1 указывает на неправильную вещь, но я не могу сказать точно.

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

Вопрос

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

ответ

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

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

0

Это связано с тем, что вы создаете дочерний процесс, выполняя систему (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

Я думал об этом, но это не тот случай. Вот [log part] (http://pastebin.com/mSHL0VZu), который показывает, что процесс сироты ('ppid = 1') не является причиной: когда рабочий запускается при помощи приложения, он также вызывает его с помощью' system() '- так же, как и сам респавр рабочих. Но после того, как приложение управляет приложением, он имеет «ppid = 1» и правильно отвечает на сигналы, а если рабочий респавнирует себя, новая копия не отвечает на них, кроме 'SIGKILL'. Таким образом, проблема возникает только тогда, когда ** работник сам восстает **. –

+0

Я провел несколько тестов с использованием промежуточного сценария оболочки, и это похоже на то, что вы говорите: 'ppid = 1' не проблема, так как работает первый икру, второй - нет.Пока у меня больше нет идей. – leafnode

+0

В любом случае, я добавил некоторый анализ того, что делает процесс. –

Смежные вопросы