Skip to content

Commit 045b33e

Browse files
committed
net/http: close Request.Body when pconn write loop exits early
The pconn write loop closes a request's body after sending the request, but in the case where the write loop exits with an unsent request in writech the body is never closed. Close the request body in this case. Fixes #49621 Change-Id: Id94a92937bbfc0beb1396446f4dee32fd2059c7e Reviewed-on: https://go-review.googlesource.com/c/go/+/461675 TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Damien Neil <[email protected]> Reviewed-by: Cherry Mui <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 78558d5 commit 045b33e

File tree

2 files changed

+45
-0
lines changed

2 files changed

+45
-0
lines changed

src/net/http/transport.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,12 @@ func (t *Transport) roundTrip(req *Request) (*Response, error) {
622622
if e, ok := err.(transportReadFromServerError); ok {
623623
err = e.err
624624
}
625+
if b, ok := req.Body.(*readTrackingBody); ok && !b.didClose {
626+
// Issue 49621: Close the request body if pconn.roundTrip
627+
// didn't do so already. This can happen if the pconn
628+
// write loop exits without reading the write request.
629+
req.closeBody()
630+
}
625631
return nil, err
626632
}
627633
testHookRoundTripRetried()

src/net/http/transport_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4092,6 +4092,45 @@ func testTransportDialCancelRace(t *testing.T, mode testMode) {
40924092
}
40934093
}
40944094

4095+
// https://go.dev/issue/49621
4096+
func TestConnClosedBeforeRequestIsWritten(t *testing.T) {
4097+
run(t, testConnClosedBeforeRequestIsWritten, testNotParallel, []testMode{http1Mode})
4098+
}
4099+
func testConnClosedBeforeRequestIsWritten(t *testing.T, mode testMode) {
4100+
ts := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) {}),
4101+
func(tr *Transport) {
4102+
tr.DialContext = func(_ context.Context, network, addr string) (net.Conn, error) {
4103+
// Connection immediately returns errors.
4104+
return &funcConn{
4105+
read: func([]byte) (int, error) {
4106+
return 0, errors.New("error")
4107+
},
4108+
write: func([]byte) (int, error) {
4109+
return 0, errors.New("error")
4110+
},
4111+
}, nil
4112+
}
4113+
},
4114+
).ts
4115+
// Set a short delay in RoundTrip to give the persistConn time to notice
4116+
// the connection is broken. We want to exercise the path where writeLoop exits
4117+
// before it reads the request to send. If this delay is too short, we may instead
4118+
// exercise the path where writeLoop accepts the request and then fails to write it.
4119+
// That's fine, so long as we get the desired path often enough.
4120+
SetEnterRoundTripHook(func() {
4121+
time.Sleep(1 * time.Millisecond)
4122+
})
4123+
defer SetEnterRoundTripHook(nil)
4124+
var closes int
4125+
_, err := ts.Client().Post(ts.URL, "text/plain", countCloseReader{&closes, strings.NewReader("hello")})
4126+
if err == nil {
4127+
t.Fatalf("expected request to fail, but it did not")
4128+
}
4129+
if closes != 1 {
4130+
t.Errorf("after RoundTrip, request body was closed %v times; want 1", closes)
4131+
}
4132+
}
4133+
40954134
// logWritesConn is a net.Conn that logs each Write call to writes
40964135
// and then proxies to w.
40974136
// It proxies Read calls to a reader it receives from rch.

0 commit comments

Comments
 (0)