排查由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,提问作者Мохамед Русланович
相关产品推荐
相关产品推荐

