FrankenPHP Worker模式下未捕获异常仅终止当前Worker进程,主进程会自动拉起新Worker,服务整体不宕机;异常需在handler内try-catch或通过set_exception_handler全局兜底,避免穿透至Swoole运行时导致进程退出。

会挂,但只挂当前 Worker 进程,不会导致整个 FrankenPHP 服务宕机。
FrankenPHP Worker 模式下未捕获异常的传播路径
FrankenPHP 的 Worker 模式基于 Swoole 的 Server::start() 启动,每个 Worker 进程运行一个独立的 PHP 执行上下文。当 handler(比如 handle() 方法或路由闭包)中抛出未捕获的 Throwable 时:
- 异常不会被自动吞掉,也不会被框架静默忽略
- 若没被
try/catch捕获,会一路冒泡到 Swoole Worker 线程顶层 - Swoole 默认行为是:打印
Fatal error: Uncaught ...到 stderr,然后调用exit(1)终止该 Worker 进程 - 主进程检测到 Worker 异常退出后,会立即 fork 一个新的 Worker 替代它(前提是未调用
Server::shutdown())
为什么看起来“像挂了”?常见错觉来源
用户观察到请求失败、响应超时或日志中断,并误以为服务整体不可用,其实只是单个 Worker 瞬时失效:
-
curl或浏览器收到空响应 / 502 / connection reset,是因为当前 Worker 已 exit,而新 Worker 尚未完成初始化(尤其加载 Composer autoloader 或配置耗时) - 日志里出现
Worker exited unexpectedly或Segmentation fault(后者多见于扩展冲突,非纯 PHP 异常) - 监控看到
worker_num波动、worker_exit_count上升,这是正常容错表现,不是故障 - 注意:如果异常发生在
onStart或onWorkerStart阶段,可能导致 Worker 根本无法启动,此时需检查初始化逻辑
如何避免 Worker 因 handler 异常退出?
核心原则:不要让未捕获异常穿透到 Swoole 运行时边界。有三层兜底可设:
立即学习“PHP免费学习笔记(深入)”;
- 在 handler 内部加
try/catch:最直接,适合已知风险点(如第三方 API 调用、文件读写) - 全局注册
set_exception_handler():捕获所有未被捕获的Throwable,记录日志并返回统一错误响应,**必须调用$response->withStatus(500)并 return,不能 throw 或 exit** - 配合
set_error_handler()把E_WARNING/E_NOTICE转为ErrorException,纳入同一套异常流处理 - FrankenPHP 官方推荐在
public/index.php入口处设置这两者,而非仅依赖框架中间件——因为中间件本身也可能崩
容易被忽略的关键细节
FrankenPHP 的 Worker 模式不等同于传统 FPM,它的生命周期更长、状态更重:
- 一旦某个 Worker 因异常退出,它持有的所有资源(PDO 连接、Redis 实例、静态变量)全部丢失,新 Worker 从零重建 —— 所以频繁异常会导致连接池打满、内存反复初始化
-
set_exception_handler()注册的函数里,不能再触发新的未捕获异常(比如日志写入失败又没兜底),否则会二次 exit,陷入无限重启循环 - Hyperf/Swoole 生态中常见的
go()协程内抛异常,不会终止 Worker,但若没用catch住且没调defer清理,可能引发资源泄漏 —— 这类问题不会导致挂,但比挂更难排查



















