frankenphp_handle_request() 返回 false 时必须主动 break,否则 worker 会卡在阻塞等待状态;退出前需清空输出缓冲、关闭数据库连接;gc_collect_cycles() 应在每次请求回调末尾立即调用。

frankenphp_handle_request() 返回 false 时必须主动 break
FrankenPHP 的 Worker 请求循环不是靠 while(true) 自动终止的,而是由 frankenphp_handle_request() 的返回值控制。它在客户端断连、超时或内部错误时返回 false,但如果你忽略这个返回值继续循环,Worker 会卡在阻塞等待状态,表现为进程不退出、netstat 显示大量 CLOSE_WAIT 或 FIN_WAIT2 连接。
正确做法是在每次调用后检查返回值:
- 用
if (!$keepRunning) { break; }显式跳出循环,否则 Worker 永远不会自然结束 - 不要写成
while (frankenphp_handle_request($cb)) { ... }—— 这种写法在首次失败后就直接退出循环,漏掉清理逻辑 - 若使用
ignore_user_abort(true),更要依赖返回值判断,不能假设连接始终可用
退出前必须清空输出缓冲和关闭连接
Worker 进程退出前若残留 ob_start() 缓冲或未关闭的数据库连接,会导致下一次启动失败或资源泄漏。这不是“请求间污染”,而是进程级残留。
常见错误现象:ob_get_contents() 在 exit 前返回上一个请求的内容;PDOException: SQLSTATE[HY000] [2002] Connection refused 频发;lsof -p $PID | wc -l 显示句柄数持续上涨。
退出前应执行:
- 调用
ob_end_clean()或ob_end_flush(),确保无未发送缓冲 - 显式设
$pdo = null,对MySQLi调用$mysqli->close() - 若用了 Laravel 的
DB::connection(),需加DB::purge() - 避免在
register_shutdown_function()中做这些操作——Worker 生命周期中它不可靠
别让 gc_collect_cycles() 挤占退出时机
gc_collect_cycles() 是强制触发 PHP 垃圾回收的唯一可靠方式,但它不是“退出前才要做的事”,而是“每次请求回调结束后立刻做”的事。如果只在循环末尾或 exit 前调用,内存早已堆积,OOM 风险极高。
关键点在于时机:
- 必须放在
frankenphp_handle_request()回调函数的**最后一行**,而不是 Worker 主循环的末尾 - 不能合并到“退出逻辑”里——Worker 可能因信号(如
SIGTERM)被强制终止,此时回调根本不会执行完 - 配合
gc_enable()开头启用(CLI 模式下默认开启,但保险起见显式调用) - 禁用
opcache.enable_cli=1,防止 OPcache 缓存污染干扰 GC 效果
max_requests 到期后 Worker 不会立即消失
max_requests 是 FrankenPHP 控制单个 Worker 处理请求数上限的配置项,设为 1000 并不意味着第 1001 次请求开始前旧 Worker 就已退出。它的真实行为是:“当前请求处理完毕后,不再接受新请求,并等待所有活跃协程结束”。这中间存在时间差。
容易被忽略的细节:
- 若请求中启用了长轮询、SSE 或 WebSocket,
max_requests到期后 Worker 仍会挂起,直到连接关闭 - 日志里看到
worker stopped after 1000 requests,不代表进程已退出——得用ps aux | grep frankenphp确认 - 滚动发布时,
frankenphp stop-workers发送的是“优雅退出”信号,和max_requests触发的是同一套机制,二者不要叠加设置过小(如同时设max_requests=10+ 频繁stop-workers),否则导致 worker 频繁 fork
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











