背景
上游请求已经超时,下游却继续查询和计算,会把一次失败变成额外资源消耗。context 的作用是传播工作预算,而不是传递任意配置。
核心判断
- 从入口传递同一条取消链,连接请求与数据库调用都使用带 context 的 API。
- 派生超时必须调用 cancel 释放资源。
- 后台独立任务重新定义生命周期,不偷偷继承即将结束的请求 context。
最小示例
下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。
func Fetch(ctx context.Context, url string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("unexpected HTTP status: %s", resp.Status)
}
const maxBody = 1 << 20
body, err := io.ReadAll(io.LimitReader(resp.Body, maxBody+1))
if err != nil {
return nil, err
}
if len(body) > maxBody {
return nil, fmt.Errorf("response exceeds body limit")
}
return body, nil
}
边界与误区
创建 HTTP 请求也可能失败,不能忽略错误。取消是否能及时生效取决于被调用的操作;CPU 循环需要自行检查,返回后也要关闭响应体。
验证方式
让服务端延迟返回,在客户端取消后检查耗时、错误与 goroutine 数。对大响应设置读取预算,避免取消之外的无界内存消耗。
小结
所有阻塞操作都绑定上游 context,重试循环和 channel 等待必须响应 Done。