Skip to content

Commit 107f3e3

Browse files
committed
http2: don't return from RoundTrip until request body is closed
Moving the Request.Body.Close call out from the ClientConn mutex results in some cases where RoundTrip returns while the Close is still in progress. This should be legal (RoundTrip explicitly allows for this), but net/http relies on Close never being called after RoundTrip returns. Add additional synchronization to ensure Close calls complete before RoundTrip returns. Fixes golang/go#55896 Change-Id: Ie3d4773966745e83987d219927929cb56ec1a7ad Reviewed-on: https://go-review.googlesource.com/c/net/+/435535 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Run-TryBot: Damien Neil <dneil@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
1 parent f486391 commit 107f3e3

File tree

1 file changed

+35
-49
lines changed

1 file changed

+35
-49
lines changed

‎http2/transport.go

Lines changed: 35 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -345,8 +345,8 @@ type clientStream struct {
345345
readErr error // sticky read error; owned by transportResponseBody.Read
346346

347347
reqBody io.ReadCloser
348-
reqBodyContentLength int64 // -1 means unknown
349-
reqBodyClosed bool // body has been closed; guarded by cc.mu
348+
reqBodyContentLength int64 // -1 means unknown
349+
reqBodyClosed chan struct{} // guarded by cc.mu; non-nil on Close, closed when done
350350

351351
// owned by writeRequest:
352352
sentEndStream bool // sent an END_STREAM flag to the peer
@@ -376,46 +376,48 @@ func (cs *clientStream) get1xxTraceFunc() func(int, textproto.MIMEHeader) error
376376
}
377377

378378
func (cs *clientStream) abortStream(err error) {
379-
var reqBody io.ReadCloser
380-
defer func() {
381-
if reqBody != nil {
382-
reqBody.Close()
383-
}
384-
}()
385379
cs.cc.mu.Lock()
386380
defer cs.cc.mu.Unlock()
387-
reqBody = cs.abortStreamLocked(err)
381+
cs.abortStreamLocked(err)
388382
}
389383

390-
func (cs *clientStream) abortStreamLocked(err error) io.ReadCloser {
384+
func (cs *clientStream) abortStreamLocked(err error) {
391385
cs.abortOnce.Do(func() {
392386
cs.abortErr = err
393387
close(cs.abort)
394388
})
395-
var reqBody io.ReadCloser
396-
if cs.reqBody != nil && !cs.reqBodyClosed {
397-
cs.reqBodyClosed = true
398-
reqBody = cs.reqBody
389+
if cs.reqBody != nil {
390+
cs.closeReqBodyLocked()
399391
}
400392
// TODO(dneil): Clean up tests where cs.cc.cond is nil.
401393
if cs.cc.cond != nil {
402394
// Wake up writeRequestBody if it is waiting on flow control.
403395
cs.cc.cond.Broadcast()
404396
}
405-
return reqBody
406397
}
407398

408399
func (cs *clientStream) abortRequestBodyWrite() {
409400
cc := cs.cc
410401
cc.mu.Lock()
411402
defer cc.mu.Unlock()
412-
if cs.reqBody != nil && !cs.reqBodyClosed {
413-
cs.reqBody.Close()
414-
cs.reqBodyClosed = true
403+
if cs.reqBody != nil && cs.reqBodyClosed == nil {
404+
cs.closeReqBodyLocked()
415405
cc.cond.Broadcast()
416406
}
417407
}
418408

409+
func (cs *clientStream) closeReqBodyLocked() {
410+
if cs.reqBodyClosed != nil {
411+
return
412+
}
413+
cs.reqBodyClosed = make(chan struct{})
414+
reqBodyClosed := cs.reqBodyClosed
415+
go func() {
416+
cs.reqBody.Close()
417+
close(reqBodyClosed)
418+
}()
419+
}
420+
419421
type stickyErrWriter struct {
420422
conn net.Conn
421423
timeout time.Duration
@@ -771,12 +773,6 @@ func (cc *ClientConn) SetDoNotReuse() {
771773
}
772774

773775
func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
774-
var reqBodiesToClose []io.ReadCloser
775-
defer func() {
776-
for _, reqBody := range reqBodiesToClose {
777-
reqBody.Close()
778-
}
779-
}()
780776
cc.mu.Lock()
781777
defer cc.mu.Unlock()
782778

@@ -793,10 +789,7 @@ func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
793789
last := f.LastStreamID
794790
for streamID, cs := range cc.streams {
795791
if streamID > last {
796-
reqBody := cs.abortStreamLocked(errClientConnGotGoAway)
797-
if reqBody != nil {
798-
reqBodiesToClose = append(reqBodiesToClose, reqBody)
799-
}
792+
cs.abortStreamLocked(errClientConnGotGoAway)
800793
}
801794
}
802795
}
@@ -1049,19 +1042,11 @@ func (cc *ClientConn) sendGoAway() error {
10491042
func (cc *ClientConn) closeForError(err error) {
10501043
cc.mu.Lock()
10511044
cc.closed = true
1052-
1053-
var reqBodiesToClose []io.ReadCloser
10541045
for _, cs := range cc.streams {
1055-
reqBody := cs.abortStreamLocked(err)
1056-
if reqBody != nil {
1057-
reqBodiesToClose = append(reqBodiesToClose, reqBody)
1058-
}
1046+
cs.abortStreamLocked(err)
10591047
}
10601048
cc.cond.Broadcast()
10611049
cc.mu.Unlock()
1062-
for _, reqBody := range reqBodiesToClose {
1063-
reqBody.Close()
1064-
}
10651050
cc.closeConn()
10661051
}
10671052

@@ -1458,11 +1443,19 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
14581443
// and in multiple cases: server replies <=299 and >299
14591444
// while still writing request body
14601445
cc.mu.Lock()
1446+
mustCloseBody := false
1447+
if cs.reqBody != nil && cs.reqBodyClosed == nil {
1448+
mustCloseBody = true
1449+
cs.reqBodyClosed = make(chan struct{})
1450+
}
14611451
bodyClosed := cs.reqBodyClosed
1462-
cs.reqBodyClosed = true
14631452
cc.mu.Unlock()
1464-
if !bodyClosed && cs.reqBody != nil {
1453+
if mustCloseBody {
14651454
cs.reqBody.Close()
1455+
close(bodyClosed)
1456+
}
1457+
if bodyClosed != nil {
1458+
<-bodyClosed
14661459
}
14671460

14681461
if err != nil && cs.sentEndStream {
@@ -1642,7 +1635,7 @@ func (cs *clientStream) writeRequestBody(req *http.Request) (err error) {
16421635
}
16431636
if err != nil {
16441637
cc.mu.Lock()
1645-
bodyClosed := cs.reqBodyClosed
1638+
bodyClosed := cs.reqBodyClosed != nil
16461639
cc.mu.Unlock()
16471640
switch {
16481641
case bodyClosed:
@@ -1737,7 +1730,7 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
17371730
if cc.closed {
17381731
return 0, errClientConnClosed
17391732
}
1740-
if cs.reqBodyClosed {
1733+
if cs.reqBodyClosed != nil {
17411734
return 0, errStopReqBodyWrite
17421735
}
17431736
select {
@@ -2110,24 +2103,17 @@ func (rl *clientConnReadLoop) cleanup() {
21102103
}
21112104
cc.closed = true
21122105

2113-
var reqBodiesToClose []io.ReadCloser
21142106
for _, cs := range cc.streams {
21152107
select {
21162108
case <-cs.peerClosed:
21172109
// The server closed the stream before closing the conn,
21182110
// so no need to interrupt it.
21192111
default:
2120-
reqBody := cs.abortStreamLocked(err)
2121-
if reqBody != nil {
2122-
reqBodiesToClose = append(reqBodiesToClose, reqBody)
2123-
}
2112+
cs.abortStreamLocked(err)
21242113
}
21252114
}
21262115
cc.cond.Broadcast()
21272116
cc.mu.Unlock()
2128-
for _, reqBody := range reqBodiesToClose {
2129-
reqBody.Close()
2130-
}
21312117
}
21322118

21332119
// countReadFrameError calls Transport.CountError with a string

0 commit comments

Comments
 (0)