Запуск задания в фоновом режиме из Perl БЕЗ ожидания возврата

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

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

Контекст

Позвольте мне более четко определить проблему: я пишу приложение для рассылки новостей, в котором я хочу, чтобы фактический процесс отправки был асинхронным. Например, пользователь нажимает «отправить», запрос немедленно возвращается, а затем он может проверить ход выполнения на определенной странице (например, с помощью AJAX). Он написан в вашем традиционном стеке LAMP.

В конкретном хосте, который я использую, функции PHP exec() и system() отключены по соображениям безопасности, а системные функции Perl (exec, system и обратные кавычки) — нет. Таким образом, мое обходное решение заключалось в создании сценария «триггера» на Perl, который вызывает фактического отправителя через интерфейс командной строки PHP и перенаправляет на страницу прогресса.

Где я застрял

Сама строка, по которой звонит отправитель, на данный момент:

system("php -q sender.php &");

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

Что я уже пробовал

  • Функция Perl exec() — тот же результат, что и system().
  • Изменение команды на: «php -q sender.php | at now»), надеясь, что демон «at» вернется и что сам процесс PHP не будет привязан к Perl.
  • Выполнение команды «косвенно»: «/bin/sh -c 'php -q sender.php &'» — все еще ждет завершения отправки sender.php.
  • fork() запускает процесс и выполняет системный вызов в дочернем (надеюсь, отсоединенном процессе) - тот же результат, что и выше

Моя тестовая среда

Просто чтобы быть уверенным, что я не упустил ничего очевидного, я создал скрипт sleeper.php, который спит всего пять секунд перед выходом. И вот такой скрипт test.cgi дословно:

#!/usr/local/bin/perl
system("php sleeper.php &");
print "Content-type: text/html\n\ndone";

Что мне попробовать сейчас?


person Rafael Almeida    schedule 15.04.2010    source источник


Ответы (5)


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

Вы можете легко сделать это с помощью модуля CPAN Proc::Daemon:

use Proc::Daemon;
# do everything you need to do before forking the child...

# make into daemon; closes all open fds
Proc::Daemon::Init();
person Ether    schedule 15.04.2010

Иногда STDERR и STDOUT также могут блокировать систему... Чтобы получить оба, я использую (для большинства сред оболочки (bash, csh и т. д.), которые я использую...):

system("php sender.php > /dev/null 2>&1 &");
person SirGCal    schedule 01.06.2010
comment
Вы правы, в некоторых случаях эта деталь тоже может быть решающей =) - person Rafael Almeida; 09.06.2010

Используйте fork(), а затем вызовите system в дочернем процессе.

my $pid = fork();
if (defined $pid && $pid == 0) {
    # child
    system($command);    # or exec($command)
    exit 0;
}
# parent
# ... continue ...
person mob    schedule 15.04.2010
comment
exec (все еще сопровождаемый выходом) был бы правильным выбором в форке. - person Hasturkun; 15.04.2010
comment
Попробовал это сейчас, но основной скрипт также не завершается до тех пор, пока не завершится вызываемый. - person Rafael Almeida; 15.04.2010
comment
просто для завершения: добавьте close STDIN;close STDOUT; перед вызовом system($command);, и асинхронный процесс не будет поддерживать соединение веб-страницы - person Atul Gupta; 01.12.2013

Другой вариант — настроить сервер gearman и рабочий процесс (или процессы), которые отправляют электронную почту. Таким образом, вы контролируете, сколько электронных писем происходит одновременно, и нет необходимости в разветвлении. Клиент (ваша программа) может добавить задачу на сервер gearman (в фоновом режиме, не дожидаясь результата, если это необходимо), и задачи ставятся в очередь до тех пор, пока сервер не передаст задачу доступному работнику. Для Gearman есть API-интерфейсы perl и php, так что это очень удобно.

person runrig    schedule 15.04.2010

Удалось решить проблему. По-видимому, то, что мешало ему вернуться, заключалось в том, что вызов отправителя таким образом не отключил стандартный вывод. Итак, решение заключалось в простом изменении системного вызова на:

system("php sender.php > /dev/null &");

Спасибо всем за помощь. На самом деле, когда я читал всю историю о «демонизации» процесса, мне пришла в голову идея отключить стандартный вывод.

person Rafael Almeida    schedule 15.04.2010
comment
Я бы не рекомендовал использовать фоновую оболочку для решения этой проблемы - она ​​зависит от вашей реализации оболочки и не особенно переносима. fork и exec - гораздо более чистое решение, ИМХО. - person friedo; 15.04.2010
comment
Я понимаю. Но в данном случае простота является главным ограничением. Я знаю, что метод fork/exec должен работать сразу (и я отметил его для будущего использования), но на данный момент я использую первое простейшее решение, которое работает =) - person Rafael Almeida; 16.04.2010