Skip to content

Commit 212d385

Browse files
fraenkelbradfitz
authored andcommitted
net/http: ignore connection closes once done with the connection
Once the connection is put back into the idle pool, the request should not take any action if the connection is closed. Fixes #41600 Change-Id: I5e4ddcdc03cd44f5197ecfbe324638604961de84 Reviewed-on: https://go-review.googlesource.com/c/go/+/257818 Reviewed-by: Brad Fitzpatrick <[email protected]> Trust: Damien Neil <[email protected]>
1 parent 4ef78b0 commit 212d385

File tree

2 files changed

+60
-4
lines changed

2 files changed

+60
-4
lines changed

src/net/http/transport.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -2599,6 +2599,7 @@ func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err err
25992599
var respHeaderTimer <-chan time.Time
26002600
cancelChan := req.Request.Cancel
26012601
ctxDoneChan := req.Context().Done()
2602+
pcClosed := pc.closech
26022603
for {
26032604
testHookWaitResLoop()
26042605
select {
@@ -2618,11 +2619,15 @@ func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err err
26182619
defer timer.Stop() // prevent leaks
26192620
respHeaderTimer = timer.C
26202621
}
2621-
case <-pc.closech:
2622-
if debugRoundTrip {
2623-
req.logf("closech recv: %T %#v", pc.closed, pc.closed)
2622+
case <-pcClosed:
2623+
pcClosed = nil
2624+
// check if we are still using the connection
2625+
if pc.t.replaceReqCanceler(req.cancelKey, nil) {
2626+
if debugRoundTrip {
2627+
req.logf("closech recv: %T %#v", pc.closed, pc.closed)
2628+
}
2629+
return nil, pc.mapRoundTripError(req, startBytesWritten, pc.closed)
26242630
}
2625-
return nil, pc.mapRoundTripError(req, startBytesWritten, pc.closed)
26262631
case <-respHeaderTimer:
26272632
if debugRoundTrip {
26282633
req.logf("timeout waiting for response headers.")

src/net/http/transport_test.go

+51
Original file line numberDiff line numberDiff line change
@@ -6433,3 +6433,54 @@ func TestErrorWriteLoopRace(t *testing.T) {
64336433
testTransportRace(req)
64346434
}
64356435
}
6436+
6437+
// Issue 41600
6438+
// Test that a new request which uses the connection of an active request
6439+
// cannot cause it to be canceled as well.
6440+
func TestCancelRequestWhenSharingConnection(t *testing.T) {
6441+
if testing.Short() {
6442+
t.Skip("skipping in short mode")
6443+
}
6444+
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, req *Request) {
6445+
w.Header().Add("Content-Length", "0")
6446+
}))
6447+
defer ts.Close()
6448+
6449+
client := ts.Client()
6450+
transport := client.Transport.(*Transport)
6451+
transport.MaxIdleConns = 1
6452+
transport.MaxConnsPerHost = 1
6453+
6454+
var wg sync.WaitGroup
6455+
6456+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
6457+
6458+
for i := 0; i < 10; i++ {
6459+
wg.Add(1)
6460+
go func() {
6461+
defer wg.Done()
6462+
for ctx.Err() == nil {
6463+
reqctx, reqcancel := context.WithCancel(ctx)
6464+
go reqcancel()
6465+
req, _ := NewRequestWithContext(reqctx, "GET", ts.URL, nil)
6466+
res, err := client.Do(req)
6467+
if err == nil {
6468+
res.Body.Close()
6469+
}
6470+
}
6471+
}()
6472+
}
6473+
6474+
for ctx.Err() == nil {
6475+
req, _ := NewRequest("GET", ts.URL, nil)
6476+
if res, err := client.Do(req); err != nil {
6477+
t.Errorf("unexpected: %p %v", req, err)
6478+
break
6479+
} else {
6480+
res.Body.Close()
6481+
}
6482+
}
6483+
6484+
cancel()
6485+
wg.Wait()
6486+
}

0 commit comments

Comments
 (0)