]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
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 | ||
7 | package http2 | |
8 | ||
9 | import ( | |
10 | "crypto/tls" | |
11 | "net/http" | |
12 | "sync" | |
13 | ) | |
14 | ||
15 | // ClientConnPool manages a pool of HTTP/2 client connections. | |
16 | type 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. | |
23 | type clientConnPoolIdleCloser interface { | |
24 | ClientConnPool | |
25 | closeIdleConnections() | |
26 | } | |
27 | ||
28 | var ( | |
29 | _ clientConnPoolIdleCloser = (*clientConnPool)(nil) | |
30 | _ clientConnPoolIdleCloser = noDialClientConnPool{} | |
31 | ) | |
32 | ||
33 | // TODO: use singleflight for dialing and addConnCalls? | |
34 | type 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 | ||
46 | func (p *clientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) { | |
47 | return p.getClientConn(req, addr, dialOnMiss) | |
48 | } | |
49 | ||
50 | const ( | |
51 | dialOnMiss = true | |
52 | noDialOnMiss = false | |
53 | ) | |
54 | ||
107c1cdb ND |
55 | // shouldTraceGetConn reports whether getClientConn should call any |
56 | // ClientTrace.GetConn hook associated with the http.Request. | |
57 | // | |
58 | // This complexity is needed to avoid double calls of the GetConn hook | |
59 | // during the back-and-forth between net/http and x/net/http2 (when the | |
60 | // net/http.Transport is upgraded to also speak http2), as well as support | |
61 | // the case where x/net/http2 is being used directly. | |
62 | func (p *clientConnPool) shouldTraceGetConn(st clientConnIdleState) bool { | |
63 | // If our Transport wasn't made via ConfigureTransport, always | |
64 | // trace the GetConn hook if provided, because that means the | |
65 | // http2 package is being used directly and it's the one | |
66 | // dialing, as opposed to net/http. | |
67 | if _, ok := p.t.ConnPool.(noDialClientConnPool); !ok { | |
68 | return true | |
69 | } | |
70 | // Otherwise, only use the GetConn hook if this connection has | |
71 | // been used previously for other requests. For fresh | |
72 | // connections, the net/http package does the dialing. | |
73 | return !st.freshConn | |
74 | } | |
75 | ||
15c0b25d AP |
76 | func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMiss bool) (*ClientConn, error) { |
77 | if isConnectionCloseRequest(req) && dialOnMiss { | |
78 | // It gets its own connection. | |
107c1cdb | 79 | traceGetConn(req, addr) |
15c0b25d AP |
80 | const singleUse = true |
81 | cc, err := p.t.dialClientConn(addr, singleUse) | |
82 | if err != nil { | |
83 | return nil, err | |
84 | } | |
85 | return cc, nil | |
86 | } | |
87 | p.mu.Lock() | |
88 | for _, cc := range p.conns[addr] { | |
107c1cdb ND |
89 | if st := cc.idleState(); st.canTakeNewRequest { |
90 | if p.shouldTraceGetConn(st) { | |
91 | traceGetConn(req, addr) | |
92 | } | |
15c0b25d AP |
93 | p.mu.Unlock() |
94 | return cc, nil | |
95 | } | |
96 | } | |
97 | if !dialOnMiss { | |
98 | p.mu.Unlock() | |
99 | return nil, ErrNoCachedConn | |
100 | } | |
107c1cdb | 101 | traceGetConn(req, addr) |
15c0b25d AP |
102 | call := p.getStartDialLocked(addr) |
103 | p.mu.Unlock() | |
104 | <-call.done | |
105 | return call.res, call.err | |
106 | } | |
107 | ||
108 | // dialCall is an in-flight Transport dial call to a host. | |
109 | type dialCall struct { | |
110 | p *clientConnPool | |
111 | done chan struct{} // closed when done | |
112 | res *ClientConn // valid after done is closed | |
113 | err error // valid after done is closed | |
114 | } | |
115 | ||
116 | // requires p.mu is held. | |
117 | func (p *clientConnPool) getStartDialLocked(addr string) *dialCall { | |
118 | if call, ok := p.dialing[addr]; ok { | |
119 | // A dial is already in-flight. Don't start another. | |
120 | return call | |
121 | } | |
122 | call := &dialCall{p: p, done: make(chan struct{})} | |
123 | if p.dialing == nil { | |
124 | p.dialing = make(map[string]*dialCall) | |
125 | } | |
126 | p.dialing[addr] = call | |
127 | go call.dial(addr) | |
128 | return call | |
129 | } | |
130 | ||
131 | // run in its own goroutine. | |
132 | func (c *dialCall) dial(addr string) { | |
133 | const singleUse = false // shared conn | |
134 | c.res, c.err = c.p.t.dialClientConn(addr, singleUse) | |
135 | close(c.done) | |
136 | ||
137 | c.p.mu.Lock() | |
138 | delete(c.p.dialing, addr) | |
139 | if c.err == nil { | |
140 | c.p.addConnLocked(addr, c.res) | |
141 | } | |
142 | c.p.mu.Unlock() | |
143 | } | |
144 | ||
145 | // addConnIfNeeded makes a NewClientConn out of c if a connection for key doesn't | |
146 | // already exist. It coalesces concurrent calls with the same key. | |
147 | // This is used by the http1 Transport code when it creates a new connection. Because | |
148 | // the http1 Transport doesn't de-dup TCP dials to outbound hosts (because it doesn't know | |
149 | // the protocol), it can get into a situation where it has multiple TLS connections. | |
150 | // This code decides which ones live or die. | |
151 | // The return value used is whether c was used. | |
152 | // c is never closed. | |
153 | func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn) (used bool, err error) { | |
154 | p.mu.Lock() | |
155 | for _, cc := range p.conns[key] { | |
156 | if cc.CanTakeNewRequest() { | |
157 | p.mu.Unlock() | |
158 | return false, nil | |
159 | } | |
160 | } | |
161 | call, dup := p.addConnCalls[key] | |
162 | if !dup { | |
163 | if p.addConnCalls == nil { | |
164 | p.addConnCalls = make(map[string]*addConnCall) | |
165 | } | |
166 | call = &addConnCall{ | |
167 | p: p, | |
168 | done: make(chan struct{}), | |
169 | } | |
170 | p.addConnCalls[key] = call | |
171 | go call.run(t, key, c) | |
172 | } | |
173 | p.mu.Unlock() | |
174 | ||
175 | <-call.done | |
176 | if call.err != nil { | |
177 | return false, call.err | |
178 | } | |
179 | return !dup, nil | |
180 | } | |
181 | ||
182 | type addConnCall struct { | |
183 | p *clientConnPool | |
184 | done chan struct{} // closed when done | |
185 | err error | |
186 | } | |
187 | ||
188 | func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) { | |
189 | cc, err := t.NewClientConn(tc) | |
190 | ||
191 | p := c.p | |
192 | p.mu.Lock() | |
193 | if err != nil { | |
194 | c.err = err | |
195 | } else { | |
196 | p.addConnLocked(key, cc) | |
197 | } | |
198 | delete(p.addConnCalls, key) | |
199 | p.mu.Unlock() | |
200 | close(c.done) | |
201 | } | |
202 | ||
203 | func (p *clientConnPool) addConn(key string, cc *ClientConn) { | |
204 | p.mu.Lock() | |
205 | p.addConnLocked(key, cc) | |
206 | p.mu.Unlock() | |
207 | } | |
208 | ||
209 | // p.mu must be held | |
210 | func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) { | |
211 | for _, v := range p.conns[key] { | |
212 | if v == cc { | |
213 | return | |
214 | } | |
215 | } | |
216 | if p.conns == nil { | |
217 | p.conns = make(map[string][]*ClientConn) | |
218 | } | |
219 | if p.keys == nil { | |
220 | p.keys = make(map[*ClientConn][]string) | |
221 | } | |
222 | p.conns[key] = append(p.conns[key], cc) | |
223 | p.keys[cc] = append(p.keys[cc], key) | |
224 | } | |
225 | ||
226 | func (p *clientConnPool) MarkDead(cc *ClientConn) { | |
227 | p.mu.Lock() | |
228 | defer p.mu.Unlock() | |
229 | for _, key := range p.keys[cc] { | |
230 | vv, ok := p.conns[key] | |
231 | if !ok { | |
232 | continue | |
233 | } | |
234 | newList := filterOutClientConn(vv, cc) | |
235 | if len(newList) > 0 { | |
236 | p.conns[key] = newList | |
237 | } else { | |
238 | delete(p.conns, key) | |
239 | } | |
240 | } | |
241 | delete(p.keys, cc) | |
242 | } | |
243 | ||
244 | func (p *clientConnPool) closeIdleConnections() { | |
245 | p.mu.Lock() | |
246 | defer p.mu.Unlock() | |
247 | // TODO: don't close a cc if it was just added to the pool | |
248 | // milliseconds ago and has never been used. There's currently | |
249 | // a small race window with the HTTP/1 Transport's integration | |
250 | // where it can add an idle conn just before using it, and | |
251 | // somebody else can concurrently call CloseIdleConns and | |
252 | // break some caller's RoundTrip. | |
253 | for _, vv := range p.conns { | |
254 | for _, cc := range vv { | |
255 | cc.closeIfIdle() | |
256 | } | |
257 | } | |
258 | } | |
259 | ||
260 | func filterOutClientConn(in []*ClientConn, exclude *ClientConn) []*ClientConn { | |
261 | out := in[:0] | |
262 | for _, v := range in { | |
263 | if v != exclude { | |
264 | out = append(out, v) | |
265 | } | |
266 | } | |
267 | // If we filtered it out, zero out the last item to prevent | |
268 | // the GC from seeing it. | |
269 | if len(in) != len(out) { | |
270 | in[len(in)-1] = nil | |
271 | } | |
272 | return out | |
273 | } | |
274 | ||
275 | // noDialClientConnPool is an implementation of http2.ClientConnPool | |
276 | // which never dials. We let the HTTP/1.1 client dial and use its TLS | |
277 | // connection instead. | |
278 | type noDialClientConnPool struct{ *clientConnPool } | |
279 | ||
280 | func (p noDialClientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) { | |
281 | return p.getClientConn(req, addr, noDialOnMiss) | |
282 | } |