context.Context未传到底层调用即失效的最常见情况是HTTP handler中使用http.DefaultClient.Do(req)而未通过http.NewRequestWithContext将ctx注入请求,导致超时或取消信号无法传递至下游,形成悬挂请求。

context.Context没传到底层调用就失效了
最常见的情况是:HTTP handler里拿到ctx,但调用下游服务时直接用了http.DefaultClient.Do(req),没把ctx塞进http.NewRequestWithContext。结果超时或取消信号只在本层生效,下游服务还在跑,形成“悬挂请求”。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 所有跨服务调用(HTTP/gRPC)必须用带
ctx的构造函数:http.NewRequestWithContext、grpc.DialContext - 数据库查询也要传
ctx,比如db.QueryContext(ctx, ...),否则SQL执行无法被中断 - 避免在中间件里用
context.Background()覆盖原ctx——这是静默丢上下文的高发点
context.WithTimeout嵌套导致超时时间错乱
比如在handler里调用context.WithTimeout(ctx, 5*time.Second),又在内部service里再套一层context.WithTimeout(ctx, 2*time.Second),实际生效的是内层2秒,但外层5秒的deadline已失效,监控和日志里看到的超时时间对不上。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 超时应由最外层(通常是API入口)统一设置,内部调用只做
context.WithValue或context.WithCancel,不重复设WithTimeout - 如果必须分阶段设超时,用
context.WithDeadline显式指定绝对时间点,避免嵌套叠加 - 用
ctx.Deadline()检查当前上下文是否已过期,而不是依赖“我设了5秒就一定剩5秒”
value键冲突导致数据覆盖或取不到
多个中间件或模块都往ctx里存值,比如都用ctx = context.WithValue(ctx, "user_id", id),但键是string类型,不同包之间一模一样的字符串字面量容易撞车;或者用int当键,结果不同模块用了同一个数字,值被悄悄覆盖。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 键必须是包私有变量,类型不限于
string,推荐用struct{}或自定义类型:type userIDKey struct{},然后ctx = context.WithValue(ctx, userIDKey{}, id) - 不要用
context.WithValue传业务核心参数(如用户ID、租户ID),应作为函数参数显式传递;ctx.Value只适合传元数据(traceID、requestID) - 取值时务必判空:
if v := ctx.Value(userIDKey{}); v != nil { ... },避免panic
goroutine泄漏伴随context取消未清理
启动一个goroutine处理异步任务(比如发消息、写日志),但没监听ctx.Done(),导致父请求取消后,goroutine还在跑,还可能继续操作已关闭的资源(如http.Response.Body)。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 所有长期运行的goroutine必须select监听
ctx.Done(),并在退出前做清理:defer func() { close(ch) }() - 用
sync.WaitGroup配合ctx控制生命周期,避免“启动了但不知道啥时候停” - 注意
http.Client默认不响应ctx.Cancel,必须确保req是http.NewRequestWithContext创建的,且resp.Body及时Close()
context.WithTimeout,而是让整个调用链每一层都尊重它——从HTTP handler到gRPC client,再到DB driver,再到底层syscall。漏掉任意一环,上下文就断了。


















