背景

上游请求已经超时,下游却继续查询和计算,会把一次失败变成额外资源消耗。context 的作用是传播工作预算,而不是传递任意配置。

核心判断

  1. 从入口传递同一条取消链,连接请求与数据库调用都使用带 context 的 API。
  2. 派生超时必须调用 cancel 释放资源。
  3. 后台独立任务重新定义生命周期,不偷偷继承即将结束的请求 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。