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

排查由Supervisor托管的PHP无限循环脚本冻结问题

问题原因分析及解决方案

核心原因:exec()阻塞等待子进程,子进程未正常退出

你当前的脚本用exec()调用bot:get-updates,但exec()会一直阻塞直到子进程完全退出。如果bot:get-updates命令本身因为以下原因卡住,主进程就会停在该步骤,无法继续循环计数,自然不会触发100次后的终止逻辑:

  • 长轮询请求无超时限制:第三方服务未响应或连接卡住,子进程一直挂在网络请求上,永远不退出。
  • 子进程内部死锁/未处理错误:bot:get-updates存在资源死锁、未捕获的致命异常,导致进程挂起而非退出。
  • 子进程异常终止未被回收:子进程崩溃后变成僵尸进程,exec()无法感知进程结束,一直处于等待状态。

针对性解决办法

1. 给子进程添加强制超时限制

修改exec()调用,给bot:get-updates加上超时约束,确保即使卡住也能强制退出:

$command = "php " . base_path() . "/artisan bot:get-updates";
// Linux环境用timeout命令限制执行时间(示例为5分钟)
exec("timeout 300 $command", $output, $returnVar);

Windows环境可使用系统自带的timeout命令或第三方工具实现类似逻辑,时长根据你的长轮询实际需求调整。

2. 改用非阻塞方式调用子进程

用shell_exec()配合后台执行,让主进程无需等待子进程结束就能继续循环:

$command = "php " . base_path() . "/artisan bot:get-updates > /dev/null 2>&1 &";
shell_exec($command);

注意:这种方式要额外处理进程堆积问题,比如每次循环前检查是否有旧的子进程在运行,避免系统资源耗尽。

3. 修复bot:get-updates内部逻辑

  • 给长轮询请求添加明确的超时参数,以Guzzle为例:
    $client = new GuzzleHttp\Client([
        'timeout' => 60,        // 请求超时60秒
        'connect_timeout' => 10 // 连接超时10秒
    ]);
    
  • 确保子进程内所有异常都被捕获处理,任何错误都能触发进程正常退出,而非挂起。

4. 加入日志监控定位问题

在主循环中添加日志,记录每轮执行的时间和子进程返回码,方便排查哪一步出现异常:

$round = 0;
while ($round < 100) {
    $startTime = time();
    error_log("启动第{$round}轮任务:" . date('Y-m-d H:i:s'));
    
    exec("timeout 300 php " . base_path() . "/artisan bot:get-updates", $output, $returnVar);
    
    error_log("第{$round}轮任务结束,耗时" . (time() - $startTime) . "秒,返回码:{$returnVar}");
    $round++;
}
error_log("循环执行完毕,进程退出");

内容的提问来源于stack exchange,提问作者Мохамед Русланович

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 07:14:58