Workerman协程中异常必须显式try-catch或设Coroutine::setExceptionHandler,否则协程静默退出;HTTP场景需在中间件或控制器中捕获并返回响应;禁用exit/die及避免致命错误以防进程崩溃。

Workerman协程环境下,异常一旦未被及时捕获,会直接终止当前协程且不向上冒泡,导致任务静默失败、日志缺失、业务中断——这不是代码写错了,是协程的异常传播机制与同步PHP完全不同。
协程内必须用try-catch包裹异步操作
在Coroutine::create()或Timer::tick等协程函数内部,所有可能抛出异常的代码(如file_get_contents、Redis::get、数据库查询)都必须被try-catch显式包围。
这一步不能省略:协程中throw的异常不会触发全局set_exception_handler,也不会中断整个Worker进程,只会让当前协程退出,后续代码不再执行。
错误示范:Coroutine::create(function () { $data = file_get_contents('http://api.com'); echo $data; }); → 若网络超时或DNS失败,协程直接退出,无日志、无响应、无法重试。
正确写法:Coroutine::create(function () { try { $data = file_get_contents('http://api.com'); echo $data; } catch (Throwable $e) { \Monolog\Logger::error('协程HTTP请求失败', ['msg' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine()]); } });
全局协程异常处理器要单独注册
协程有自己的异常处理上下文,必须调用Coroutine::setExceptionHandler()设置专属处理器,不能依赖PHP主进程的set_exception_handler()。
方法一:在Worker启动前注册(推荐)
第一步:在start.php顶部引入use Workerman\Coroutine;
第二步:调用Coroutine::setExceptionHandler(fn(Throwable $e) => error_log('[Coroutine] Uncaught: ' . $e->getMessage()));
第三步:确保该行代码在Worker::runAll()之前执行——否则注册无效。
方法二:在onWorkerStart中为每个进程单独注册$worker->onWorkerStart = function () { Coroutine::setExceptionHandler(function (Throwable $e) { \support\Utils::log('coroutine_error', $e); }); };
【协程异常处理器只对当前协程生效,不同Worker进程需各自注册】
中间件里捕获协程异常并统一响应
当协程用于HTTP请求处理(如onMessage中),异常必须转化为标准HTTP响应返回给客户端,否则连接会挂起或断开。
方法一:在控制器中主动try-catch
直接在onMessage回调或Webman控制器方法内包裹业务逻辑:public function index(Request $request): Response { try { $result = Coroutine\run(fn() => api_call()); return response_json($result); } catch (ClientException $e) { return response_json(['code' => 400, 'msg' => 'API调用失败'], 400); } catch (Throwable $e) { return response_json(['code' => 500, 'msg' => '服务异常'], 500); } }
方法二:用前置中间件拦截所有协程异常
创建中间件类,在process()中启动协程并捕获其异常:public function process(Request $request, callable $next): Response { try { return Coroutine\run(fn() => $next($request)); } catch (TooManyRequestsHttpException $e) { return response_json(['msg' => $e->getMessage()], 429); } catch (Throwable $e) { \support\Utils::log('middleware_coroutine_error', $e); return response_json(['msg' => '系统繁忙'], 500); } }
注意:此处Coroutine\run()会等待协程完成再返回,避免异步响应错乱;不要用Coroutine::create(),否则中间件立即返回,协程在后台静默执行。
禁止在协程中使用exit/die或触发致命错误
协程内调用exit或发生Parse Error、Fatal Error等,会导致整个Worker进程崩溃,而不是仅退出当前协程。
例如:Coroutine::create(function () { $undefined->method(); }); → 触发Fatal Error → Worker进程退出 → 所有连接断开 → 主进程重启子进程。
解决方式只有两个:
① 开发阶段开启display_errors=On并配合IDE静态分析,提前发现语法/调用错误;
② 生产环境用register_shutdown_function()兜底捕获致命错误,但无法恢复协程,只能记录日志并通知告警。
【协程中任何未被捕获的致命错误都会杀死整个Worker进程】

















