iris mvc中协程panic无法被全局中间件捕获,因recover仅作用于当前goroutine;controller内显式启动的新goroutine需各自独立defer recover,否则会静默崩溃。

为什么Iris MVC的协程panic不能靠全局中间件捕获
Iris的MVC控制器方法默认运行在主HTTP goroutine中,但一旦你在Controller里显式启动新协程(比如go c.Service.AsyncNotify()),那个协程里的panic就完全脱离了HTTP handler的执行流。此时你在app.Use()里写的recover中间件根本不起作用——它只对当前goroutine有效。
常见错误现象:http: panic serving 127.0.0.1:8080: runtime error: invalid memory address 日志反复出现,但接口返回正常,说明主流程没崩,但后台协程已静默退出。
- 每个新
go语句都开启独立goroutine,必须各自配defer recover() - 不能指望MVC层自动注入或框架代为恢复
- 协程内调用第三方库(如
json.Unmarshal传错结构体)极易触发panic,且这类panic往往在开发期被忽略
Controller里启动协程时必须手动包一层safeGo
别直接写go doSomething(),而是封装一个带recover的启动函数,确保异常不外泄。
示例代码:
func safeGo(f func(), logger *log.Logger) {
go func() {
defer func() {
if r := recover(); r != nil {
logger.Printf("panic in goroutine: %+v", r)
// 可选:上报监控、发告警、记录traceID
}
}()
f()
}()
}
- 必须把
logger作为参数传入,避免闭包捕获Controller字段导致内存泄漏 - 不要在
safeGo里尝试ctx.JSON或修改HTTP响应——协程已脱离请求生命周期 - 如果协程需要反馈结果给主流程,改用
channel或sync.WaitGroup,而非共享状态
异步任务中panic导致数据不一致怎么办
单纯recover住panic还不够。比如协程里执行DB更新后panic,事务可能已提交一半。
关键动作不是“捕获”,而是“隔离”和“可重试”:
- 所有异步操作必须自带幂等标识(如
task_id),失败后能被重复调度 - 避免在协程里直接调用
c.Ctx——iris.Context不可跨goroutine使用,会引发data race - 数据库操作优先用
tx.Commit()前校验,而不是依赖panic后rollback(很多驱动不保证panic时自动回滚) - 若必须用事务,应在协程启动前开启,并把
*sql.Tx作为参数传入,而非从Controller字段取
最容易被忽略的点:第三方SDK的panic传染性
某些SDK(如老版本gopkg.in/yaml.v2或未加json.RawMessage校验的解析逻辑)会在输入非法时直接panic,且不提供error返回路径。
这种panic无法通过业务层if err != nil拦截,必须在调用点前置防御:
- 对所有外部输入做
json.Valid()或yaml.UnmarshalStrict()预检 - 对SDK调用单独套
defer recover(),不要和业务逻辑混在同一函数里 - 上线前用fuzz测试验证边界输入,比等线上panic再补救更可靠
协程panic的真正难点不在捕获,而在判断“该不该让它panic”——配置加载失败、连接池枯竭这类问题,早暴露比静默recover更有价值。











