从 PHP 执行程序会挂起 APACHE

Posted

技术标签:

【中文标题】从 PHP 执行程序会挂起 APACHE【英文标题】:Executing a program from PHP hangs APACHE 【发布时间】:2012-12-03 19:44:38 【问题描述】:

您好,提前感谢您的关注。

在过去的两周里,我一直在为一些让我发疯的事情苦苦挣扎。我在我的 Windows 机器上安装了 APACHE (2.2.22) 和 php (5.4.3),我试图从同时调用另一个程序的 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 在其main() 例程中有效地达到了通常的return 0; 语句,因此它似乎是在C 运行时中的木头下的东西。 此外,如果我编写一个简单的 mytask.exe,它只是休眠 10 秒,那么 PHP 脚本也会阻塞 10 秒(或者在我刚刚提到的随机行为之后永远阻塞)。换句话说,当我从 PHP 脚本调用 job.exe 时,我无法让 job.exe 生成一个进程而不等待它结束。

所以:在生成 mytask.exe 时我做错了,现在,这是题外话的第二部分。

我使用 WINAPI 函数 CreateProcess() 从 job.exe 生成任务。在 MSDN 文档中,我使用 bInheritHandles = FALSE 调用 CreateProcess,以避免子进程使用 PHP 脚本产生 I/O 死锁。我还关闭了 PROCESS_INFORMATION 结构中 CreateProcess() 返回的进程句柄。我唯一不做的就是等待过程结束。另一方面,关于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 中执行一个创建其他进程的程序?

我希望有人能对这个问题有所了解...非常感谢!

【问题讨论】:

【参考方案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对此表示:

对于控制台进程,新进程不会继承其父控制台(默认)。新进程可以稍后调用 AllocConsole 函数来创建控制台。有关详细信息,请参阅创建控制台。

现在,尽管子进程继续执行,但 job.exe 会立即返回。

2)关于调用exec()时PHP脚本随机挂起的事实

这似乎是 PHP 的一个错误。在 PHP 会话的上下文中运行 exec() 系列函数可能会使 APACHE 随机挂起,这是重新启动服务器所必需的。我发现了一个thread in the Internet,其中用户注意到在调用exec() 之前关闭会话(通过session_write_close())会阻止脚本挂起。这同样适用于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

希望这会有所帮助。

【讨论】:

以上是关于从 PHP 执行程序会挂起 APACHE的主要内容,如果未能解决你的问题,请参考以下文章

为啥在由 Apache 运行时,来自 Google Secret Manager API 的 gRPC 调用会挂起?

iPhone 应用程序在从后台进入前台时会挂起/阻止 UI 几秒钟

创建 PHP 会话变量会挂起我的浏览器

为啥当我尝试读取管道时管道会挂起?

在服务中删除和创建性能计数器时,它可能会挂起该服务

缩放 Azure 角色时,实例化 DCOM 对象有时会挂起