]> Cypherpunks repositories - gostls13.git/commitdiff
http: add Transport.MaxIdleConnsPerHost
authorBrad Fitzpatrick <bradfitz@golang.org>
Thu, 31 Mar 2011 19:58:50 +0000 (12:58 -0700)
committerBrad Fitzpatrick <bradfitz@golang.org>
Thu, 31 Mar 2011 19:58:50 +0000 (12:58 -0700)
R=rsc
CC=golang-dev
https://golang.org/cl/4280079

src/pkg/http/transport.go
src/pkg/http/transport_test.go

index ed7843bc713c0f6d0ca4fb35af614e0185283bca..7f85c8c281d7d2d3891d90062726311b249b282d 100644 (file)
@@ -24,6 +24,10 @@ import (
 // environment variables.
 var DefaultTransport RoundTripper = &Transport{}
 
+// DefaultMaxIdleConnsPerHost is the default value of Transport's
+// MaxIdleConnsPerHost.
+const DefaultMaxIdleConnsPerHost = 2
+
 // Transport is an implementation of RoundTripper that supports http,
 // https, and http proxies (for either http or https with CONNECT).
 // Transport can also cache connections for future re-use.
@@ -31,11 +35,17 @@ type Transport struct {
        lk       sync.Mutex
        idleConn map[string][]*persistConn
 
-       // TODO: tunables on max cached connections (total, per-server), duration
+       // TODO: tunable on global max cached connections
+       // TODO: tunable on timeout on cached connections
        // TODO: optional pipelining
 
        IgnoreEnvironment bool // don't look at environment variables for proxy configuration
        DisableKeepAlives bool
+
+       // MaxIdleConnsPerHost, if non-zero, controls the maximum idle
+       // (keep-alive) to keep to keep per-host.  If zero,
+       // DefaultMaxIdleConnsPerHost is used.
+       MaxIdleConnsPerHost int
 }
 
 // RoundTrip implements the RoundTripper interface.
@@ -147,7 +157,7 @@ func (cm *connectMethod) proxyAuth() string {
 func (t *Transport) putIdleConn(pconn *persistConn) {
        t.lk.Lock()
        defer t.lk.Unlock()
-       if t.DisableKeepAlives {
+       if t.DisableKeepAlives || t.MaxIdleConnsPerHost < 0 {
                pconn.close()
                return
        }
@@ -155,6 +165,14 @@ func (t *Transport) putIdleConn(pconn *persistConn) {
                return
        }
        key := pconn.cacheKey
+       max := t.MaxIdleConnsPerHost
+       if max == 0 {
+               max = DefaultMaxIdleConnsPerHost
+       }
+       if len(t.idleConn[key]) >= max {
+               pconn.close()
+               return
+       }
        t.idleConn[key] = append(t.idleConn[key], pconn)
 }
 
index 5c3e1cdb582a92d0a2b11c2719e710646e1f7ba1..69a17df856e06190d90960c189fb7a046febc0d6 100644 (file)
@@ -164,7 +164,7 @@ func TestTransportIdleCacheKeys(t *testing.T) {
        }
 
        if e := "|http|" + ts.Listener.Addr().String(); keys[0] != e {
-               t.Logf("Expected idle cache key %q; got %q", e, keys[0])
+               t.Errorf("Expected idle cache key %q; got %q", e, keys[0])
        }
 
        tr.CloseIdleConnections()
@@ -173,6 +173,58 @@ func TestTransportIdleCacheKeys(t *testing.T) {
        }
 }
 
+func TestTransportMaxPerHostIdleConns(t *testing.T) {
+       ch := make(chan string)
+       ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+               fmt.Fprintf(w, "%s", <-ch)
+       }))
+       defer ts.Close()
+       maxIdleConns := 2
+       tr := &Transport{DisableKeepAlives: false, MaxIdleConnsPerHost: maxIdleConns}
+       c := &Client{Transport: tr}
+
+       // Start 3 outstanding requests (will hang until we write to
+       // ch)
+       donech := make(chan bool)
+       doReq := func() {
+               c.Get(ts.URL)
+               donech <- true
+       }
+       go doReq()
+       go doReq()
+       go doReq()
+
+       if e, g := 0, len(tr.IdleConnKeysForTesting()); e != g {
+               t.Fatalf("Before writes, expected %d idle conn cache keys; got %d", e, g)
+       }
+
+       ch <- "res1"
+       <-donech
+       keys := tr.IdleConnKeysForTesting()
+       if e, g := 1, len(keys); e != g {
+               t.Fatalf("after first response, expected %d idle conn cache keys; got %d", e, g)
+       }
+       cacheKey := "|http|" + ts.Listener.Addr().String()
+       if keys[0] != cacheKey {
+               t.Fatalf("Expected idle cache key %q; got %q", cacheKey, keys[0])
+       }
+       if e, g := 1, tr.IdleConnCountForTesting(cacheKey); e != g {
+               t.Errorf("after first response, expected %d idle conns; got %d", e, g)
+       }
+
+       ch <- "res2"
+       <-donech
+       if e, g := 2, tr.IdleConnCountForTesting(cacheKey); e != g {
+               t.Errorf("after second response, expected %d idle conns; got %d", e, g)
+       }
+
+       ch <- "res3"
+       <-donech
+       if e, g := maxIdleConns, tr.IdleConnCountForTesting(cacheKey); e != g {
+               t.Errorf("after third response, still expected %d idle conns; got %d", e, g)
+       }
+}
+
 func TestTransportServerClosingUnexpectedly(t *testing.T) {
        ts := httptest.NewServer(hostPortHandler)
        defer ts.Close()