Commit 46161cd0 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

net/http: fix memory leak in Transport

Fixes #5794

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/10747044
parent 64441d6d
...@@ -48,6 +48,12 @@ func (t *Transport) IdleConnCountForTesting(cacheKey string) int { ...@@ -48,6 +48,12 @@ func (t *Transport) IdleConnCountForTesting(cacheKey string) int {
return len(conns) return len(conns)
} }
func (t *Transport) IdleConnChMapSizeForTesting() int {
t.idleMu.Lock()
defer t.idleMu.Unlock()
return len(t.idleConnCh)
}
func NewTestTimeoutHandler(handler Handler, ch <-chan time.Time) Handler { func NewTestTimeoutHandler(handler Handler, ch <-chan time.Time) Handler {
f := func() <-chan time.Time { f := func() <-chan time.Time {
return ch return ch
......
...@@ -217,6 +217,7 @@ func (t *Transport) CloseIdleConnections() { ...@@ -217,6 +217,7 @@ func (t *Transport) CloseIdleConnections() {
t.idleMu.Lock() t.idleMu.Lock()
m := t.idleConn m := t.idleConn
t.idleConn = nil t.idleConn = nil
t.idleConnCh = nil
t.idleMu.Unlock() t.idleMu.Unlock()
if m == nil { if m == nil {
return return
...@@ -295,8 +296,10 @@ func (t *Transport) putIdleConn(pconn *persistConn) bool { ...@@ -295,8 +296,10 @@ func (t *Transport) putIdleConn(pconn *persistConn) bool {
max = DefaultMaxIdleConnsPerHost max = DefaultMaxIdleConnsPerHost
} }
t.idleMu.Lock() t.idleMu.Lock()
waitingDialer := t.idleConnCh[key]
select { select {
case t.idleConnCh[key] <- pconn: case waitingDialer <- pconn:
// We're done with this pconn and somebody else is // We're done with this pconn and somebody else is
// currently waiting for a conn of this type (they're // currently waiting for a conn of this type (they're
// actively dialing, but this conn is ready // actively dialing, but this conn is ready
...@@ -305,6 +308,11 @@ func (t *Transport) putIdleConn(pconn *persistConn) bool { ...@@ -305,6 +308,11 @@ func (t *Transport) putIdleConn(pconn *persistConn) bool {
t.idleMu.Unlock() t.idleMu.Unlock()
return true return true
default: default:
if waitingDialer != nil {
// They had populated this, but their dial won
// first, so we can clean up this map entry.
delete(t.idleConnCh, key)
}
} }
if t.idleConn == nil { if t.idleConn == nil {
t.idleConn = make(map[string][]*persistConn) t.idleConn = make(map[string][]*persistConn)
...@@ -324,7 +332,13 @@ func (t *Transport) putIdleConn(pconn *persistConn) bool { ...@@ -324,7 +332,13 @@ func (t *Transport) putIdleConn(pconn *persistConn) bool {
return true return true
} }
// getIdleConnCh returns a channel to receive and return idle
// persistent connection for the given connectMethod.
// It may return nil, if persistent connections are not being used.
func (t *Transport) getIdleConnCh(cm *connectMethod) chan *persistConn { func (t *Transport) getIdleConnCh(cm *connectMethod) chan *persistConn {
if t.DisableKeepAlives {
return nil
}
key := cm.key() key := cm.key()
t.idleMu.Lock() t.idleMu.Lock()
defer t.idleMu.Unlock() defer t.idleMu.Unlock()
......
...@@ -1575,6 +1575,41 @@ func TestProxyFromEnvironment(t *testing.T) { ...@@ -1575,6 +1575,41 @@ func TestProxyFromEnvironment(t *testing.T) {
} }
} }
func TestIdleConnChannelLeak(t *testing.T) {
var mu sync.Mutex
var n int
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
mu.Lock()
n++
mu.Unlock()
}))
defer ts.Close()
tr := &Transport{
Dial: func(netw, addr string) (net.Conn, error) {
return net.Dial(netw, ts.Listener.Addr().String())
},
}
defer tr.CloseIdleConnections()
c := &Client{Transport: tr}
// First, without keep-alives.
for _, disableKeep := range []bool{true, false} {
tr.DisableKeepAlives = disableKeep
for i := 0; i < 5; i++ {
_, err := c.Get(fmt.Sprintf("http://foo-host-%d.tld/", i))
if err != nil {
t.Fatal(err)
}
}
if got := tr.IdleConnChMapSizeForTesting(); got != 0 {
t.Fatalf("ForDisableKeepAlives = %v, map size = %d; want 0", disableKeep, got)
}
}
}
// rgz is a gzip quine that uncompresses to itself. // rgz is a gzip quine that uncompresses to itself.
var rgz = []byte{ var rgz = []byte{
0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00,
......
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