laravel 中 while(true) 超时是因为 web 请求是短生命周期,受 php max_execution_time(默认30秒)、nginx fastcgi_read_timeout等多层限制;正确做法是将循环逻辑移至队列任务,并配置 $timeout、retry_after 及 supervisor 管理。

为什么 Laravel 里写 while(true) 就超时
因为 Web 请求本质是短生命周期,PHP-FPM worker 处理完就回收,max_execution_time 默认 30 秒,while(true) 不加退出条件和 sleep,要么立刻超时,要么卡死 worker。这不是 Laravel 的限制,而是 PHP SAPI 模式和反向代理(Nginx/Apache)共同决定的。
常见错误现象:Fatal error: Maximum execution time of 30 seconds exceeded;或 Nginx 返回 504 Gateway Timeout,其实 PHP 还没超时,网关先断了。
- Web 路由里禁用超时(
ini_set('max_execution_time', '0'))只是掩耳盗铃,FPM worker 被长期占用,并发能力归零 -
sleep()不会触发 PHP 超时计时器重置,但会阻塞整个进程,无法响应其他请求 - Nginx 的
fastcgi_read_timeout、Apache 的Timeout、Cloudflare 的 100 秒硬限制,都比 PHP 层更早生效
Laravel 队列才是循环逻辑的正确载体
真正需要“循环执行”的任务(比如轮询状态、监听事件、定时拉取),不该放在 Web 路由里,而应拆成可终止、可重试、可监控的队列任务。Laravel 队列 worker 是常驻进程,天然支持周期性调度。
实操建议:
- 用
php artisan queue:work --daemon(Laravel 8+ 已弃用,改用--once+ Supervisor 循环启停)或 Supervisor 管理 worker 生命周期 - 任务内用
sleep(5)或usleep(500000)控制节奏,但必须配protected $timeout = 120,防止某次循环卡死 - 每次循环开头检查退出信号(如数据库标记、文件存在性),避免无限运行
- 别在任务里写
while(true),改用for ($i = 0; $i + 显式 break 条件,便于测试和压测
HTTP 调用卡住才是循环超时的真凶
90% 的“循环超时”实际是某次 Http::get() 或 curl_exec() 阻塞了整个任务。同步 HTTP 客户端在 FPM 下会挂起 worker,哪怕只等 3 秒,也等于浪费一个并发连接。
关键参数必须显式设:
-
Http::timeout(10)->connectTimeout(5)->get($url):分离连接超时与读取超时 - 避免用
file_get_contents(),它不支持细粒度超时控制 - 外部 API 响应慢?加
retry(2, 1000)(重试 2 次,间隔 1 秒),别靠延长单次 timeout - 高频轮询?改用长连接或 Server-Sent Events(SSE),而不是每秒发一次 GET
别信 set_time_limit(0),它在队列里根本不起作用
set_time_limit() 在 queue:work 常驻进程中会被反复重置,对单个任务无效。你写 set_time_limit(0),只是让当前任务片段“看起来没超时”,但 worker 进程仍受 --timeout 和 retry_after 约束。
真正生效的只有两处:
- 任务类里的
protected $timeout = 90 - 启动命令的
php artisan queue:work --timeout=90 - 对应驱动的
retry_after ≥ $timeout + 10(例如 Redis 驱动在config/queue.php中设为100)
如果任务里有不可中断的系统调用(比如没设超时的 cURL、大数组 array_filter()),唯一可靠的方式是用 pcntl_alarm() + SIGALRM——但这仅限 CLI 环境,且需确认 pcntl 扩展已启用。











