hyperf进程异常退出主因是协程调度器失活或致命错误未兜底;$signal==11为段错误需core dump分析;协程sleep死锁本质是调度器离线;shutdown函数须在onworkerstart中注册;max_request=0易致内存泄漏和oom。

Hyperf 进程异常退出,八成不是代码逻辑错了,而是协程调度器失活或致命错误未兜底——尤其在 swoole_async_set(['enable_coroutine' => false]) 后又启动协程的混合配置下,Swoole\Coroutine\System::sleep() 会直接触发死锁,且不报错、不超时、CPU 归零。
WorkerError 回调里 $signal == 11 就是段错误,别只看 exit_code
当 WorkerError 回调被触发,$signal 值才是判断崩溃性质的关键:
-
$signal === 11:真实段错误(SIGSEGV),必须开core dump并用gdb分析;检查/proc/[pid]/status的State是否为Z(僵尸)或T(暂停) -
$signal === 9:大概率被系统 OOM Killer 杀掉,立刻查dmesg -T | grep -i "killed process" -
$signal === 0且$exitCode === 255:PHP 致命错误(如E_ERROR、E_PARSE),try-catch完全无效,只能靠register_shutdown_function+error_get_last()
注意:register_shutdown_function 必须在 onWorkerStart 里注册,全局写一次等于没写——每个 Worker 是独立进程,不继承父进程的钩子。
协程 sleep 死锁的本质是“调度器离线”,不是函数有问题
只要出现 [fatal error]: all coroutines (count: 1) are asleep - deadlock!,说明当前进程只有一个协程,且它正在 sleep(),而调度器无其他协程或 I/O 事件可轮转。典型场景:
- 父进程禁用协程(
swoole_async_set(['enable_coroutine' => false])),却在子进程中调用go()+Swoole\Coroutine\System::sleep() - 定时器回调(
Swoole\Timer::after())启动的子进程,内部只有无限sleep,没有co::read()、co::gethostbyname()等唤醒事件 - 协程池中每个协程都只做
sleep,没有实际 I/O 或计算任务,最终调度器判定“无可调度”
验证方式:用 strace -p [pid] -e trace=futex,clone,wait4,若卡在 futex(0x..., FUTEX_WAIT_PRIVATE, ...) 就是调度器挂起;再配合 php ./vendor/bin/swoole-tracker status 查 coroutine_num 是否长期为 0 或 1。
Hyperf 下致命错误兜底必须分层注册
Hyperf 默认不自动注册 register_shutdown_function 到每个 Worker,漏掉就等于放弃兜底能力。正确做法是:
- 在
onWorkerStart回调里注册 shutdown 钩子,并确保记录$worker_id和时间戳,否则日志混在一起无法定位 - 在每个协程入口(比如
go(function () { ... })内部)加try-catch (\Throwable $e),因为协程异常不跨协程传播 - 全局设置
set_exception_handler和set_error_handler,捕获漏网的未定义变量、类型错误等 - 禁用
display_errors,避免错误信息泄露到响应体;所有错误统一走日志,不要echo或var_dump
Hyperf 的 ExceptionHandler 只管 HTTP 请求层,对 Worker 初始化失败、定时器回调崩溃、子进程异常这些底层问题完全不感知。
max_request 设为 0 是生产环境最危险的配置之一
设 max_request = 0 表示永不重启 Worker,相当于把内存泄漏、静态变量污染、资源句柄累积全堆在一个进程里。后果:
- Worker 越跑越慢,最后因 OOM 或段错误退出,退出前可能已服务数百个请求,问题难以复现
- 一旦出现崩溃,
WorkerError日志里$exitCode和$signal会随泄漏程度变化,干扰根因判断 - 和
max_request_grace搭配使用才能真正平滑——否则请求中途被 kill,客户端收到空响应
建议值:max_request = 3000 ~ 10000,配合监控看 WorkerExit 频率。如果发现 Worker 总在接近该值时退出,说明不是配置问题,是真有泄漏,该查对象池、连接池、静态数组了。











