net/http: Client.Do() in HTTP/2 mode returns unexpected nil error after client timeout #65375
Labels
FrozenDueToAge
NeedsInvestigation
Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone
Go version
go1.20.13 darwin/arm64, go1.21.6 darwin/arm64
Output of
go env
in your module/workspace:What did you do?
It appears that if
http.Client.Do()
encounters a client-initiated timeout, it can sometimes returns a nil error and a server response if thehttp.Request
object has been reused. This only appears to happen if the client is speaking HTTP/2.Here's a minimum reproduction case: https://gist.github.com/bobvawter/c5fb99bbc8de533bb53881fe78b8db44
In the linked repro, there's an HTTP/2 enabled server that shows a pathologically slow endpoint. This endpoint returns 200/OK if it is able to completely consume the request body, or 400/Bad Request if the body can't be read after a delay. The
http.Client
is configured with a timeout that will fire most of the time. The client loop retries the request if there is an error, since we can inspecthttp.NewRequestWithContext()
to see that its implementation ofGetBody
makes copies of abytes.Reader
.After about 30 seconds of running the test on my M1 laptop, the repro will demonstrate that a timed-out request to the server can cause
Client.Do()
to return a nil error and that the HTTP 400 status code is visible to the client. We often, but not always, see the expected "context deadline exceeded" error.The final log output that the repro creates before halting will look like:
The message from line 141 is an error generated by reading the
http.Request.Body
. There are cases too, where the body is some non-zero, but partial, amount of the content length. Since only the client-configured timeout should be introducing errors, it's odd that we see the resulting HTTP 400 sent by the handler.This repro proceeds as expected if:
What did you see happen?
The repro case linked above gets to the block identified as
ISSUE
, whereclient.Do()
has returned anil
error and we see the 400 status code returned by the trivial http handler appear, theBAD STATUS
message.What did you expect to see?
I would expect that if the client timeout fires or the client is unable to transmit the entire payload to the server, that
http.Client.Do()
should return a non-nil error.I would also expect the behaviors to be broadly similar between HTTP/1.1 and HTTP/2 protocols.
The text was updated successfully, but these errors were encountered: