О pcntl_signal() и открытых сокетах

// Апрель 22nd, 2013 // Асинхронное программирование, Веб-разработка

tuxСейчас пишу асинхронный PHP-демон и довольно глубоко пришлось погрузиться в библиотеку pcntl, а в частности в её функции работы с сигналами. Надо заметить, что касается написания всяких серверных долгоживущих штук — то там вообще всё очень интересно, и, на первый взгляд, совсем не очевидно.

Интро

Представим себе что у нас есть демон, который слушает сокет в неблокирующем режиме. Например, если мы пишем веб-сервер на php (хотя это из области фантастики и быдлокодинга), то он будет слушать 80 порт. Соответственно у него будут некие методы onOpen(), onClose(), onMessage(). Каждый из них — это обработчик открытия, закрытия соединения и получения сообщения. Кроме того, наш сервер умеет обрабатывать консольные сочетания клавиш (Ctrl+C), на которые забиндена shutdown_function().

register_shutdown_function(function endScript() use ($importVar) {
echo "My little Script has finished";
});

А ещё он умеет ловить POSIX сигналы (SIGHUP) и обрабатывать их.

// tick use required as of PHP 4.3.0
declare(ticks = 1);
// signal handler function
function sig_handler($signo)
{
switch ($signo) {
case SIGTERM:
// handle shutdown tasks
exit;
break;
case SIGHUP:
// handle restart tasks
break;
case SIGUSR1:
echo "Caught SIGUSR1...\n";
break;
default:
// handle all other signals
}

echo "Installing signal handler...\n";

// setup signal handlers
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGHUP, "sig_handler");
pcntl_signal(SIGUSR1, "sig_handler");

daemon

Проблема

Вроде бы, с первого взгляда всё нормально. Однако потом мы замечаем, что в некоторый момент времени наш обработчик sig_handler перестаёт вызываться, хотя мы и послали ему сигнал.

kill -s SIGHUP 25581

> ps aux | grep php

andrey 25581 0.3 0.1 516856 47824 ? S 15:37 0:45 php /home/site.ru/web/app/console server
andrey 32496 0.0 0.0 109396 928 pts/8 S+ 19:11 0:00 grep --color=auto php

Как воспроизвести:
1. Запускаем скрипт, который устанавливает коллбеки с помощью pcntl_signal() на SIGTERM, а потом в бесконечном цикле слушает сокет
2. Делаем ему kill -s SIGTERM {pid}
3. Он живет и не умирает!
4. Делаем коннект к сокету, он открывает соединение, и только тут срабатывает коллбек из п.1, сигнал по которому уже давно послан в п.2

Чтобы проверить этот баг можно воспользоваться вот этим скриптом.

Решение

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

Be aware signal handler functions set with pcntl_signal are not called while a socket is blocking waiting 
for a connection; the signal is absorbed silently and the handler called when a connection is made. Process handling is not 
available when using a blocking socket! Bear this in mind.

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

Ссылки

http://php.net/manual/en/function.socket-accept.php
http://grokbase.com/t/php/php-bugs/0236e2jbj8/bug-15906-pcntl-signal-does-not-work-while-waiting-with-socket-accept
http://highloadblog.ru/articles/9.html
http://pecl.php.net/package-changelog.php?package=libevent&release=0.0.4
http://phpclub.ru/talk/threads/pcntl_signal-%D0%B8-libevent.66402/
http://grokbase.com/t/php/php-bugs/0236e2jbj8/bug-15906-pcntl-signal-does-not-work-while-waiting-with-socket-accept

Share

Спасибо!


Если вам помогла статья, или вы хотите поддержать мои исследования и блог - вот лучший способ сделать это:


2 Responses to “О pcntl_signal() и открытых сокетах”

  1. Slava:

    libevent вам в помощь.

Комментировать