I try to read from response.Body after request's context was cancelled and get context cancelled error, but only sometimes and only when response body is long
I am making a request with contextWithTimeout and cancel context before I have read body. My code looks like this:
type Client struct {
httpClient *http.Client
}
func (c *Client) Req(ctx context.Context, timeout time.Duration, method string, url *url.URL, body io.Reader) (*http.Response, error) {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, method, url.String(), body)
if err != nil {
return nil, err
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}
then I call client.Req in another function and try to do io.ReadAll(response.Body), but when the response is long it sometimes returns context cancelled error. Why does it happen and why it does not happen with short responses?
When you run
resp, err := c.httpClient.Do(req)the following happens (simplifying quite a bit - see here if you want details):You now call
cancelwhich signals that the request should be cancelled. This is a signal, not an immediate action; the call tocanceleffectively just closes a channel and returns. This means that theio.ReadAll(response.Body)you call afterReqreturns will run concurrently with the routines actioning the cancellation.So you have a race between the response being read and the cancellation signal being acted upon. You may think that the signal must be acted upon before more data comes over the network, but there are a range of complicating factors:
So in simple terms "it does not happen with short responses" because the response is read before the cancellation is acted upon. The larger the response, the more time it will take to read, so the more likely it is that the cancellation will take effect.