Go Context 取消:让停止信号真正穿过调用链
背景 上游请求已经超时,下游却继续查询和计算,会把一次失败变成额外资源消耗。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。