Comments 9
Самое главное, что следует еще знать про pcntl обработчики, это то что они — не re-entry.
Т.е. если ловить, к примеру, SIGTSTP, чтобы перезапустить демон из консоли, то нельзя писать так:
ибо перезапущенный таким образом демон (новый запущенный экземпляр) уже не сможет получать/обрабатывать сигналы.
Нужно писать так:
первый метод дергать при запуске, а второй — каждый раз в основном цикле демона.
Т.е. если ловить, к примеру, SIGTSTP, чтобы перезапустить демон из консоли, то нельзя писать так:
pcntl_signal(SIGTSTP, function () {
system('screen -d -h 1000 -m /usr/bin/php /home/.../daemon.php');
exit;
});
ибо перезапущенный таким образом демон (новый запущенный экземпляр) уже не сможет получать/обрабатывать сигналы.
Нужно писать так:
protected $__restartNeeded = false;
protected function initSignalHandlers()
{
pcntl_signal(SIGTSTP, function () { $this->__restartNeeded = true; });
}
protected function receiveSignals()
{
pcntl_signal_dispatch();
if ($this->__restartNeeded) {
system('screen -d -h 1000 -m /usr/bin/php /home/.../daemon.php');
exit;
}
}
первый метод дергать при запуске, а второй — каждый раз в основном цикле демона.
+1
При вызове pcntl_signal_dispatch(); обработка сигнала идет постфактум в отличии от использования declare(ticks = 1), поэтому мне не понятно, чем отличаются эти два вариант. Можно ли по подробнее? Так же не понятно, почему новый запущенный экземпляр не сможет получать/обрабатывать сигналы, ведь через system запускается отдельный независимый процесс.
0
не смотря, на то, что «получение» сигналов производится после вызова pcntl_signal_dispatch, функция коллбэка в pcntl_signal является обработчиком сигнала и если из нее убить текущий процесс и начать новый (даже через screen), этот процесс будет считаться вызванным и выполняющимся из обработчика сигнала и не сможет другие сигналы получать, т.к. обработчики не re-entrant (код обработчика сигнала получить другой или такой же сигнал не может).
0
Для меня основная проблема в обработке сигналов в PHP в следующем (впрочем, то же самое в той или иной мере справедливо для Perl, Python, Ruby и скорее всего для всех остальных языков с виртуальной машиной). Вы не можете обрабатывать сигналы, находясь «посередине» вызова какой-нибудь встроенной функции. В качестве простой иллюстрации можно взять функцию fgets.
Рассмотрим 2 варианта использования функции pcntl_signal (PHP 5.3):
Вариант первый. Третий аргумент pcntl_signal (restart_syscalls) установлен в умолчательное значение true:
Вариант второй. Третий аргумент pcntl_signal (restart_syscalls) установлен в false:
На самом деле, во втором случае функция fgets() сначала вернула значение false, и уже потом был вызван мой обработчик. В этом можно убедиться, если убрать exit(0). Тот факт, что она вернула значение по второму нажатию Ctrl+C объясняется логикой, заложенной в функцию fgets() — если покопаться в исходниках, можно найти соответствующий участок кода :).
Для большинства встроенных функций даже это не будет работать: например, mysql_query() и даже mysqli_query() через mysqlnd не возвращают управление, пока не будет получен ответ от сервера:
Все дело в том, что на Си тот же код с mysql_query() будет спокойно работать и будет вызван обработчик сигнала, как только он пришел. Проблема возникает из-за того, что большинство встроенных функций написаны на Си (а не на самом языке :)), поэтому обработка сигналов происходит тоже на уровне кода на Си, который успешно получает сигнал. Но виртуальная машина будет в неконсистентном состоянии, если она передаст управление из какого-то внутреннего кода в пользовательскую функцию. Поэтому все эти языки не вызывают обработку сигнала посередине вызова встроенной функции.
Это может показаться чем-то очень странным и непонятным, но на самом деле простого решения здесь нет, и, боюсь, нормально обрабатывать SIGINT и SIGTERM в этих языках попросту невозможно. Это будет возможно только в том случае, если встроенные функции не будут делать ничего сложного, вроде обработки MySQL запроса, а будут только предоставлять сырой интерфейс к сокетам, а логика работы с ними будет написана на самом языке. Что, конечно же, вряд ли когда-нибудь произойдет, потому что тогда всё будет работать ооочень медленно, да ещё и вряд ли кто-то захочет писать на таком языке, в котором нет никаких стандартных библиотек :).
Рассмотрим 2 варианта использования функции pcntl_signal (PHP 5.3):
Вариант первый. Третий аргумент pcntl_signal (restart_syscalls) установлен в умолчательное значение true:
$ cat test.php
<?php
declare(ticks=1);
dl('pcntl.so');
pcntl_signal(SIGINT, function() {
echo "Stopped!\n";
exit(0);
});
echo "Enter your name: ";
$name = fgets(STDIN);
echo "Your name is $name";
?>
$ php test.php
Enter your name: youROCK^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C # я нажимал Ctrl+C много раз, затем <Enter>
Stopped!
$
Вариант второй. Третий аргумент pcntl_signal (restart_syscalls) установлен в false:
$ cat test-false.php
<?php
declare(ticks=1);
dl('pcntl.so');
pcntl_signal(SIGINT, function() {
echo "Stopped!\n";
exit(0);
}, false);
echo "Enter your name: ";
$name = fgets(STDIN);
echo "Your name is $name";
?>
$ php test-false.php
Enter your name: youROCK^C^CStopped! # я нажал Ctrl+C, потом опять нажал Ctrl+C, и на второй раз оно сработало!
$
На самом деле, во втором случае функция fgets() сначала вернула значение false, и уже потом был вызван мой обработчик. В этом можно убедиться, если убрать exit(0). Тот факт, что она вернула значение по второму нажатию Ctrl+C объясняется логикой, заложенной в функцию fgets() — если покопаться в исходниках, можно найти соответствующий участок кода :).
Для большинства встроенных функций даже это не будет работать: например, mysql_query() и даже mysqli_query() через mysqlnd не возвращают управление, пока не будет получен ответ от сервера:
$ cat test-mysql.php
<?php
declare(ticks=1);
dl('pcntl.so');
mysql_connect('127.0.0.1', 'root', 'root');
pcntl_signal(SIGINT, function() {
echo "Stopped!\n";
exit(0);
}, false);
echo "Performing long query ...";
mysql_query('SELECT SLEEP(100);');
echo "Done\n";
?>
$ php test-mysql.php
Performing long query ...^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^CStopped! # сишная библиотека libmysql продолжает читать из сокета, несмотря на то, что получает код ошибки EINTR от read()
Все дело в том, что на Си тот же код с mysql_query() будет спокойно работать и будет вызван обработчик сигнала, как только он пришел. Проблема возникает из-за того, что большинство встроенных функций написаны на Си (а не на самом языке :)), поэтому обработка сигналов происходит тоже на уровне кода на Си, который успешно получает сигнал. Но виртуальная машина будет в неконсистентном состоянии, если она передаст управление из какого-то внутреннего кода в пользовательскую функцию. Поэтому все эти языки не вызывают обработку сигнала посередине вызова встроенной функции.
Это может показаться чем-то очень странным и непонятным, но на самом деле простого решения здесь нет, и, боюсь, нормально обрабатывать SIGINT и SIGTERM в этих языках попросту невозможно. Это будет возможно только в том случае, если встроенные функции не будут делать ничего сложного, вроде обработки MySQL запроса, а будут только предоставлять сырой интерфейс к сокетам, а логика работы с ними будет написана на самом языке. Что, конечно же, вряд ли когда-нибудь произойдет, потому что тогда всё будет работать ооочень медленно, да ещё и вряд ли кто-то захочет писать на таком языке, в котором нет никаких стандартных библиотек :).
0
imo, то что встроенные функции не обрабатывают сигналы, в чем-то даже плюс, иначе вы никогда не могли бы сказать, на каком этапе сигнал пришел в mysql_query и что уже успело там внутри случиться, а что — нет. В текущей же реализации, всегда можно завершить процесс тогда, когда он до конца обработает какой-то конечный блок логики.
0
В водах статьи я уже написал, что declare(ticks=1); — это зло, и в 5.3 его можно использовать, а вызывать pcntl_signal_dispatch() в начале или в конце итерации основного цикла.
По хорошему итерация основного цикла демона должна проходить не прерываясь, т.к. внутри может идти работа с несколькими разными типами БД, и если прервать итерацию в середине и вызвать exit(), то эти БД будут неконсистентны. Конечно использование pcntl_signal_dispatch() не спасет от kill -9 и консистентность нужно обеспечивать как-то по другому, но зачем усложнять свой код вызовами обработчиков из любого места?
По хорошему итерация основного цикла демона должна проходить не прерываясь, т.к. внутри может идти работа с несколькими разными типами БД, и если прервать итерацию в середине и вызвать exit(), то эти БД будут неконсистентны. Конечно использование pcntl_signal_dispatch() не спасет от kill -9 и консистентность нужно обеспечивать как-то по другому, но зачем усложнять свой код вызовами обработчиков из любого места?
0
На самом деле демонов на PHP почти никто не пишет, зато пишут программы, которые могли бы хотеть что-нибудь за собой прибрать при получении соответствующего сигнала :). Выходит так, что вряд ли это возможно, потому что при перехвате сигналов «зависшая» программа ещё и перестает на эти самые сигналы отвечать, пока не выйдет из «зависшей» встроенной функции.
0
Sign up to leave a comment.
Обработка pcntl-сигналов в PHP