上下文错误
Go 里遇到“上下文错误”,先不要看 err.Error() 的文字。最稳定的分类方式是使用 errors.Is(err, context.Canceled) 判断调用者或父上下文主动取消,使用 errors.Is(err, context.DeadlineExceeded) 判断截止时间已经耗尽;如果还需要知道取消的业务原因,再读取 context.Cause(ctx)。这三个信息层次不同,不能互相替代。
context.Canceled表示非 deadline 原因的取消,context.DeadlineExceeded表示 deadline 到期。- 错误可能被数据库、HTTP 或业务层包装,所以优先用
errors.Is,不要比较字符串。 ctx.Err()适合判断当前上下文状态,context.Cause(ctx)适合追踪更具体的取消原因。
先把两个标准错误对应到触发条件
Context 的取消信号只有在 Done 关闭后才有错误结果。标准库把“截止时间经过”与“其它原因取消”分成两个哨兵错误:
| 错误 | 代表的边界 | 常见触发点 |
|---|---|---|
context.Canceled | 非 deadline 的取消 | 调用 cancel()、父上下文被取消、调用者不再需要结果 |
context.DeadlineExceeded | 截止时间已经经过 | WithTimeout 或 WithDeadline 到期 |
子上下文的结果取最先发生的取消事件。如果父上下文先到期,子上下文即使自己还配置着更晚的时间,也应按 DeadlineExceeded 处理;如果代码先调用了返回的取消函数,则是 Canceled。因此不要只根据“这是一个 timeout 上下文”来猜错误类别。

用 errors.Is 判断被包装的上下文错误
下游函数经常会给错误增加调用位置或资源信息,例如 fmt.Errorf("查询用户: %w", err)。这时错误的展示文本已经变化,但底层仍然可能是 context.Canceled 或 context.DeadlineExceeded。用 errors.Is 沿着包装链判断,才能在日志可读性和机器分流之间同时保留信息。
func classify(err error) string {
// 先识别上下文的两个标准分类,兼容下游用 %w 包装后的错误。
switch {
case errors.Is(err, context.Canceled):
return "caller-canceled"
case errors.Is(err, context.DeadlineExceeded):
return "deadline-exceeded"
case err == nil:
return "ok"
default:
// 非上下文错误继续交给上层按业务类型处理。
return "other"
}
}
判断顺序不应写成字符串比较,也不建议把所有超时都转换成同一个自定义错误。调用方可能需要对主动取消停止重试,对 deadline 超时做有限重试,而对连接失败或参数错误采取完全不同的动作。
结合 ctx.Err 处理请求边界
在循环或 select 中,ctx.Err() 是读取当前上下文最终分类的直接入口:上下文仍然有效时返回 nil,结束后返回两个标准错误之一。它适合放在“停止继续工作”的返回路径;真正从下游拿到的 err 则仍应使用 errors.Is,因为中间层可能包装或替换错误。
func waitForWork(ctx context.Context, jobs
如果一个函数接收了父上下文,就应继续把它传给数据库、HTTP 客户端或其它下游调用,而不是重新创建 context.Background()。否则上游取消和 deadline 无法到达真正执行工作的地方,最后的错误分类也会失真。

需要业务原因时读取 context.Cause
从 Go 1.20 起,context.WithCancelCause 可以在取消时记录一个更具体的原因,之后通过 context.Cause(ctx) 读取。它不会把 ctx.Err() 的标准分类改成业务错误:主动取消仍然是 context.Canceled,业务错误放在 Cause 中。deadline 场景也可以使用带 cause 的构造函数来补充说明。
func runWithCause(parent context.Context) error {
// 标准 Err 用来做稳定分流,Cause 用来保留更具体的失败原因。
ctx, cancel := context.WithCancelCause(parent)
defer cancel(nil) // 正常收尾时释放关联资源,不额外覆盖已有原因
if err := prepare(ctx); err != nil {
cancel(fmt.Errorf("准备阶段失败: %w", err))
return err
}
if err := ctx.Err(); err != nil {
// 取消分类与业务原因分开读取,避免把 Cause 当作 Err 比较。
return fmt.Errorf("上下文结束: %w; 原因: %v", err, context.Cause(ctx))
}
return nil
}
如果只是为了判断“该不该继续”,读取 ctx.Err() 就够了;如果日志、指标或上层提示还需要知道是谁触发取消,再读取 context.Cause。不要把 Cause 的具体文字当作稳定枚举,业务代码仍应围绕自己的 sentinel error 或类型做 errors.Is、errors.As 判断。
把判断逻辑收拢到错误处理函数
在服务边界统一分流,可以避免每个调用点都重复一套字符串判断。主动取消通常不适合记成服务故障,也不应因为客户端已经断开就继续重试;deadline 超时则需要结合幂等性、剩余预算和下游状态决定是否重试。
func actionFor(err error) string {
// 取消和超时都属于上下文结束,但运营含义并不相同。
switch {
case errors.Is(err, context.Canceled):
return "stop-without-retry"
case errors.Is(err, context.DeadlineExceeded):
return "retry-only-if-idempotent"
default:
return "handle-by-domain-error"
}
}
可以把下面这条速查规则放进项目的错误处理约定:先用 errors.Is 判断标准上下文错误,再根据请求是否幂等决定重试;需要解释取消来源时读取 context.Cause;所有派生上下文都在生命周期结束时调用 cancel。
相关问题
能不能直接比较 err.Error()?
不建议。错误可能被包装,文本也可能来自不同下游实现;使用 errors.Is 才能保留底层分类。
调用 cancel() 后一定是 Canceled 吗?
对当前派生上下文主动调用其取消函数时通常是 context.Canceled;但如果父上下文更早因 deadline 结束,子上下文会继承先发生的 DeadlineExceeded。
为什么既看 err 又看 ctx.Err?
err 描述下游调用返回的错误,ctx.Err() 描述当前上下文状态。前者可能被包装,后者更适合确认请求边界是否已经结束。
Cause 会替代 DeadlineExceeded 吗?
不会。Cause 用于补充取消原因,标准分类仍由 Err 返回;两者应分别用于分流与解释。
参考文档:https://pkg.go.dev/context;https://go.dev/src/context/context.go
-
- Golang · Go问答 | 24分钟前 | 标准库 · 时间处理 · Go问答 · Go 时间解析 time.Parse RFC3339Nano RFC3339 Nanosecond
- RFC3339 解析
- 414浏览 收藏
-
- Golang · Go问答 | 36分钟前 |
- JSON 自定义解码
- 132浏览 收藏
-
- Golang · Go问答 | 44分钟前 | 标准库 · go · url · net/url PathEscape URL转义 QueryEscape
- URL 转义
- 399浏览 收藏
-
- Golang · Go问答 | 47分钟前 | url · Go问答 · Go net/url QueryEscape ParseQuery QueryUnescape PathUnescape
- 查询参数解码
- 102浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 982次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 986次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 1105次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 1075次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 916次使用
-
- Go map 并发写 panic 怎么办:从共享 map 到可控写入路径
- 2026-06-30 123浏览
-
- Go代码规范错误处理示例经验总结
- 2022-12-23 278浏览
-
- go语言中的defer关键字
- 2023-02-17 150浏览
-
- Golang中Interface接口的三个特性
- 2023-01-07 394浏览
-
- go语言中函数与方法介绍
- 2023-01-07 297浏览

上下文取消树
