1// Copyright 2015 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Transport code's client connection pooling. 6 7package http2 8 9import ( 10 "crypto/tls" 11 "net/http" 12 "sync" 13) 14 15// ClientConnPool manages a pool of HTTP/2 client connections. 16type ClientConnPool interface { 17 GetClientConn(req *http.Request, addr string) (*ClientConn, error) 18 MarkDead(*ClientConn) 19} 20 21// clientConnPoolIdleCloser is the interface implemented by ClientConnPool 22// implementations which can close their idle connections. 23type clientConnPoolIdleCloser interface { 24 ClientConnPool 25 closeIdleConnections() 26} 27 28var ( 29 _ clientConnPoolIdleCloser = (*clientConnPool)(nil) 30 _ clientConnPoolIdleCloser = noDialClientConnPool{} 31) 32 33// TODO: use singleflight for dialing and addConnCalls? 34type clientConnPool struct { 35 t *Transport 36 37 mu sync.Mutex // TODO: maybe switch to RWMutex 38 // TODO: add support for sharing conns based on cert names 39 // (e.g. share conn for googleapis.com and appspot.com) 40 conns map[string][]*ClientConn // key is host:port 41 dialing map[string]*dialCall // currently in-flight dials 42 keys map[*ClientConn][]string 43 addConnCalls map[string]*addConnCall // in-flight addConnIfNeede calls 44} 45 46func (p *clientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) { 47 return p.getClientConn(req, addr, dialOnMiss) 48} 49 50const ( 51 dialOnMiss = true 52 noDialOnMiss = false 53) 54 55func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMiss bool) (*ClientConn, error) { 56 if isConnectionCloseRequest(req) && dialOnMiss { 57 // It gets its own connection. 58 const singleUse = true 59 cc, err := p.t.dialClientConn(addr, singleUse) 60 if err != nil { 61 return nil, err 62 } 63 return cc, nil 64 } 65 p.mu.Lock() 66 for _, cc := range p.conns[addr] { 67 if cc.CanTakeNewRequest() { 68 p.mu.Unlock() 69 return cc, nil 70 } 71 } 72 if !dialOnMiss { 73 p.mu.Unlock() 74 return nil, ErrNoCachedConn 75 } 76 call := p.getStartDialLocked(addr) 77 p.mu.Unlock() 78 <-call.done 79 return call.res, call.err 80} 81 82// dialCall is an in-flight Transport dial call to a host. 83type dialCall struct { 84 p *clientConnPool 85 done chan struct{} // closed when done 86 res *ClientConn // valid after done is closed 87 err error // valid after done is closed 88} 89 90// requires p.mu is held. 91func (p *clientConnPool) getStartDialLocked(addr string) *dialCall { 92 if call, ok := p.dialing[addr]; ok { 93 // A dial is already in-flight. Don't start another. 94 return call 95 } 96 call := &dialCall{p: p, done: make(chan struct{})} 97 if p.dialing == nil { 98 p.dialing = make(map[string]*dialCall) 99 } 100 p.dialing[addr] = call 101 go call.dial(addr) 102 return call 103} 104 105// run in its own goroutine. 106func (c *dialCall) dial(addr string) { 107 const singleUse = false // shared conn 108 c.res, c.err = c.p.t.dialClientConn(addr, singleUse) 109 close(c.done) 110 111 c.p.mu.Lock() 112 delete(c.p.dialing, addr) 113 if c.err == nil { 114 c.p.addConnLocked(addr, c.res) 115 } 116 c.p.mu.Unlock() 117} 118 119// addConnIfNeeded makes a NewClientConn out of c if a connection for key doesn't 120// already exist. It coalesces concurrent calls with the same key. 121// This is used by the http1 Transport code when it creates a new connection. Because 122// the http1 Transport doesn't de-dup TCP dials to outbound hosts (because it doesn't know 123// the protocol), it can get into a situation where it has multiple TLS connections. 124// This code decides which ones live or die. 125// The return value used is whether c was used. 126// c is never closed. 127func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn) (used bool, err error) { 128 p.mu.Lock() 129 for _, cc := range p.conns[key] { 130 if cc.CanTakeNewRequest() { 131 p.mu.Unlock() 132 return false, nil 133 } 134 } 135 call, dup := p.addConnCalls[key] 136 if !dup { 137 if p.addConnCalls == nil { 138 p.addConnCalls = make(map[string]*addConnCall) 139 } 140 call = &addConnCall{ 141 p: p, 142 done: make(chan struct{}), 143 } 144 p.addConnCalls[key] = call 145 go call.run(t, key, c) 146 } 147 p.mu.Unlock() 148 149 <-call.done 150 if call.err != nil { 151 return false, call.err 152 } 153 return !dup, nil 154} 155 156type addConnCall struct { 157 p *clientConnPool 158 done chan struct{} // closed when done 159 err error 160} 161 162func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) { 163 cc, err := t.NewClientConn(tc) 164 165 p := c.p 166 p.mu.Lock() 167 if err != nil { 168 c.err = err 169 } else { 170 p.addConnLocked(key, cc) 171 } 172 delete(p.addConnCalls, key) 173 p.mu.Unlock() 174 close(c.done) 175} 176 177func (p *clientConnPool) addConn(key string, cc *ClientConn) { 178 p.mu.Lock() 179 p.addConnLocked(key, cc) 180 p.mu.Unlock() 181} 182 183// p.mu must be held 184func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) { 185 for _, v := range p.conns[key] { 186 if v == cc { 187 return 188 } 189 } 190 if p.conns == nil { 191 p.conns = make(map[string][]*ClientConn) 192 } 193 if p.keys == nil { 194 p.keys = make(map[*ClientConn][]string) 195 } 196 p.conns[key] = append(p.conns[key], cc) 197 p.keys[cc] = append(p.keys[cc], key) 198} 199 200func (p *clientConnPool) MarkDead(cc *ClientConn) { 201 p.mu.Lock() 202 defer p.mu.Unlock() 203 for _, key := range p.keys[cc] { 204 vv, ok := p.conns[key] 205 if !ok { 206 continue 207 } 208 newList := filterOutClientConn(vv, cc) 209 if len(newList) > 0 { 210 p.conns[key] = newList 211 } else { 212 delete(p.conns, key) 213 } 214 } 215 delete(p.keys, cc) 216} 217 218func (p *clientConnPool) closeIdleConnections() { 219 p.mu.Lock() 220 defer p.mu.Unlock() 221 // TODO: don't close a cc if it was just added to the pool 222 // milliseconds ago and has never been used. There's currently 223 // a small race window with the HTTP/1 Transport's integration 224 // where it can add an idle conn just before using it, and 225 // somebody else can concurrently call CloseIdleConns and 226 // break some caller's RoundTrip. 227 for _, vv := range p.conns { 228 for _, cc := range vv { 229 cc.closeIfIdle() 230 } 231 } 232} 233 234func filterOutClientConn(in []*ClientConn, exclude *ClientConn) []*ClientConn { 235 out := in[:0] 236 for _, v := range in { 237 if v != exclude { 238 out = append(out, v) 239 } 240 } 241 // If we filtered it out, zero out the last item to prevent 242 // the GC from seeing it. 243 if len(in) != len(out) { 244 in[len(in)-1] = nil 245 } 246 return out 247} 248 249// noDialClientConnPool is an implementation of http2.ClientConnPool 250// which never dials. We let the HTTP/1.1 client dial and use its TLS 251// connection instead. 252type noDialClientConnPool struct{ *clientConnPool } 253 254func (p noDialClientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) { 255 return p.getClientConn(req, addr, noDialOnMiss) 256} 257