Обработчики сигналов процесса не называются - PullRequest
4 голосов
/ 29 апреля 2019

Я работаю на предварительно разветвленном сервере сокетов TCP, написанном на PHP.

Демон (родительский процесс) разветвляется на некоторое количество дочерних элементов, а затем ждет, пока ему не сообщат о выходе, и дочерние элементывсе прошло или он получает сигнал.

SIGINT и SIGTERM заставляют его отправлять SIGTERM всем дочерним элементам.

Дети устанавливают свои собственные обработчики сигналов: SIGTERM вызывает чистый выход.SIGUSR1 заставляет его сбросить некоторую информацию о состоянии (просто распечатайте, что он получил сигнал в примере кода ниже).

Если дочерний объект неожиданно завершает работу, родительский элемент запускает нового дочернего элемента, если флаг выхода не был установлен с помощьюОбработчик SIGINT.

Начальные дочерние элементы, разветвленные во время инициализации демона, реагируют на сигналы, как и ожидалось.

Недавно разветвленные дочерние элементы для замены неожиданного дочернего выхода, не отвечают сигналы.
Следующий код можно использовать для демонстрации этого:

<?php
$children = [];
$exiting = false;

pcntl_async_signals( true );
pcntl_signal( SIGCHLD, 'sigchldHandler' );
pcntl_signal( SIGINT, 'sigintHandler' );
pcntl_signal( SIGTERM, 'sigintHandler' );

// Fork our children.
for( $ii = 0; $ii < 1; $ii++ )
{
  startChild();
}

// Forks a single child.
function startChild()
{
  global $children;

  echo "Parent: starting child\n";
  $pid = pcntl_fork();
  switch( true )
  {
    case ( $pid > 0 ):
      $children[$pid] = $pid;
      break;

    case ( $pid === 0 ):
      child();
      exit( 0 );

    default:
      die( 'Parent: pcntl_fork() failed' );
      break;
  }
}

// As long as we have any children...
while( true )
{
  if( empty( $children ) ) break;
  sleep( 1 );
}

// The child process.
function child()
{
  $pid = posix_getpid();
  echo "Child $pid: started\n";
  sleep( 10 ); // Give us a chance to start strace (4/30/19 08:27)

  pcntl_sigprocmask( SIG_SETMASK, [] );           // Make sure nothing is blocked.
  pcntl_async_signals( true );                    // This may be inherited.
  pcntl_signal( SIGINT, SIG_IGN );                // Ignore SIGINT.
  pcntl_signal( SIGTERM, function() use ( $pid )  // Exit on SIGTERM.
  {
    echo "Child $pid: received SIGTERM\n";
    exit( 0 );
  }, false );
  pcntl_signal( SIGUSR1, function() use( $pid )   // Acknowledge SIGUSR1.
  {
    printf( "Child %d: Received SIGUSR1\n", $pid );
  });

  // Do "work" here.
  while( true )
  {
    sleep( 60 );
  }
}

// Handle SIGCHLD in the parent.
// Start a new child unless we're exiting.
function sigchldHandler()
{
  global $children, $exiting;

  echo "Parent: received SIGCHLD\n";
  while( true )
  {
    if( ( $pid = pcntl_wait( $status, WNOHANG ) ) < 1 )
    {
      break;
    }
    echo "Parent: child $pid exited\n";
    unset( $children[$pid] );
    if( !$exiting )
    {
      startChild();
    }
  }
}

// Handle SIGINT in the parent.
// Set exiting to true and send SIGTERM to all children.
function sigintHandler()
{
  global $children, $exiting;

  $exiting = true;
  echo PHP_EOL;
  foreach( $children as $pid )
  {
    echo "Parent: sending SIGTERM to $pid\n";
    posix_kill( $pid, SIGTERM );
  }
}

Запустите этот скрипт в терминальной сессии.Начальный вывод будет аналогичен этому с другим PID:

Родитель: начальный дочерний элемент
Дочерний 65016: начатый

Из другого сеанса терминала выполните уничтожениекоманда:

# kill -USR1 65016

Дочерний процесс отобразит это в первом терминальном сеансе:

Дочернее 65016: ПолученоSIGUSR1

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

# kill -TERM 65016 Выход для первого сеанса терминала будет выглядеть следующим образом (с разными PIDS):

Дочерний элемент 65016: получен SIGTERM
Родитель: получен SIGCHLD
Родитель: ребенок 65016 завершен
Родитель: начальный ребенок
Ребенок 65039: запущен

Новый дочерний процесс будет получать, но реагировать на любые сигналы в этот моменткроме SIGKILL и SIGSTOP, которые не могут быть перехвачены.

При отправке родителя SIGINT заставит его отправить SIGTERM новому дочернему элементу.Ребенок не получит его, и родитель будет ждать, пока ребенок насильственно не убьется, прежде чем выйти (да, рабочий код будет включать тайм-аут и SIGKILL всех оставшихся потомков).

Среда:
- Ubuntu 18.04.2
- macOS Mojave 10.14.3 (такое же поведение)
- PHP 7.2.17 (cli)

Я обнаружил, что у меня нет идей.Мысли?

РЕДАКТИРОВАТЬ 30-Apr-2019 08:27 PDT:
У меня есть немного больше информации.Я добавил sleep (10) сразу после 'echo "Child $ pid: start \ n";'чтобы дать мне шанс запустить strace на дочернем элементе.

На основе вывода strace выглядит, что сигналы доставляются, но обработчик дочернего сигнала не вызывается.

# sudo strace - p 69710
strace: Process 69710 attached
restart_syscall(<... resuming interrupted nanosleep ...>) = 0
rt_sigprocmask( SIG_SETMASK, [], ~[ KILL STOP RTMIN RT_1], 8) = 0
rt_sigaction( SIGINT, {sa_handler = SIG_IGN, sa_mask = [], sa_flags = SA_RESTORER, sa_restorer = 0x7f6e8881cf20}, null, 8) = 0
rt_sigprocmask( SIG_UNBLOCK, [ INT ], null, 8 ) = 0
rt_sigaction( SIGTERM, {sa_handler = 0x55730bdaf2e0, sa_mask = ~[ ILL TRAP ABRT BUS FPE KILL SEGV CONT STOP TSTP TTIN TTOU SYS RTMIN RT_1], sa_flags = SA_RESTORER | SA_INTERRUPT | SA_SIGINFO, sa_restorer = 0x7f6e8881cf20}, null, 8) = 0
rt_sigprocmask( SIG_UNBLOCK, [ TERM ], null, 8 ) = 0
rt_sigaction( SIGUSR1, {sa_handler = 0x55730bdaf2e0, sa_mask = ~[ ILL TRAP ABRT BUS FPE KILL SEGV CONT STOP TSTP TTIN TTOU SYS RTMIN RT_1], sa_flags = SA_RESTORER | SA_RESTART | SA_SIGINFO, sa_restorer = 0x7f6e8881cf20}, null, 8) = 0
rt_sigprocmask( SIG_UNBLOCK, [ USR1 ], null, 8 ) = 0
nanosleep({tv_sec = 60, tv_nsec = 0}, 0x7ffe79859470) = 0
nanosleep({tv_sec = 60, tv_nsec = 0}, {tv_sec = 37, tv_nsec = 840636107}) = ? ERESTART_RESTARTBLOCK( Interrupted by signal)
--- SIGUSR1 {si_signo = SIGUSR1, si_code = SI_USER, si_pid = 69544, si_uid = 1000} ---
rt_sigreturn({mask = []})                 = -1 EINTR( Interrupted system call)
rt_sigprocmask( SIG_BLOCK, ~[ RTMIN RT_1], [], 8) = 0
rt_sigprocmask( SIG_SETMASK, [], null, 8 ) = 0
nanosleep({tv_sec = 60, tv_nsec = 0}, 0x7ffe79859470) = 0

1 Ответ

2 голосов
/ 02 мая 2019

Я считаю, что проблема в том, что обработка сигналов PHP не работает так, как это может случиться, когда pcntl_fork вызывается внутри зарегистрированной функции обработки сигналов.Поскольку второй дочерний процесс создается внутри sigchldHandler, он не получает обработки последующих сигналов.

Редактировать: К сожалению, у меня нет ссылок на это.Я сам ударился головой о стену с такой же проблемой, что и OP (отсюда и новая учетная запись!), И я не могу найти каких-либо окончательных ответов или объяснений этого поведения, только доказательства из ручных тестов-заглушек.Я бы тоже хотел знать (:

...