You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 10:30:44