Skip to content

Commit b74b905

Browse files
net/http: fix request canceler leak on connection close
writeLoop goroutine closes persistConn closech in case of request body write error which in turn finishes readLoop without removing request canceler. Fixes #61708
1 parent 2ab9218 commit b74b905

File tree

2 files changed

+63
-0
lines changed

2 files changed

+63
-0
lines changed

src/net/http/transport.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2277,6 +2277,7 @@ func (pc *persistConn) readLoop() {
22772277
pc.t.cancelRequest(rc.cancelKey, rc.req.Context().Err())
22782278
case <-pc.closech:
22792279
alive = false
2280+
pc.t.setReqCanceler(rc.cancelKey, nil)
22802281
}
22812282

22822283
testHookReadLoopBeforeNextRead()

src/net/http/transport_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6969,3 +6969,65 @@ func testProxyAuthHeader(t *testing.T, mode testMode) {
69696969
}
69706970
resp.Body.Close()
69716971
}
6972+
6973+
// Issue 61708
6974+
func TestTransportReqCancelerCleanupOnRequestBodyWriteError(t *testing.T) {
6975+
ln := newLocalListener(t)
6976+
addr := ln.Addr().String()
6977+
6978+
done := make(chan struct{})
6979+
go func() {
6980+
conn, err := ln.Accept()
6981+
if err != nil {
6982+
t.Errorf("ln.Accept: %v", err)
6983+
return
6984+
}
6985+
// Start reading request before sending response to avoid
6986+
// "Unsolicited response received on idle HTTP channel" RoundTrip error.
6987+
if _, err := io.ReadFull(conn, make([]byte, 1)); err != nil {
6988+
t.Errorf("conn.Read: %v", err)
6989+
return
6990+
}
6991+
io.WriteString(conn, "HTTP/1.1 200\r\nContent-Length: 3\r\n\r\nfoo")
6992+
<-done
6993+
conn.Close()
6994+
}()
6995+
6996+
didRead := make(chan bool)
6997+
SetReadLoopBeforeNextReadHook(func() { didRead <- true })
6998+
defer SetReadLoopBeforeNextReadHook(nil)
6999+
7000+
tr := &Transport{}
7001+
7002+
// Send a request with a body guaranteed to fail on write.
7003+
req, err := NewRequest("POST", "http://"+addr, io.LimitReader(neverEnding('x'), 1<<30))
7004+
if err != nil {
7005+
t.Fatalf("NewRequest: %v", err)
7006+
}
7007+
7008+
resp, err := tr.RoundTrip(req)
7009+
if err != nil {
7010+
t.Fatalf("tr.RoundTrip: %v", err)
7011+
}
7012+
7013+
close(done)
7014+
7015+
// Before closing response body wait for readLoopDone goroutine
7016+
// to complete due to closed connection by writeLoop.
7017+
<-didRead
7018+
7019+
resp.Body.Close()
7020+
7021+
// Verify no outstanding requests after readLoop/writeLoop
7022+
// goroutines shut down.
7023+
waitCondition(t, 10*time.Millisecond, func(d time.Duration) bool {
7024+
n := tr.NumPendingRequestsForTesting()
7025+
if n > 0 {
7026+
if d > 0 {
7027+
t.Logf("pending requests = %d after %v (want 0)", n, d)
7028+
}
7029+
return false
7030+
}
7031+
return true
7032+
})
7033+
}

0 commit comments

Comments
 (0)