GraphQL resolver 中 panic 必须在每个函数内手动 defer+recover,因执行器并行调用导致 goroutine 隔离,外层 recover 无效;gqlgen 要求 resolver 签名首参为 context.Context,recover 后仅记录日志并退出,不可继续业务逻辑。

GraphQL resolver 里不能靠全局 recover 拦截 panic
Go 的 recover 无法跨 goroutine 生效,而 GraphQL 执行器(如 graphql-go/graphql 或 gqlgen)默认会对多个字段并行调用 resolver —— 每个 resolver 很可能运行在独立的 goroutine 中。你在 HTTP handler 外层加的 defer func() { recover() }(),对这些子 goroutine 里的 panic 完全无效,进程仍会崩溃。
必须在每个 resolver 函数内部手动加 defer+recover
不是“统一注入”,而是“逐个防护”。因为 resolver 是由 GraphQL 执行器回调的普通函数,没有中间件机制自动包裹。你得显式写进每个 resolver 主体开头:
func (r *queryResolver) User(ctx context.Context, id string) (*model.User, error) {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in User resolver: %v", r)
}
}()
// 实际业务逻辑
return db.FindUserByID(id), nil
}
-
defer必须在函数入口就注册,不能放在条件分支或循环里 - 不要试图返回
recover()的结果作为error—— panic 后状态已损坏,继续执行可能二次崩溃 - 日志里建议带上
ctx.Value("request_id")或类似标识,方便追踪哪次请求触发了 panic
gqlgen 用户注意:resolver 签名必须含 context.Context
如果用 gqlgen,生成的 resolver 接口强制要求第一个参数是 context.Context。漏掉它会导致编译失败,或更糟:运行时 panic 后 recover 捕获到的堆栈不包含上下文信息,难以定位来源。
- 错误写法:
func(r *queryResolver) Users() ([]*model.User, error) - 正确写法:
func(r *queryResolver) Users(ctx context.Context) ([]*model.User, error) - 哪怕你不用 ctx 做超时控制,也得保留它——这是 gqlgen 解析器调度和取消传播的基础
recover 后别碰已破坏的数据结构
比如 resolver 里发生了 map 并发写入 panic,recover() 成功返回后,那个 map 内部结构大概率已损坏。此时若还尝试读取它、或调用 len()、甚至 range,很可能再次 panic。
立即学习“go语言免费学习笔记(深入)”;
- recover 后只做记录和退出,不要继续业务逻辑
- 不要在 recover 块里调用
close(ch)、向疑似损坏的 slice 追加元素、或访问刚解引用的指针字段 - 如果必须清理资源(如临时文件),应提前注册到
defer链中,而非依赖 recover 后的代码


















