Могу ли я полагаться на вызов register_shutdown_function() в SIGTERM, если настроен pcntl_signal()?

Я работаю над приложением, которое периодически вызывает фоновые процессы. Один из них вызывался cron, но я ищу что-то более надежное, поэтому конвертирую его для работы под Supervisor. (Вероятно, он будет работать в течение 10 минут, в течение которых он может определить, что нужно сделать, или что он простаивает. После выхода Supervisor автоматически перезапустит чистый экземпляр.)

Поскольку Supervisor лучше обеспечивает параллельную работу только определенного количества экземпляров чего-либо, я могу работать дольше. Однако это означает, что мои процессы с большей вероятностью получат сигналы завершения либо от kill напрямую, либо потому, что они были остановлены через Supervisor. Поэтому я экспериментирую с тем, как справиться с этим в PHP.

Похоже, что основным решением является использование pcntl_signal() вот так :

declare(ticks = 1);
pcntl_signal(SIGTERM, 'signalHandler');
pcntl_signal(SIGINT, 'signalHandler');

function signalHandler($signal) {
    switch($signal) {
        case SIGTERM:
        case SIGINT:
            echo "Exiting now...\n";
            exit();
    }
}

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

Однако похоже, что я могу использовать register_shutdown_function(). Это не перехватывает ^C или другие знаки завершения сами по себе, и руководство по этому поводу совершенно ясно:

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

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

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

Кроме того, могу ли я полагаться на это поведение, когда руководство ставит под сомнение это? Я использую PHP 5.5 и пока не собираюсь обновляться до PHP7. Так что мне было бы интересно, работает ли это на 5.5 и 5.6, на различных дистрибутивах.

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


person halfer    schedule 17.07.2016    source источник
comment
Мне просто интересно, почему вам вообще нужно перезапускать процессы PHP (учитывая, что у вас нет утечки памяти случайным образом ;-))?   -  person bwoebi    schedule 17.08.2016
comment
Просто хорошая практика, @bwoebi - это дает мне уверенность, что всегда есть свежий экземпляр, даже если PHP и моя система полностью защищены от утечек :-).   -  person halfer    schedule 17.08.2016
comment
Я не согласен, что это действительно хорошая практика. Для вашего варианта использования это может быть хорошо, но как только вам нужен постоянно доступный сокет [TCP/unix/...] (например, для вывода информации из живой программы), это плохо, когда он периодически недоступен.   -  person bwoebi    schedule 17.08.2016
comment
О да, он ничего не слушает. Это веб-скрейпер, поэтому соединение уходит, а не входит.   -  person halfer    schedule 17.08.2016
comment
@BenjaminBasmaci: я принял ваше редактирование с небольшой модификацией. Однако позвольте мне отговорить вас от внесения ответных правок. Для этого есть две причины: (а) ваша работа по редактированию все еще должна пройти через очередь проверки, поэтому вы потенциально можете создать большое количество небольших правок, что может замедлить утверждение более важных правок; (б) возмездие не является идеальной мотивацией для редактирования - это должно быть исключительно для поддержания качества, технического письма.   -  person halfer    schedule 28.08.2020
comment
Тем не менее, если вы хотите участвовать в редактировании в целом, я бы не стал препятствовать этому. Особенно после того, как вы достигнете порога автоматического одобрения, нам не помешает еще несколько редакторов. Нам и так не хватает, учитывая количество материалов LQ, которые приходят каждый день.   -  person halfer    schedule 28.08.2020


Ответы (2)


Функции завершения работы пользователя PHP вызываются во время обычного завершения процесса, что несколько аналогично функциям, зарегистрированным с помощью atexit в других языках программирования. Явный exit или неявный выход в конце скрипта является обычным завершением процесса.

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

Когда вы перехватываете SIGTERM с помощью pcntl_signal, вы устанавливаете другое поведение и даете себе возможность испытать обычное завершение процесса.

Да, на такое поведение можно положиться. Хотя основная запись в руководстве не является явной, остальная часть «Примечания», которое вы цитируете, гласит:

[Вы] можете использовать pcntl_signal() для установки обработчика SIGTERM, который использует exit() для корректного завершения.

person pilcrow    schedule 17.08.2016
comment
Хорошо, спасибо за это. Да, я видел это примечание, но я прочитал его по-другому - функции завершения работы не будут выполняться, если процесс будет убит, это довольно явно, но, возможно, это просто плохая формулировка. Последняя часть примечания, похоже, подразумевает, что следует использовать обработчик сигнала вместо, а не в дополнение к обработчику выключения. Ваша интерпретация заставила бы меня поверить, что на это поведение можно положиться и в PHP 7 - это звучит правильно для вас? - person halfer; 17.08.2016

В дополнение к pilcrows правильный ответ:

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

Вы можете использовать обработчик сигнала, чтобы просто выполнить аварийную очистку, а затем вызвать posix_kill(getmypid(), SIGKILL); для принудительного завершения без очистки. Если вы exit() здесь, обычно инициируется последовательность выключения (т. е. вызов всех деструкторов, обработчиков выключения и т. д.).

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

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

register_shutdown_function(function() { $GLOBALS["#__in_shutdown"] = 1; });

и проверьте exit():

if (empty($GLOBALS["#__in_shutdown"])) {
    exit;
}

На всякий случай, если вы действительно хотите быть в безопасности, если кто-то не запустит на нем SIGKILL.

Обратите внимание, что 7.0 или 7.1 не изменят это поведение; все изменения в 7.1 declare(ticks=1); лишние, демонстрируемое поведение остается прежним.

person bwoebi    schedule 17.08.2016
comment
Хорошо, спасибо. Что касается замечания о добавлении, я, вероятно, плохо сформулировал его - я имел в виду, что оба необходимы, если кто-то явно хочет использовать обработчики выключения, чтобы получить несколько обратных вызовов в ситуации sigterm/sigint. - person halfer; 17.08.2016
comment
Чтобы аккуратно закрыть каждую часть моей системы, я бы использовал фиктивный обработчик sig, как в исходном посте, в корневом сценарии, а затем ненулевое количество обработчиков выключения, настроенных в различных классах, и ни один из обработчики выключения сами вызовут exit(), оставив это обработчику sig. Это звучит как хороший подход? - person halfer; 17.08.2016
comment
Я имел в виду добавление if() вокруг exit() в обработчике сигнала, чтобы получение SIGTERM, когда вы уже находитесь в последовательности выключения, не прерывало ее. В остальном да, звучит нормально. - person bwoebi; 17.08.2016