Выполнение программы из PHP зависает APACHE

Здравствуйте и заранее спасибо за проявленный интерес.

В течение последних двух недель я боролся с чем-то, что сводило меня с ума. У меня установлены APACHE (2.2.22) и PHP (5.4.3) на моем компьютере с Windows, и я пытаюсь вызвать программу из PHP-скрипта, который одновременно вызывает другую программу. Обе программы написаны на C/C++ и скомпилированы с помощью MINGW32. Что касается версии Windows, я протестировал Windows 2003 Server и Windows 7 Professional, и обе они вызывают одинаковые проблемы.

Позвольте мне представить эти две программы:

1) mytask.exe: это программа, которая должна выполняться в фоновом режиме и периодически записывает свой статус в файл.

2) job.exe: это программа, которую я хочу вызвать из PHP-скрипта. Его цель — создать mytask.exe как независимый процесс (а не как поток).

Если я запускаю из окна консоли приведенную ниже команду, то job.exe немедленно возвращается и оставляет mytask.exe работающим в фоновом режиме до тех пор, пока он не завершится.

> job.exe spawn mytask.exe
jobid=18874111458879FED

Обратите внимание, что job.exe сбрасывает идентификатор, который используется для управления mytask.exe. Например:

> job.exe status 18874111458879FED
RUNNING

Я проверил, что если я запускаю первую команду из скрипта PHP, скрипт PHP произвольно блокируется навсегда. Если я загляну в диспетчер задач Windows, то увижу, что job.exe находится в зомби-подобном состоянии. Я могу утверждать, что job.exe эффективно достигает обычного оператора return 0; в своей подпрограмме main(), так что это кажется чем-то скрытым в среде выполнения C. Кроме того, если я напишу простой mytask.exe, который просто спит в течение 10 секунд, то PHP-скрипт также блокируется на 10 секунд (или блокируется навсегда, следуя случайному поведению, о котором я только что упомянул). Другими словами, у меня нет способа заставить job.exe порождать процесс, не дожидаясь его завершения, когда я вызываю job.exe из PHP-скрипта.

Итак: есть что-то, что я делаю неправильно, когда запускаю mytask.exe, и теперь наступает вторая часть этого отступления.

Я использую функцию WINAPI CreateProcess() для порождения задач из job.exe. Что касается документации MSDN, я вызываю CreateProcess с bInheritHandles = FALSE, чтобы дочерний процесс не приводил к взаимоблокировкам ввода-вывода с PHP-скриптом. Я также закрываю дескрипторы процессов, возвращаемые функцией CreateProcess() в структуре PROCESS_INFORMATION. Единственное, чего я не делаю, так это жду окончания процесса. С другой стороны, что касается стороны PHP, я безуспешно пробовал функции exec() и proc_open() PHP для вызова job.exe.

Мои последние наблюдения, однако, кажутся правильными, но они меня не убеждают, потому что я не понимаю, почему они как-то работают. Дело в том, что если mytask.exe перед сном делает fclose(stdout), то PHP-скрипт сразу возвращается. НО КАК??? Я сказал CreateProcess() не наследовать дескрипторы, так почему я получаю такие результаты? В любом случае, я не могу придерживаться этого патча, потому что программы, запускаемые job.exe, могут не знать о том, кто их вызывает, поэтому закрытие stdout для этих программ не является хорошим решением. В UNIX все так просто... Вы просто вызываете fork(), закрываете стандартные потоки и затем вызываете execve для вызова программы. В Windows я также пытался создать поток-оболочку с помощью CreateThread() (для эмуляции fork()), а затем вызвать CreateProcess() из этого потока после закрытия стандартных потоков... но это также закрыло потоки job.exe !

Весь этот вопрос можно было бы синтезировать в один: как мне выполнить из PHP программу, которая создает другие процессы?

Я надеюсь, что кто-то может пролить свет на этот вопрос... Большое спасибо!


person Claudix    schedule 03.12.2012    source источник


Ответы (1)


Я думаю, что прибил решение, которое разделено на две части:

1) Что касается того факта, что основной процесс останавливается до тех пор, пока не завершится дочерний процесс.

Согласно документации MSDN, это определение CreateProcess():

BOOL WINAPI CreateProcess(
  _In_opt_     LPCTSTR lpApplicationName,
  _Inout_opt_  LPTSTR lpCommandLine,
  _In_opt_     LPSECURITY_ATTRIBUTES lpProcessAttributes,
  _In_opt_     LPSECURITY_ATTRIBUTES lpThreadAttributes,
  _In_         BOOL bInheritHandles,
  _In_         DWORD dwCreationFlags,
  _In_opt_     LPVOID lpEnvironment,
  _In_opt_     LPCTSTR lpCurrentDirectory,
  _In_         LPSTARTUPINFO lpStartupInfo,
  _Out_        LPPROCESS_INFORMATION lpProcessInformation
);

Как я сказал в своем вопросе, я передаю FALSE в bInheritHandles, но я также передал 0 в dwCreationFlags. После небольшого дополнительного исследования я обнаружил, что есть флаг с именем DETACHED_PROCESS, для которого MSDN говорит:

For console processes, the new process does not inherit its parent's console (the default). The new process can call the AllocConsole function at a later time to create a console. For more information, see Creation of a Console.

Теперь job.exe возвращается немедленно, несмотря на то, что дочерний процесс продолжает свое выполнение.

2) Что касается того факта, что PHP-скрипт случайным образом зависает при вызове exec()

Кажется, это ошибка PHP. Выполнение функций семейства exec() в контексте сеанса PHP может привести к случайному зависанию APACHE, что необходимо для перезапуска сервера. Я нашел в Интернете тред, в котором пользователь заметил, что закрытие сеанса ( через session_write_close()) перед вызовом exec() предотвратит зависание скрипта. То же самое относится и к функциям proc_open/proc_close. Итак, мой скрипт теперь выглядит так:

session_write_close();  //Close the session before proc_open()
$proc = proc_open($cmd,$pipedesc,$pipes);
//do stuff with pipes... 
//... and close pipes
$retval = proc_close($proc);
session_start(); //restore session

Надеюсь это поможет.

person Claudix    schedule 04.12.2012