blob: 652bc11a029b6b8d80ef44200d6a3b5b431850ea [file] [log] [blame]
Don Newton98fd8812019-09-23 15:15:02 -04001// 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 (
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +000010 "context"
Don Newton98fd8812019-09-23 15:15:02 -040011 "crypto/tls"
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +000012 "errors"
Don Newton98fd8812019-09-23 15:15:02 -040013 "net/http"
14 "sync"
15)
16
17// ClientConnPool manages a pool of HTTP/2 client connections.
18type ClientConnPool interface {
19 GetClientConn(req *http.Request, addr string) (*ClientConn, error)
20 MarkDead(*ClientConn)
21}
22
23// clientConnPoolIdleCloser is the interface implemented by ClientConnPool
24// implementations which can close their idle connections.
25type clientConnPoolIdleCloser interface {
26 ClientConnPool
27 closeIdleConnections()
28}
29
30var (
31 _ clientConnPoolIdleCloser = (*clientConnPool)(nil)
32 _ clientConnPoolIdleCloser = noDialClientConnPool{}
33)
34
35// TODO: use singleflight for dialing and addConnCalls?
36type clientConnPool struct {
37 t *Transport
38
39 mu sync.Mutex // TODO: maybe switch to RWMutex
40 // TODO: add support for sharing conns based on cert names
41 // (e.g. share conn for googleapis.com and appspot.com)
42 conns map[string][]*ClientConn // key is host:port
43 dialing map[string]*dialCall // currently in-flight dials
44 keys map[*ClientConn][]string
45 addConnCalls map[string]*addConnCall // in-flight addConnIfNeede calls
46}
47
48func (p *clientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
49 return p.getClientConn(req, addr, dialOnMiss)
50}
51
52const (
53 dialOnMiss = true
54 noDialOnMiss = false
55)
56
57// shouldTraceGetConn reports whether getClientConn should call any
58// ClientTrace.GetConn hook associated with the http.Request.
59//
60// This complexity is needed to avoid double calls of the GetConn hook
61// during the back-and-forth between net/http and x/net/http2 (when the
62// net/http.Transport is upgraded to also speak http2), as well as support
63// the case where x/net/http2 is being used directly.
64func (p *clientConnPool) shouldTraceGetConn(st clientConnIdleState) bool {
65 // If our Transport wasn't made via ConfigureTransport, always
66 // trace the GetConn hook if provided, because that means the
67 // http2 package is being used directly and it's the one
68 // dialing, as opposed to net/http.
69 if _, ok := p.t.ConnPool.(noDialClientConnPool); !ok {
70 return true
71 }
72 // Otherwise, only use the GetConn hook if this connection has
73 // been used previously for other requests. For fresh
74 // connections, the net/http package does the dialing.
75 return !st.freshConn
76}
77
78func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMiss bool) (*ClientConn, error) {
79 if isConnectionCloseRequest(req) && dialOnMiss {
80 // It gets its own connection.
81 traceGetConn(req, addr)
82 const singleUse = true
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +000083 cc, err := p.t.dialClientConn(req.Context(), addr, singleUse)
Don Newton98fd8812019-09-23 15:15:02 -040084 if err != nil {
85 return nil, err
86 }
87 return cc, nil
88 }
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +000089 for {
90 p.mu.Lock()
91 for _, cc := range p.conns[addr] {
92 if st := cc.idleState(); st.canTakeNewRequest {
93 if p.shouldTraceGetConn(st) {
94 traceGetConn(req, addr)
95 }
96 p.mu.Unlock()
97 return cc, nil
Don Newton98fd8812019-09-23 15:15:02 -040098 }
Don Newton98fd8812019-09-23 15:15:02 -040099 }
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000100 if !dialOnMiss {
101 p.mu.Unlock()
102 return nil, ErrNoCachedConn
103 }
104 traceGetConn(req, addr)
105 call := p.getStartDialLocked(req.Context(), addr)
Don Newton98fd8812019-09-23 15:15:02 -0400106 p.mu.Unlock()
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000107 <-call.done
108 if shouldRetryDial(call, req) {
109 continue
110 }
111 return call.res, call.err
Don Newton98fd8812019-09-23 15:15:02 -0400112 }
Don Newton98fd8812019-09-23 15:15:02 -0400113}
114
115// dialCall is an in-flight Transport dial call to a host.
116type dialCall struct {
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000117 _ incomparable
118 p *clientConnPool
119 // the context associated with the request
120 // that created this dialCall
121 ctx context.Context
Don Newton98fd8812019-09-23 15:15:02 -0400122 done chan struct{} // closed when done
123 res *ClientConn // valid after done is closed
124 err error // valid after done is closed
125}
126
127// requires p.mu is held.
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000128func (p *clientConnPool) getStartDialLocked(ctx context.Context, addr string) *dialCall {
Don Newton98fd8812019-09-23 15:15:02 -0400129 if call, ok := p.dialing[addr]; ok {
130 // A dial is already in-flight. Don't start another.
131 return call
132 }
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000133 call := &dialCall{p: p, done: make(chan struct{}), ctx: ctx}
Don Newton98fd8812019-09-23 15:15:02 -0400134 if p.dialing == nil {
135 p.dialing = make(map[string]*dialCall)
136 }
137 p.dialing[addr] = call
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000138 go call.dial(call.ctx, addr)
Don Newton98fd8812019-09-23 15:15:02 -0400139 return call
140}
141
142// run in its own goroutine.
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000143func (c *dialCall) dial(ctx context.Context, addr string) {
Don Newton98fd8812019-09-23 15:15:02 -0400144 const singleUse = false // shared conn
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000145 c.res, c.err = c.p.t.dialClientConn(ctx, addr, singleUse)
Don Newton98fd8812019-09-23 15:15:02 -0400146 close(c.done)
147
148 c.p.mu.Lock()
149 delete(c.p.dialing, addr)
150 if c.err == nil {
151 c.p.addConnLocked(addr, c.res)
152 }
153 c.p.mu.Unlock()
154}
155
156// addConnIfNeeded makes a NewClientConn out of c if a connection for key doesn't
157// already exist. It coalesces concurrent calls with the same key.
158// This is used by the http1 Transport code when it creates a new connection. Because
159// the http1 Transport doesn't de-dup TCP dials to outbound hosts (because it doesn't know
160// the protocol), it can get into a situation where it has multiple TLS connections.
161// This code decides which ones live or die.
162// The return value used is whether c was used.
163// c is never closed.
164func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn) (used bool, err error) {
165 p.mu.Lock()
166 for _, cc := range p.conns[key] {
167 if cc.CanTakeNewRequest() {
168 p.mu.Unlock()
169 return false, nil
170 }
171 }
172 call, dup := p.addConnCalls[key]
173 if !dup {
174 if p.addConnCalls == nil {
175 p.addConnCalls = make(map[string]*addConnCall)
176 }
177 call = &addConnCall{
178 p: p,
179 done: make(chan struct{}),
180 }
181 p.addConnCalls[key] = call
182 go call.run(t, key, c)
183 }
184 p.mu.Unlock()
185
186 <-call.done
187 if call.err != nil {
188 return false, call.err
189 }
190 return !dup, nil
191}
192
193type addConnCall struct {
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000194 _ incomparable
Don Newton98fd8812019-09-23 15:15:02 -0400195 p *clientConnPool
196 done chan struct{} // closed when done
197 err error
198}
199
200func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
201 cc, err := t.NewClientConn(tc)
202
203 p := c.p
204 p.mu.Lock()
205 if err != nil {
206 c.err = err
207 } else {
208 p.addConnLocked(key, cc)
209 }
210 delete(p.addConnCalls, key)
211 p.mu.Unlock()
212 close(c.done)
213}
214
Don Newton98fd8812019-09-23 15:15:02 -0400215// p.mu must be held
216func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) {
217 for _, v := range p.conns[key] {
218 if v == cc {
219 return
220 }
221 }
222 if p.conns == nil {
223 p.conns = make(map[string][]*ClientConn)
224 }
225 if p.keys == nil {
226 p.keys = make(map[*ClientConn][]string)
227 }
228 p.conns[key] = append(p.conns[key], cc)
229 p.keys[cc] = append(p.keys[cc], key)
230}
231
232func (p *clientConnPool) MarkDead(cc *ClientConn) {
233 p.mu.Lock()
234 defer p.mu.Unlock()
235 for _, key := range p.keys[cc] {
236 vv, ok := p.conns[key]
237 if !ok {
238 continue
239 }
240 newList := filterOutClientConn(vv, cc)
241 if len(newList) > 0 {
242 p.conns[key] = newList
243 } else {
244 delete(p.conns, key)
245 }
246 }
247 delete(p.keys, cc)
248}
249
250func (p *clientConnPool) closeIdleConnections() {
251 p.mu.Lock()
252 defer p.mu.Unlock()
253 // TODO: don't close a cc if it was just added to the pool
254 // milliseconds ago and has never been used. There's currently
255 // a small race window with the HTTP/1 Transport's integration
256 // where it can add an idle conn just before using it, and
257 // somebody else can concurrently call CloseIdleConns and
258 // break some caller's RoundTrip.
259 for _, vv := range p.conns {
260 for _, cc := range vv {
261 cc.closeIfIdle()
262 }
263 }
264}
265
266func filterOutClientConn(in []*ClientConn, exclude *ClientConn) []*ClientConn {
267 out := in[:0]
268 for _, v := range in {
269 if v != exclude {
270 out = append(out, v)
271 }
272 }
273 // If we filtered it out, zero out the last item to prevent
274 // the GC from seeing it.
275 if len(in) != len(out) {
276 in[len(in)-1] = nil
277 }
278 return out
279}
280
281// noDialClientConnPool is an implementation of http2.ClientConnPool
282// which never dials. We let the HTTP/1.1 client dial and use its TLS
283// connection instead.
284type noDialClientConnPool struct{ *clientConnPool }
285
286func (p noDialClientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
287 return p.getClientConn(req, addr, noDialOnMiss)
288}
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000289
290// shouldRetryDial reports whether the current request should
291// retry dialing after the call finished unsuccessfully, for example
292// if the dial was canceled because of a context cancellation or
293// deadline expiry.
294func shouldRetryDial(call *dialCall, req *http.Request) bool {
295 if call.err == nil {
296 // No error, no need to retry
297 return false
298 }
299 if call.ctx == req.Context() {
300 // If the call has the same context as the request, the dial
301 // should not be retried, since any cancellation will have come
302 // from this request.
303 return false
304 }
305 if !errors.Is(call.err, context.Canceled) && !errors.Is(call.err, context.DeadlineExceeded) {
306 // If the call error is not because of a context cancellation or a deadline expiry,
307 // the dial should not be retried.
308 return false
309 }
310 // Only retry if the error is a context cancellation error or deadline expiry
311 // and the context associated with the call was canceled or expired.
312 return call.ctx.Err() != nil
313}