Подтвердить что ты не робот

Прекращение процессов дочерних процессов зомби, раздвоенных с сервера сокетов

Отказ

Мне хорошо известно, что PHP, возможно, не был лучшим выбором в этом случае для сервера сокетов. Пожалуйста, воздержитесь от предложения разные языки/платформы - поверьте мне - я слышал это от всех направления.

Работая в Unix-среде и используя PHP 5.2.17, моя ситуация выглядит следующим образом. Я построил сервер сокетов на PHP, который обменивается данными с флеш-клиентами. Мой первый шаг состоял в том, что каждое входящее соединение блокировало последовательные соединения, пока оно не закончило обрабатываться. Я решил это, используя PHP pcntl_fork(). Я успешно смог вызвать множество дочерних процессов (сохраняя их PID в родительском), которые занимались передачей сообщений другим клиентам и, следовательно, "освобождали" родительский процесс и позволяли ему продолжать обрабатывать следующее соединение [s].

Моя главная проблема прямо сейчас заключается в обработке/обработке с коллекцией этих мертвых/зомби-дочерних процессов и их прекращении. Я прочитал (снова и снова) соответствующие страницы руководства PHP для pcntl_fork() и понял, что родительский процесс отвечает за очистка своих детей. Родительский процесс получает SIGNAL из своего дочернего элемента, когда дочерний элемент выполняет exit(0). Я могу "поймать" этот сигнал, используя функцию pcntl_signal(), чтобы настроить обработчик сигнала.

Мой signal_handler выглядит следующим образом:

declare(ticks = 1); 
function sig_handler($signo){ 
  global $forks; // this is an array that holds all the child PID's
  foreach($forks AS $key=>$childPid){
    echo "has my child {$childPid} gone away?".PHP_EOL;
    if (posix_kill($childPid, 9)){
      echo "Child {$childPid} has tragically died!".PHP_EOL;
      unset($forks[$key]);
    }
  }
}

Я действительно вижу как echo, включая соответствующий и правильный дочерний PID, который нужно удалить, но кажется, что

posix_kill($childPid, 9)

Я понимаю, что синонимом kill -9 $childPid возвращается TRUE, хотя на самом деле НЕ удаляет этот процесс...

Взято из man-страниц posix_kill:

Возвращает TRUE при успешном завершении или FALSE при сбое.


Я отслеживаю дочерние процессы с помощью команды ps. Они выглядят следующим образом:

web5      5296  5234  0 14:51 ?        00:00:00 [php] <defunct>
web5      5321  5234  0 14:51 ?        00:00:00 [php] <defunct>
web5      5466  5234  0 14:52 ?        00:00:00 [php] <defunct>

Как вы можете видеть, все эти процессы являются дочерними процессами родителя, у которого есть PID 5234

Я что-то пропустил в своем понимании? Кажется, мне удалось заставить все работать (и это так), но я остался с бесчисленными зомби-процессами в системе!

Мои планы на зомби-апокалипсис - твердые камни -
но что я могу сделать, когда даже sudo kill -9 не убивает дочерние процессы зомби?


Обновление через 10 дней

Я сам ответил на этот вопрос после некоторых дополнительных исследований, если вы все еще можете выдержать свои промахи продолжить.

4b9b3361

Ответ 1

Я обещаю там решение в конце: P

Хорошо... так вот, 10 дней спустя, и я считаю, что я решил эту проблему. Я не хотел добавлять к уже длинному сообщению, поэтому я включу в этот ответ некоторые из вещей, которые я пробовал.

Взяв @sym advice и прочитав больше в документации и комментариях к документации, pcntl_waitpid():

Если дочерний объект по запросу pid уже вышел на время вызова (так называемый Процесс "зомби" ), функция немедленно возвращается. Любые системные ресурсы, используемые ребенком
освобождены...

Итак, я настраиваю обработчик pcntl_signal() следующим образом:

function sig_handler($signo){ 
    global $childProcesses;
    $pid = pcntl_waitpid(-1, $status, WNOHANG);
    echo "Sound the alarm! ";
    if ($pid != 0){
        if (posix_kill($pid, 9)){
            echo "Child {$pid} has tragically died!".PHP_EOL;
            unset($childProcesses[$pid]);
        }
    }
}
// These define the signal handling
// pcntl_signal(SIGTERM, "sig_handler");
// pcntl_signal(SIGHUP,  "sig_handler");
// pcntl_signal(SIGINT, "sig_handler");
pcntl_signal(SIGCHLD, "sig_handler");

Для завершения я включу фактический код, который я использую для разветвления дочернего процесса -

function broadcastData($socketArray, $data){
        global $db,$childProcesses;
        $pid = pcntl_fork();
        if($pid == -1) {
                // Something went wrong (handle errors here)
                // Log error, email the admin, pull emergency stop, etc...
                echo "Could not fork()!!";
        } elseif($pid == 0) {
                // This part is only executed in the child
                foreach($socketArray AS $socket) {
                        // There more happening here but the essence is this
                        socket_write($socket,$msg,strlen($msg));

                        // TODO : Consider additional forking here for each client. 
                }
                // This is where the signal is fired
                exit(0);
        }

        // If the child process did not exit above, then this code would be
        // executed by both parent and child. In my case, the child will 
        // never reach these commands. 
        $childProcesses[] = $pid;
        // The child process is now occupying the same database 
        // connection as its parent (in my case mysql). We have to
        // reinitialize the parent DB connection in order to continue using it. 
        $db = dbEngine::factory(_dbEngine); 
}

Да... Это соотношение 1:1 комментариев к коду: P

Итак, это выглядело отлично, и я увидел эхо:

Звучит сигнал тревоги! Ребенок 12345 трагически умер!

Однако, когда петля сервера сокетов повторила следующую итерацию, функция socket_select() не выполнила эту ошибку:

PHP Предупреждение: socket_select(): невозможно выбрать [4]: ​​Прерванный системный вызов...

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


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

много кофе, боль глаз и 10 дней спустя...

Бросок барабана пожалуйста

TL & DR - Решение:

Упомянуто здесь в комментарии от 2007 года в документации php сокетов и в этот учебник по stuporglue (поиск "хорошего воспитания" ), можно просто "игнорировать" сигналы, из дочерних процессов (SIGCHLD), передавая SIG_IGN функции pcntl_signal() -

pcntl_signal(SIGCHLD, SIG_IGN);

Цитата из связанного сообщения в блоге:

Если мы игнорируем SIGCHLD, дочерние процессы будут получены автоматически после завершения.

Верьте или нет - я включил эту строку pcntl_signal(), удалил всех других обработчиков и все, что касалось детей, и это сработало! Больше не осталось процессов <defunct>!

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

Ответ 2

Относительно вашего отказа от ответственности - PHP не лучше/хуже, чем многие другие языки для записи сервера. Есть некоторые вещи, которые невозможно сделать (легкие процессы, асинхронные операции ввода-вывода), но они на самом деле не применяются к forking server. Если вы используете OO-код, убедитесь, что у вас включена циклическая проверка ссылок на сборщик мусора.

Как только дочерний процесс завершается, он становится зомби, пока родительский процесс не очистит его. Ваш код, кажется, посылает сигнал KILL каждому ребенку при получении любого сигнала. Он не будет очищать записи процесса. Он завершит процессы, которые не вызвали выход. Чтобы правильно обработать дочерний процесс, вы должны вызвать waitpid (см. Также этот пример на странице руководства pcntl_wait).

Ответ 3

http://www.linuxsa.org.au/tips/zombies.html

Зомби - это мертвые процессы. Вы не можете убить мертвых. Все процессы в конце концов умирают, и когда они это делают, они становятся зомби. Они потребляют почти нет ресурсов, чего можно ожидать, потому что они мертвы! Причиной зомби является то, что родитель (процесс) зомби может получить статус выхода зомби и статистику использования ресурсов. parent сигнализирует операционной системе, что ему больше не нужен зомби используя один из системных вызовов wait().

Когда процесс умирает, его дочерние процессы становятся детьми из процесс номер 1, который является процессом init. Init - это `` always '' ожидая смерти детей, чтобы они не оставались зомби.

Если у вас есть процессы зомби, это значит, что эти зомби не были ожидали от своего родителя (посмотрите на PPID, отображаемый ps -l). Вы имеют три варианта: исправить родительский процесс (заставить его подождать); убей родитель; или жить с ним. Помните, что жить с ним не так сложно потому что зомби занимают не более одной дополнительной строки на выходе от ps.

Ответ 4

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

Если только страница pcntl_fork() связана с posix -setsid() многие из нас обнаружили бы, что решение было так просто лет назад.