Завершение дочерних процессов zombie, разветвленных с сервера сокетов

отказ от ответственности

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

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

моя главная проблема прямо сейчас работа / обработка с коллекцией этих мертвых / зомби-процессов ребенка и их прекращение. Я прочитал (снова и снова) соответствующие страницы руководства PHP для pcntl_fork () и понимают, что родительский процесс отвечает за очистку своих детей. Родительский процесс получает сигнал от своего ребенка, когда ребенок выполняет 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]);
    }
  }
}

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

posix_kill($childPid, 9)

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

взято с man страницы posix_kill :

возвращает TRUE on успех или ложь в случае неудачи.


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

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

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

мои планы на зомби-апокалипсис скала -
но что я могу сделать, когда даже ... --10--> не убивает процессы ребенка зомби?


обновление 10 дней спустя

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

4 ответов


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

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

С @ sym совет, и читать больше в документацию и комментарии к документации, в pcntl_waitpid() описание государства :

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

поэтому я настраиваю свой 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's 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's DB connection in order to continue using it. 
        $db = dbEngine::factory(_dbEngine); 
}

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

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

звуковой сигнал! 12345 ребенок трагически погиб!

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

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


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

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

барабанная дробь пожалуйста!--39-->

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

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

pcntl_signal(SIGCHLD, SIG_IGN);

цитирую, что связано блог :

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

Верьте или нет - я включил это pcntl_signal() line, удалил все другие обработчики и вещи, связанные с детьми, и это сработало! Больше их не было!--10--> процессы остались висеть вокруг!

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


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

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


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

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

когда процесс умирает, его дочерние процессы все становятся детьми процесс № 1, который является процессом инициализации. Инит `всегда" ожидание смерти детей, чтобы они не оставались зомби.

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


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

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