Hyperf协程异常需分层处理:HTTP请求、异步任务、定时任务等各走独立异常链路;空catch、混用handler或使用同步IO驱动会导致异常静默丢失,必须确保异常真实抛出并经对应处理器捕获。

Hyperf 协程异常不能靠“统一加一层 try/catch”就捕获全,必须按运行上下文分层处理:HTTP 请求、异步任务、定时任务、自定义进程各自走不同的异常链路,混用 handler 配置或空 catch 会导致异常静默丢失。
HTTP 请求里的协程异常走 http handler
控制器、中间件、AOP 切面中抛出的 Throwable,只要没被提前 catch,就会进入 config/autoload/exceptions.php 中 'http' 数组注册的处理器。关键点:
- 确保
Hyperf\HttpServer\ExceptionHandler\HttpExceptionHandler在列表靠前,它负责兜底 404/405 等标准错误,避免被后续 handler 错误转成 500 - 自定义 handler 的
isValid()必须精准匹配异常类型(如$throwable instanceof ValidationException),不能无差别返回true - 若在 controller 里手动
try { DB::select(...) } catch (PDOException $e) { }却不重抛,异常就断在了这里,根本不会进全局 handler
async-queue 任务异常必须由 handle() 自身抛出
Hyperf\AsyncQueue\Job::handle() 中的异常默认被 async-queue 组件捕获并静默吞掉——这是设计行为,不是 bug。重试机制只响应“未被捕获的异常”。
- 删掉所有形如
catch (\Exception $e) { }的空块;需要日志就写logger()->error(...); throw $e; - handler 配置在
'exception'数组下才生效(不是'http'),例如:App\Exception\Handler\MySqlExceptionHandler::class - 构造函数里传入不可序列化对象(如
PDO实例)会导致任务投递失败,handle()根本不会执行,也就没有异常可捕获
协程内 IO 操作失败不是“异常捕获问题”,而是资源卡死
调用非协程版 Redis、原生 PDO 或 file_get_contents() 时,协程会挂起甚至被调度器放弃,表现为超时、无日志、连接泄漏——这不是 try/catch 能解决的。
- MySQL 必须用
Hyperf\Database\Connection封装的协程驱动,不是new \PDO() - Redis 必须通过
Hyperf\Redis\RedisFactory获取实例,禁用new \Redis() - HTTP 请求必须用
Hyperf\HttpClient\HttpClient,而非curl_exec()或 Guzzle 同步客户端
底层致命错误和协程调度异常需单独透出
Fatal error: Must be called in the coroutine environment 这类错误是 PHP 致命错误,无法被任何 try/catch 捕获,只能靠日志暴露。
- 启动前设置
Swoole\Error::$log_file = 'php://stderr',让 Swoole 底层错误直接打到终端 - 注册协程级异常处理器:
Swoole\Coroutine::set(['exception_handler' => fn($e) => error_log('[COROUTINE ERROR] ' . $e)]) - 查问题优先看
runtime/logs/swoole.log,不是storage/logs/*.log;很多静默崩溃只在这里留痕
最常被忽略的是:协程异常是否真的“抛出了”——空 catch、构造函数序列化失败、IO 用了同步驱动,都会让异常根本没机会浮出水面。排查时先确认错误是否真实发生,再谈捕获。


















