Skip to content

Commit 606d4a3

Browse files
zombiezenrsc
authored andcommitted
net/http: ensure Request.Body.Close is called once and only once
Makes *Request.write always close the body, so that callers no longer have to close the body on returned errors, which was the trigger for double-close behavior. Fixes #40382 Change-Id: I128f7ec70415f240d82154cfca134b3f692191e3 Reviewed-on: https://go-review.googlesource.com/c/go/+/257819 Reviewed-by: Damien Neil <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]> Trust: Damien Neil <[email protected]> Trust: Brad Fitzpatrick <[email protected]> Run-TryBot: Damien Neil <[email protected]> TryBot-Result: Go Bot <[email protected]>
1 parent dfee333 commit 606d4a3

File tree

4 files changed

+97
-10
lines changed

4 files changed

+97
-10
lines changed

src/net/http/client_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2026,3 +2026,60 @@ func TestClientPopulatesNilResponseBody(t *testing.T) {
20262026
t.Errorf("substitute Response.Body was unexpectedly non-empty: %q", b)
20272027
}
20282028
}
2029+
2030+
// Issue 40382: Client calls Close multiple times on Request.Body.
2031+
func TestClientCallsCloseOnlyOnce(t *testing.T) {
2032+
setParallel(t)
2033+
defer afterTest(t)
2034+
cst := newClientServerTest(t, h1Mode, HandlerFunc(func(w ResponseWriter, r *Request) {
2035+
w.WriteHeader(StatusNoContent)
2036+
}))
2037+
defer cst.close()
2038+
2039+
// Issue occurred non-deterministically: needed to occur after a successful
2040+
// write (into TCP buffer) but before end of body.
2041+
for i := 0; i < 50 && !t.Failed(); i++ {
2042+
body := &issue40382Body{t: t, n: 300000}
2043+
req, err := NewRequest(MethodPost, cst.ts.URL, body)
2044+
if err != nil {
2045+
t.Fatal(err)
2046+
}
2047+
resp, err := cst.tr.RoundTrip(req)
2048+
if err != nil {
2049+
t.Fatal(err)
2050+
}
2051+
resp.Body.Close()
2052+
}
2053+
}
2054+
2055+
// issue40382Body is an io.ReadCloser for TestClientCallsCloseOnlyOnce.
2056+
// Its Read reads n bytes before returning io.EOF.
2057+
// Its Close returns nil but fails the test if called more than once.
2058+
type issue40382Body struct {
2059+
t *testing.T
2060+
n int
2061+
closeCallsAtomic int32
2062+
}
2063+
2064+
func (b *issue40382Body) Read(p []byte) (int, error) {
2065+
switch {
2066+
case b.n == 0:
2067+
return 0, io.EOF
2068+
case b.n < len(p):
2069+
p = p[:b.n]
2070+
fallthrough
2071+
default:
2072+
for i := range p {
2073+
p[i] = 'x'
2074+
}
2075+
b.n -= len(p)
2076+
return len(p), nil
2077+
}
2078+
}
2079+
2080+
func (b *issue40382Body) Close() error {
2081+
if atomic.AddInt32(&b.closeCallsAtomic, 1) == 2 {
2082+
b.t.Error("Body closed more than once")
2083+
}
2084+
return nil
2085+
}

src/net/http/request.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ var errMissingHost = errors.New("http: Request.Write on Request with no Host or
544544

545545
// extraHeaders may be nil
546546
// waitForContinue may be nil
547+
// always closes body
547548
func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitForContinue func() bool) (err error) {
548549
trace := httptrace.ContextClientTrace(r.Context())
549550
if trace != nil && trace.WroteRequest != nil {
@@ -553,6 +554,15 @@ func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitF
553554
})
554555
}()
555556
}
557+
closed := false
558+
defer func() {
559+
if closed {
560+
return
561+
}
562+
if closeErr := r.closeBody(); closeErr != nil && err == nil {
563+
err = closeErr
564+
}
565+
}()
556566

557567
// Find the target host. Prefer the Host: header, but if that
558568
// is not given, use the host from the request URL.
@@ -671,6 +681,7 @@ func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitF
671681
trace.Wait100Continue()
672682
}
673683
if !waitForContinue() {
684+
closed = true
674685
r.closeBody()
675686
return nil
676687
}
@@ -683,6 +694,7 @@ func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitF
683694
}
684695

685696
// Write body and trailer
697+
closed = true
686698
err = tw.writeBody(w)
687699
if err != nil {
688700
if tw.bodyReadError == err {
@@ -1387,10 +1399,11 @@ func (r *Request) wantsClose() bool {
13871399
return hasToken(r.Header.get("Connection"), "close")
13881400
}
13891401

1390-
func (r *Request) closeBody() {
1391-
if r.Body != nil {
1392-
r.Body.Close()
1402+
func (r *Request) closeBody() error {
1403+
if r.Body == nil {
1404+
return nil
13931405
}
1406+
return r.Body.Close()
13941407
}
13951408

13961409
func (r *Request) isReplayable() bool {

src/net/http/transfer.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,9 +330,18 @@ func (t *transferWriter) writeHeader(w io.Writer, trace *httptrace.ClientTrace)
330330
return nil
331331
}
332332

333-
func (t *transferWriter) writeBody(w io.Writer) error {
334-
var err error
333+
// always closes t.BodyCloser
334+
func (t *transferWriter) writeBody(w io.Writer) (err error) {
335335
var ncopy int64
336+
closed := false
337+
defer func() {
338+
if closed || t.BodyCloser == nil {
339+
return
340+
}
341+
if closeErr := t.BodyCloser.Close(); closeErr != nil && err == nil {
342+
err = closeErr
343+
}
344+
}()
336345

337346
// Write body. We "unwrap" the body first if it was wrapped in a
338347
// nopCloser or readTrackingBody. This is to ensure that we can take advantage of
@@ -369,6 +378,7 @@ func (t *transferWriter) writeBody(w io.Writer) error {
369378
}
370379
}
371380
if t.BodyCloser != nil {
381+
closed = true
372382
if err := t.BodyCloser.Close(); err != nil {
373383
return err
374384
}

src/net/http/transport.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -623,14 +623,20 @@ var errCannotRewind = errors.New("net/http: cannot rewind body after connection
623623

624624
type readTrackingBody struct {
625625
io.ReadCloser
626-
didRead bool
626+
didRead bool
627+
didClose bool
627628
}
628629

629630
func (r *readTrackingBody) Read(data []byte) (int, error) {
630631
r.didRead = true
631632
return r.ReadCloser.Read(data)
632633
}
633634

635+
func (r *readTrackingBody) Close() error {
636+
r.didClose = true
637+
return r.ReadCloser.Close()
638+
}
639+
634640
// setupRewindBody returns a new request with a custom body wrapper
635641
// that can report whether the body needs rewinding.
636642
// This lets rewindBody avoid an error result when the request
@@ -649,10 +655,12 @@ func setupRewindBody(req *Request) *Request {
649655
// rewindBody takes care of closing req.Body when appropriate
650656
// (in all cases except when rewindBody returns req unmodified).
651657
func rewindBody(req *Request) (rewound *Request, err error) {
652-
if req.Body == nil || req.Body == NoBody || !req.Body.(*readTrackingBody).didRead {
658+
if req.Body == nil || req.Body == NoBody || (!req.Body.(*readTrackingBody).didRead && !req.Body.(*readTrackingBody).didClose) {
653659
return req, nil // nothing to rewind
654660
}
655-
req.closeBody()
661+
if !req.Body.(*readTrackingBody).didClose {
662+
req.closeBody()
663+
}
656664
if req.GetBody == nil {
657665
return nil, errCannotRewind
658666
}
@@ -2379,7 +2387,7 @@ func (pc *persistConn) writeLoop() {
23792387
// Request.Body are high priority.
23802388
// Set it here before sending on the
23812389
// channels below or calling
2382-
// pc.close() which tears town
2390+
// pc.close() which tears down
23832391
// connections and causes other
23842392
// errors.
23852393
wr.req.setError(err)
@@ -2388,7 +2396,6 @@ func (pc *persistConn) writeLoop() {
23882396
err = pc.bw.Flush()
23892397
}
23902398
if err != nil {
2391-
wr.req.Request.closeBody()
23922399
if pc.nwrite == startBytesWritten {
23932400
err = nothingWrittenError{err}
23942401
}

0 commit comments

Comments
 (0)