【问题标题】:Does PHP proc_open block the Web request?PHP proc_open 会阻止 Web 请求吗?
【发布时间】:2011-10-29 10:35:01
【问题描述】:

默认情况下,在 Linux 上,通过 proc_open() 创建进程是否会使 PHP 脚本在生成的进程终止之前不会终止?我不想这样,我马上关闭进程句柄。

proc_open 本身不会阻塞,这很清楚。但是整体 HTTP 请求执行情况如何?

【问题讨论】:

  • 不,proc_open() 不会阻止任何东西。但是这里有一些问题: proc_close() 可能无法关闭进程并阻止您的 PHP 应用程序。如果是这种情况,请尝试在尝试 proc_close() 之前关闭所有打开的管道或/并至少使用 proc_terminate() + proc_get_status() 进行调试 - 它们不会阻止脚本的执行。总体 HTTP 请求执行取决于您使用的 Web 服务器 - 可以在所有 PHP 的子进程终止之前阻止请求,但至少 Apache/Nginx 不会这样做。
  • 德克萨斯州。管道不是问题 - 我没有通过任何管道。
  • 那你为什么使用 proc_open 而不是 exec/system/passthru 等?他们的行为更加透明。
  • passthru() 将输出传递到 HTTP 响应中——我不希望这样。 Exec() 和 system() 等到进程完成。与此同时,我想启动一个可能需要一段时间(大约 1 分钟)的后台进程,然后继续 HTTP 处理。如果该进程比当前的 HTTP 请求更长,那就这样吧。
  • 不,exec() 和 system() 仅在您不在后台运行时才等待进程完成 - 尝试 exec("top -b -n 1000 > /dev/null 2> / dev/null &");

标签: php process


【解决方案1】:

周末有空,所以对 *nix 系统上的 proc_open() 做了一点研究。

虽然 proc_open() 不会阻止 PHP 的脚本执行,即使 shell 脚本没有在后台运行,但如果您不自己调用 PHP 的脚本完全执行后,PHP 会自动调用 proc_close()。所以,我们可以想象在脚本的末尾总是有一行 proc_close() 。

问题在于不明显但符合逻辑的 proc_close() 行为。假设我们有一个像这样的脚本:

$proc = proc_open('top -b -n 10000',
                array(
                    array('pipe', 'r'),
                    array('pipe', 'w')),
                $pipes);
//Process some data outputted by our script, but not all data
echo fread($pipes[1],100);
//Don't wait till scipt execution ended - exit
//close pipes   
array_map('fclose',$pipes);
//close process
proc_close($proc);

奇怪,proc_close() 会在 shell 脚本执行结束之前等待,但我们的脚本很快就被终止了。之所以会发生,是因为我们关闭了管道(如果我们忘记了,PHP 似乎会默默地执行此操作),因此一旦该脚本尝试向已经不存在的管道写入内容 - 它就会出错并终止。

现在,让我们尝试不使用管道(嗯,会有,但他们将使用当前的 tty,没有任何 PHP 链接):

$proc = proc_open("top -b -n 10000", array(), $pipes);
proc_close($proc);

现在,我们的 PHP 脚本正在等待我们的 shell 脚本结束。我们能避免吗?幸运的是 PHP 使用

生成 shell 脚本
sh -c 'shell_script'

所以,我们可以直接杀死 sh 进程并让我们的脚本继续运行:

$proc = proc_open("top -b -n 10000", array(), $pipes);
$proc_status=proc_get_status($proc);
exec('kill -9 '.$proc_status['pid']);
proc_close($proc);

当然,我们可以在后台运行该进程,例如:

$proc = proc_open("top -b -n 10000 &", array(), $pipes);
proc_close($proc);

并没有任何问题,但是这个特性给我们带来了最复杂的问题:我们可以运行一个进程,使用 proc_open() 读取一些输出,然后强制进程进入后台吗?嗯,在某种程度上 - 是的。

这里的主要问题是管道:我们不能关闭它们,否则我们的进程将会死掉,但是我们需要它们从该进程中读取一些有用的数据。事实证明,我们可以在这里使用一个魔术 - gdb。

首先,在某处创建一个文件(在我的示例中为 /usr/share/gdb_null_descr),其内容如下:

p dup2(open("/dev/null",0),1)
p dup2(open("/dev/null",0),2)

它会告诉 gdb 将描述符 1 和 2(嗯,它们通常是 stdout 和 stderr)更改为新的文件处理程序(本例中为 /dev/null,但您可以更改它)。

现在,最后一件事:确保 gdb 可以连接到其他正在运行的进程 - 它在某些系统上是默认设置,但例如在 ubuntu 10.10 上,如果您是,则必须将 /proc/sys/kernel/yama/ptrace_scope 设置为 0不要以 root 身份运行它。

享受:

$proc = proc_open('top -b -n 10000',
                array(
                    array('pipe', 'r'),
                    array('pipe', 'w'),
                    array('pipe', 'w')),
                $pipes);
//Process some data outputted by our script, but not all data
echo fread($pipes[1],100);

$proc_status=proc_get_status($proc);

//Find real pid of our process(we need to go down one step in process tree)
$pid=trim(exec('ps h -o pid  --ppid '.$proc_status['pid']));

//Kill parent sh process
exec('kill -s 9 '.$proc_status['pid']);

//Change stdin/stdout handlers in our process
exec('gdb -p '.$pid.' --batch -x /usr/share/gdb_null_descr');

array_map('fclose',$pipes);
proc_close($proc);

编辑:我忘了提到 PHP 不会立即运行你的 shell 脚本,所以你必须稍等片刻才能执行其他 shell 命令,但通常它足够快(或者 PHP 足够慢)而且我懒得将检查添加到我的示例中。

【讨论】:

  • 你能解释一下这个短语是什么意思吗? > PHP 不会立即运行您的 shell 脚本,因此您必须稍等片刻才能执行其他 shell 命令
  • 当您执行“proc_open(SHELL_CMD); runPhpFunction();”之类的操作时在 PHP 中,runPhpFunction() 可以在 SHELL_CMD 实际开始在系统中运行之前执行。虽然 proc_open() 似乎是同步的,因为它返回 SHELL_CMD 的 PID(这在 SHELL_CMD 启动之前是不可能的),但这实际上不是您的 SHELL_CMD 的 PID,它是 SHELL(/bin/sh 或其他)的 PI​​D SHELL_CMD 将在其中执行,并且 SHELL 实际执行 SHELL_CMD 可能需要一些时间(并因此导致竞争条件),但大多数情况下它足够快以至于它似乎按顺序运行。
【解决方案2】:

我遇到了类似的问题,写了一个小脚本来处理它:

https://github.com/peeter-tomberg/php-shell-executer

我所做的是后台进程,并且仍然可以访问后台进程的结果(stderr 和 stdout)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-24
    • 2015-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-18
    相关资源
    最近更新 更多