Skip to content

Commit 6a13c67

Browse files
committed
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. Fixes golang/go#49645 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]>
1 parent 47ca1ff commit 6a13c67

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
@@ -1124,36 +1124,49 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
11241124
}
11251125
}
11261126

1127+
handleResponseHeaders := func() (*http.Response, error) {
1128+
res := cs.res
1129+
if res.StatusCode > 299 {
1130+
// On error or status code 3xx, 4xx, 5xx, etc abort any
1131+
// ongoing write, assuming that the server doesn't care
1132+
// about our request body. If the server replied with 1xx or
1133+
// 2xx, however, then assume the server DOES potentially
1134+
// want our body (e.g. full-duplex streaming:
1135+
// golang.org/issue/13444). If it turns out the server
1136+
// doesn't, they'll RST_STREAM us soon enough. This is a
1137+
// heuristic to avoid adding knobs to Transport. Hopefully
1138+
// we can keep it.
1139+
cs.abortRequestBodyWrite()
1140+
}
1141+
res.Request = req
1142+
res.TLS = cc.tlsState
1143+
if res.Body == noBody && actualContentLength(req) == 0 {
1144+
// If there isn't a request or response body still being
1145+
// written, then wait for the stream to be closed before
1146+
// RoundTrip returns.
1147+
if err := waitDone(); err != nil {
1148+
return nil, err
1149+
}
1150+
}
1151+
return res, nil
1152+
}
1153+
11271154
for {
11281155
select {
11291156
case <-cs.respHeaderRecv:
1130-
res := cs.res
1131-
if res.StatusCode > 299 {
1132-
// On error or status code 3xx, 4xx, 5xx, etc abort any
1133-
// ongoing write, assuming that the server doesn't care
1134-
// about our request body. If the server replied with 1xx or
1135-
// 2xx, however, then assume the server DOES potentially
1136-
// want our body (e.g. full-duplex streaming:
1137-
// golang.org/issue/13444). If it turns out the server
1138-
// doesn't, they'll RST_STREAM us soon enough. This is a
1139-
// heuristic to avoid adding knobs to Transport. Hopefully
1140-
// we can keep it.
1141-
cs.abortRequestBodyWrite()
1142-
}
1143-
res.Request = req
1144-
res.TLS = cc.tlsState
1145-
if res.Body == noBody && actualContentLength(req) == 0 {
1146-
// If there isn't a request or response body still being
1147-
// written, then wait for the stream to be closed before
1148-
// RoundTrip returns.
1149-
if err := waitDone(); err != nil {
1150-
return nil, err
1151-
}
1152-
}
1153-
return res, nil
1157+
return handleResponseHeaders()
11541158
case <-cs.abort:
1155-
waitDone()
1156-
return nil, cs.abortErr
1159+
select {
1160+
case <-cs.respHeaderRecv:
1161+
// If both cs.respHeaderRecv and cs.abort are signaling,
1162+
// pick respHeaderRecv. The server probably wrote the
1163+
// response and immediately reset the stream.
1164+
// golang.org/issue/49645
1165+
return handleResponseHeaders()
1166+
default:
1167+
waitDone()
1168+
return nil, cs.abortErr
1169+
}
11571170
case <-ctx.Done():
11581171
err := ctx.Err()
11591172
cs.abortStream(err)

0 commit comments

Comments
 (0)