HTTP客户端超时必须用context.WithTimeout配合req.WithContext(),禁用client.Timeout;http.Get等快捷函数不支持context,数据库需用QueryContext等带Context方法,goroutine阻塞操作须手动监听ctx.Done()。
HTTP 客户端请求必须用 context.WithTimeout 配合 WithContext
直接调用 http.get 或 client.do(req) 时,如果没注入 context,超时控制就完全失效——dns 卡住、tcp 连接挂起、tls 握手冻结,都会无限等待。正确做法是:先用 context.withtimeout 创建子 context,再用 req.withcontext(ctx) 注入请求,最后交给 client 执行。
-
http.Client.Timeout字段和context.WithTimeout别混用,建议设为0,避免行为冲突 - 必须显式构造
*http.Request,不能依赖http.Get这类快捷函数,它们不接受 context - 错误判断要用
errors.Is(err, context.DeadlineExceeded),别用字符串匹配"timeout"
数据库查询必须传入 ctx 到 QueryContext / ExecContext
像 database/sql 这类标准库已全面支持 context,但只在带 Context 后缀的方法里生效:QueryContext、ExecContext、PrepareContext。如果还用老接口如 Query,哪怕外面套了 WithTimeout,查询照样卡死不响应。
- 超时后连接会自动关闭,但前提是 driver 实现了 context 取消(主流 MySQL/PostgreSQL 驱动都已支持)
- 注意
rows.Close()不会自动触发 cancel,它只是释放本地资源;真正的中断靠的是 query 执行阶段监听ctx.Done() - 如果查询返回大量数据,读取
rows.Next()时也要检查ctx.Err(),否则可能在遍历中途漏掉超时信号
goroutine 内部阻塞操作必须主动监听 ctx.Done()
不是所有地方都能靠标准库自动响应 context——比如自定义 channel 等待、time.Sleep、纯计算循环、或调用未适配 context 的第三方函数。这时必须手动插入检查点,否则 WithTimeout 形同虚设。
- 避免写
for i := 0; i ,应在循环内加 <code>if ctx.Err() != nil { return } - 用
select替代单 channel 接收:case v := → <code>case v := - 不要在 goroutine 启动前就
defer cancel(),cancel 必须在 goroutine 退出后调用,否则可能提前关掉 timer
服务端 handler 中派生子 context 要基于 r.Context()
在 HTTP handler 里直接用 context.Background() 调 WithTimeout 是错的——它切断了与客户端连接生命周期的关联。一旦用户关闭浏览器或网络中断,你的 goroutine 仍会继续跑,直到超时才退出,造成泄漏。
- 始终从
r.Context()派生:ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second) - 这个派生 ctx 会继承客户端断连信号,
ctx.Done()在连接关闭时也会被关闭 - 务必把该 ctx 透传给所有下游调用:DB 查询、HTTP 请求、日志写入(如果日志库支持 context)
真正容易被忽略的点是:context 取消信号不会“穿透”到系统调用底层,尤其在 DNS 解析或 TCP 建连阶段;你看到的“超时生效”,其实是 Go 标准库在各层做了协作式退出。这意味着,每个环节都得有人响应,没人监听,信号就停在那里。


















