Skip to content

Commit 224a180

Browse files
fraenkeltoothrot
authored andcommitted
[release-branch.go1.13] net/http: only decrement connection count if we removed a connection
The connection count must only be decremented if the persistent connection was also removed. Fixes #36583 Change-Id: I5070717d5d9effec78016005fa4910593500c8cf Reviewed-on: https://go-review.googlesource.com/c/go/+/202087 Reviewed-by: Brad Fitzpatrick <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/go/+/215177 Run-TryBot: Alexander Rakoczy <[email protected]> Reviewed-by: Alexander Rakoczy <[email protected]>
1 parent 17ac3f6 commit 224a180

File tree

2 files changed

+66
-5
lines changed

2 files changed

+66
-5
lines changed

src/net/http/transport.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -542,8 +542,9 @@ func (t *Transport) roundTrip(req *Request) (*Response, error) {
542542

543543
_, isH2DialError := pconn.alt.(http2erringRoundTripper)
544544
if http2isNoCachedConnError(err) || isH2DialError {
545-
t.removeIdleConn(pconn)
546-
t.decConnsPerHost(pconn.cacheKey)
545+
if t.removeIdleConn(pconn) {
546+
t.decConnsPerHost(pconn.cacheKey)
547+
}
547548
}
548549
if !pconn.shouldRetryRequest(req, err) {
549550
// Issue 16465: return underlying net.Conn.Read error from peek,
@@ -966,26 +967,28 @@ func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) {
966967
}
967968

968969
// removeIdleConn marks pconn as dead.
969-
func (t *Transport) removeIdleConn(pconn *persistConn) {
970+
func (t *Transport) removeIdleConn(pconn *persistConn) bool {
970971
t.idleMu.Lock()
971972
defer t.idleMu.Unlock()
972-
t.removeIdleConnLocked(pconn)
973+
return t.removeIdleConnLocked(pconn)
973974
}
974975

975976
// t.idleMu must be held.
976-
func (t *Transport) removeIdleConnLocked(pconn *persistConn) {
977+
func (t *Transport) removeIdleConnLocked(pconn *persistConn) bool {
977978
if pconn.idleTimer != nil {
978979
pconn.idleTimer.Stop()
979980
}
980981
t.idleLRU.remove(pconn)
981982
key := pconn.cacheKey
982983
pconns := t.idleConn[key]
984+
var removed bool
983985
switch len(pconns) {
984986
case 0:
985987
// Nothing
986988
case 1:
987989
if pconns[0] == pconn {
988990
delete(t.idleConn, key)
991+
removed = true
989992
}
990993
default:
991994
for i, v := range pconns {
@@ -996,9 +999,11 @@ func (t *Transport) removeIdleConnLocked(pconn *persistConn) {
996999
// conns at the end.
9971000
copy(pconns[i:], pconns[i+1:])
9981001
t.idleConn[key] = pconns[:len(pconns)-1]
1002+
removed = true
9991003
break
10001004
}
10011005
}
1006+
return removed
10021007
}
10031008

10041009
func (t *Transport) setReqCanceler(r *Request, fn func(error)) {

src/net/http/transport_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5832,3 +5832,59 @@ func TestDontCacheBrokenHTTP2Conn(t *testing.T) {
58325832
t.Errorf("GotConn calls = %v; want %v", got, want)
58335833
}
58345834
}
5835+
5836+
// Issue 34941
5837+
// When the client has too many concurrent requests on a single connection,
5838+
// http.http2noCachedConnError is reported on multiple requests. There should
5839+
// only be one decrement regardless of the number of failures.
5840+
func TestTransportDecrementConnWhenIdleConnRemoved(t *testing.T) {
5841+
defer afterTest(t)
5842+
5843+
h := HandlerFunc(func(w ResponseWriter, r *Request) {
5844+
_, err := w.Write([]byte("foo"))
5845+
if err != nil {
5846+
t.Fatalf("Write: %v", err)
5847+
}
5848+
})
5849+
5850+
ts := httptest.NewUnstartedServer(h)
5851+
ts.TLS = &tls.Config{NextProtos: []string{"h2"}}
5852+
ts.StartTLS()
5853+
defer ts.Close()
5854+
5855+
c := ts.Client()
5856+
tr := c.Transport.(*Transport)
5857+
tr.MaxConnsPerHost = 1
5858+
if err := ExportHttp2ConfigureTransport(tr); err != nil {
5859+
t.Fatalf("ExportHttp2ConfigureTransport: %v", err)
5860+
}
5861+
5862+
errCh := make(chan error, 300)
5863+
doReq := func() {
5864+
resp, err := c.Get(ts.URL)
5865+
if err != nil {
5866+
errCh <- fmt.Errorf("request failed: %v", err)
5867+
return
5868+
}
5869+
defer resp.Body.Close()
5870+
_, err = ioutil.ReadAll(resp.Body)
5871+
if err != nil {
5872+
errCh <- fmt.Errorf("read body failed: %v", err)
5873+
}
5874+
}
5875+
5876+
var wg sync.WaitGroup
5877+
for i := 0; i < 300; i++ {
5878+
wg.Add(1)
5879+
go func() {
5880+
defer wg.Done()
5881+
doReq()
5882+
}()
5883+
}
5884+
wg.Wait()
5885+
close(errCh)
5886+
5887+
for err := range errCh {
5888+
t.Errorf("error occurred: %v", err)
5889+
}
5890+
}

0 commit comments

Comments
 (0)