Go 里 context 取消传播的一个细节
线上有个服务偶尔 goroutine 数量往上飙,最后定位到一处对下游的调用在请求结束后没被取消。复盘下来是个很常见的疏忽。
最小复现
func handler(w http.ResponseWriter, r *http.Request) {
// 错误:用了 context.Background(),和请求生命周期脱钩
go doWork(context.Background())
}
func doWork(ctx context.Context) {
// 请求早就返回了,这个还在慢慢跑
resp, _ := http.Get("https://slow.example.com/")
_ = resp
}
问题在于 context.Background() 永不取消。请求处理完了,但派生出去的这个 goroutine 拿着一个永远活着的 context,下游一慢就堆积。
正确做法
要么直接用 r.Context(),要么在需要脱离请求生命周期时用 context.WithTimeout 兜一个上限:
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
加上超时之后,那条泄漏曲线立刻就平了。教训是:任何跨网络的调用都应该带一个会取消的 context,别图省事传 Background。
← 返回首页