Commit 1a058cd0 authored by Michael Fraenkel's avatar Michael Fraenkel Committed by Brad Fitzpatrick

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 #34941

Change-Id: I5070717d5d9effec78016005fa4910593500c8cf
Reviewed-on: https://go-review.googlesource.com/c/go/+/202087Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent e7ce8627
......@@ -545,9 +545,10 @@ func (t *Transport) roundTrip(req *Request) (*Response, error) {
_, isH2DialError := pconn.alt.(http2erringRoundTripper)
if http2isNoCachedConnError(err) || isH2DialError {
t.removeIdleConn(pconn)
if t.removeIdleConn(pconn) {
t.decConnsPerHost(pconn.cacheKey)
}
}
if !pconn.shouldRetryRequest(req, err) {
// Issue 16465: return underlying net.Conn.Read error from peek,
// as we've historically done.
......@@ -958,26 +959,28 @@ func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) {
}
// removeIdleConn marks pconn as dead.
func (t *Transport) removeIdleConn(pconn *persistConn) {
func (t *Transport) removeIdleConn(pconn *persistConn) bool {
t.idleMu.Lock()
defer t.idleMu.Unlock()
t.removeIdleConnLocked(pconn)
return t.removeIdleConnLocked(pconn)
}
// t.idleMu must be held.
func (t *Transport) removeIdleConnLocked(pconn *persistConn) {
func (t *Transport) removeIdleConnLocked(pconn *persistConn) bool {
if pconn.idleTimer != nil {
pconn.idleTimer.Stop()
}
t.idleLRU.remove(pconn)
key := pconn.cacheKey
pconns := t.idleConn[key]
var removed bool
switch len(pconns) {
case 0:
// Nothing
case 1:
if pconns[0] == pconn {
delete(t.idleConn, key)
removed = true
}
default:
for i, v := range pconns {
......@@ -988,9 +991,11 @@ func (t *Transport) removeIdleConnLocked(pconn *persistConn) {
// conns at the end.
copy(pconns[i:], pconns[i+1:])
t.idleConn[key] = pconns[:len(pconns)-1]
removed = true
break
}
}
return removed
}
func (t *Transport) setReqCanceler(r *Request, fn func(error)) {
......
......@@ -5893,3 +5893,59 @@ func TestDontCacheBrokenHTTP2Conn(t *testing.T) {
t.Errorf("GotConn calls = %v; want %v", got, want)
}
}
// Issue 34941
// When the client has too many concurrent requests on a single connection,
// http.http2noCachedConnError is reported on multiple requests. There should
// only be one decrement regardless of the number of failures.
func TestTransportDecrementConnWhenIdleConnRemoved(t *testing.T) {
defer afterTest(t)
h := HandlerFunc(func(w ResponseWriter, r *Request) {
_, err := w.Write([]byte("foo"))
if err != nil {
t.Fatalf("Write: %v", err)
}
})
ts := httptest.NewUnstartedServer(h)
ts.EnableHTTP2 = true
ts.StartTLS()
defer ts.Close()
c := ts.Client()
tr := c.Transport.(*Transport)
tr.MaxConnsPerHost = 1
if err := ExportHttp2ConfigureTransport(tr); err != nil {
t.Fatalf("ExportHttp2ConfigureTransport: %v", err)
}
errCh := make(chan error, 300)
doReq := func() {
resp, err := c.Get(ts.URL)
if err != nil {
errCh <- fmt.Errorf("request failed: %v", err)
return
}
defer resp.Body.Close()
_, err = ioutil.ReadAll(resp.Body)
if err != nil {
errCh <- fmt.Errorf("read body failed: %v", err)
}
}
var wg sync.WaitGroup
for i := 0; i < 300; i++ {
wg.Add(1)
go func() {
defer wg.Done()
doReq()
}()
}
wg.Wait()
close(errCh)
for err := range errCh {
t.Errorf("error occurred: %v", err)
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment