PHP中proc_open()执行C++程序时超时终止机制失效问题
问题根源
你的问题核心是阻塞式的fread调用导致超时判断无法执行:当C++程序进入死循环且不输出任何内容时,fread($pipes[1], 1)会一直阻塞等待数据,循环里的超时检查代码根本没机会运行,自然触发不了超时终止。
解决方案
方案1:给管道设置读取超时
给标准输出管道设置超时属性,让fread在指定时间内无数据返回时直接结束阻塞,从而让超时判断代码得以执行。
修改后的关键代码片段:
$process = proc_open($cmd, $descriptorspec, $pipes); // 将超时时间拆分为秒和微秒,适配stream_set_timeout参数 $timeout_sec = (int)$timeout; $timeout_usec = (int)(($timeout - $timeout_sec) * 1000000); stream_set_timeout($pipes[1], $timeout_sec, $timeout_usec); fwrite($pipes[0], $stdin); fclose($pipes[0]); while(!feof($pipes[1])) { // 先检查全局超时 if(microtime(true) > $timer + $timeout) { $TL = true; break; } $ML = max($ML, memory_get_usage()); $chunk = fread($pipes[1], 1); // 检查管道自身是否超时 $stream_meta = stream_get_meta_data($pipes[1]); if($stream_meta['timed_out']) { $TL = true; break; } $stdout .= $chunk; }
方案2:用stream_select实现非阻塞监听
stream_select可以监听管道的可读状态,并设置最大等待时长,彻底避免fread阻塞,是更灵活可靠的方式。
修改后的循环部分代码:
fwrite($pipes[0], $stdin); fclose($pipes[0]); // 设置管道为非阻塞模式 stream_set_blocking($pipes[1], 0); $read = [$pipes[1]]; $write = []; $except = []; while(true) { // 计算剩余超时时间 $remaining = ($timer + $timeout) - microtime(true); if($remaining <= 0) { $TL = true; break; } $remaining_sec = (int)$remaining; $remaining_usec = (int)(($remaining - $remaining_sec) * 1000000); // 监听管道可读事件,超时返回0 $num_changed = stream_select($read, $write, $except, $remaining_sec, $remaining_usec); if($num_changed === false) { // 监听出错,直接跳出 break; } elseif($num_changed === 0) { // 超时触发 $TL = true; break; } else { // 有数据可读,批量读取提升效率 $chunk = fread($pipes[1], 1024); if($chunk === false || feof($pipes[1])) { break; } $stdout .= $chunk; $ML = max($ML, memory_get_usage()); // 重置read数组,因为stream_select会修改它 $read = [$pipes[1]]; } }
额外修正点
- 原代码多次调用
proc_get_status($process),可只调用一次存储结果,减少开销; - 终止进程前先判断进程是否仍在运行,避免无意义的命令执行;
- 别忘了关闭标准错误管道
$pipes[2],避免资源泄漏。
完整修正后的函数(基于stream_select方案):
function execute($cmd, $stdin, $timeout, $memoryout) { $descriptorspec = [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']]; $timer = microtime(true); $TL = false; $ML = 0; $stdout = ''; $process = proc_open($cmd, $descriptorspec, $pipes); if(!$process) { return 'code:RE'; } stream_set_blocking($pipes[1], 0); fwrite($pipes[0], $stdin); fclose($pipes[0]); $read = [$pipes[1]]; $write = []; $except = []; while(true) { $remaining = ($timer + $timeout) - microtime(true); if($remaining <= 0) { $TL = true; break; } $remaining_sec = (int)$remaining; $remaining_usec = (int)(($remaining - $remaining_sec) * 1000000); $num_changed = stream_select($read, $write, $except, $remaining_sec, $remaining_usec); if($num_changed === false || $num_changed === 0) { $TL = $num_changed === 0; break; } $chunk = fread($pipes[1], 1024); if($chunk === false || feof($pipes[1])) { break; } $stdout .= $chunk; $ML = max($ML, memory_get_usage()); $read = [$pipes[1]]; } $status = proc_get_status($process); if(!$status['running']) { if($status['exitcode'] !== 0 && !$TL && $ML <= $memoryout) { $stdout = 'code:RE'; } } else { $pid = $status['pid']; strstr(PHP_OS, 'WIN') ? exec("taskkill /F /T /PID $pid") : exec("kill -9 $pid"); } fclose($pipes[1]); fclose($pipes[2]); proc_close($process); if($TL) $stdout = 'code:TL'; if($ML > $memoryout) $stdout = 'code:ML'; return $stdout; }
内容的提问来源于stack exchange,提问作者Mihail Kostov
相关产品推荐
相关产品推荐

