Skip to content

Commit 94fb2bc

Browse files
neildmknyszek
authored andcommitted
[internal-branch.go1.17-vendor] http2: avoid spurious RoundTrip error when server closes and resets stream
Avoid a race condition between RoundTrip and the read loop when the read loop reads a response followed by an immediate stream reset. For golang/go#49645 Fixes golang/go#49662 Change-Id: Ifb34e2dcb8cc443d3ff5d562cc032edf09da5307 Reviewed-on: https://go-review.googlesource.com/c/net/+/364834 Trust: Damien Neil <[email protected]> Run-TryBot: Damien Neil <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]> (cherry picked from commit 6a13c67) Reviewed-on: https://go-review.googlesource.com/c/net/+/368379 Reviewed-by: Michael Knyszek <[email protected]>
1 parent e108c19 commit 94fb2bc

File tree

1 file changed

+39
-26
lines changed

1 file changed

+39
-26
lines changed

http2/transport.go

+39-26
Original file line numberDiff line numberDiff line change
@@ -1033,36 +1033,49 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
10331033
}
10341034
}
10351035

1036+
handleResponseHeaders := func() (*http.Response, error) {
1037+
res := cs.res
1038+
if res.StatusCode > 299 {
1039+
// On error or status code 3xx, 4xx, 5xx, etc abort any
1040+
// ongoing write, assuming that the server doesn't care
1041+
// about our request body. If the server replied with 1xx or
1042+
// 2xx, however, then assume the server DOES potentially
1043+
// want our body (e.g. full-duplex streaming:
1044+
// golang.org/issue/13444). If it turns out the server
1045+
// doesn't, they'll RST_STREAM us soon enough. This is a
1046+
// heuristic to avoid adding knobs to Transport. Hopefully
1047+
// we can keep it.
1048+
cs.abortRequestBodyWrite()
1049+
}
1050+
res.Request = req
1051+
res.TLS = cc.tlsState
1052+
if res.Body == noBody && actualContentLength(req) == 0 {
1053+
// If there isn't a request or response body still being
1054+
// written, then wait for the stream to be closed before
1055+
// RoundTrip returns.
1056+
if err := waitDone(); err != nil {
1057+
return nil, err
1058+
}
1059+
}
1060+
return res, nil
1061+
}
1062+
10361063
for {
10371064
select {
10381065
case <-cs.respHeaderRecv:
1039-
res := cs.res
1040-
if res.StatusCode > 299 {
1041-
// On error or status code 3xx, 4xx, 5xx, etc abort any
1042-
// ongoing write, assuming that the server doesn't care
1043-
// about our request body. If the server replied with 1xx or
1044-
// 2xx, however, then assume the server DOES potentially
1045-
// want our body (e.g. full-duplex streaming:
1046-
// golang.org/issue/13444). If it turns out the server
1047-
// doesn't, they'll RST_STREAM us soon enough. This is a
1048-
// heuristic to avoid adding knobs to Transport. Hopefully
1049-
// we can keep it.
1050-
cs.abortRequestBodyWrite()
1051-
}
1052-
res.Request = req
1053-
res.TLS = cc.tlsState
1054-
if res.Body == noBody && actualContentLength(req) == 0 {
1055-
// If there isn't a request or response body still being
1056-
// written, then wait for the stream to be closed before
1057-
// RoundTrip returns.
1058-
if err := waitDone(); err != nil {
1059-
return nil, err
1060-
}
1061-
}
1062-
return res, nil
1066+
return handleResponseHeaders()
10631067
case <-cs.abort:
1064-
waitDone()
1065-
return nil, cs.abortErr
1068+
select {
1069+
case <-cs.respHeaderRecv:
1070+
// If both cs.respHeaderRecv and cs.abort are signaling,
1071+
// pick respHeaderRecv. The server probably wrote the
1072+
// response and immediately reset the stream.
1073+
// golang.org/issue/49645
1074+
return handleResponseHeaders()
1075+
default:
1076+
waitDone()
1077+
return nil, cs.abortErr
1078+
}
10661079
case <-ctx.Done():
10671080
err := ctx.Err()
10681081
cs.abortStream(err)

0 commit comments

Comments
 (0)