blob: 3a67636fe252fb32ade02282896b6b1eb662573c [file] [log] [blame]
khenaidooac637102019-01-14 15:44:34 -05001// 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
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.
62func (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
76func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMiss bool) (*ClientConn, error) {
77 if isConnectionCloseRequest(req) && dialOnMiss {
78 // It gets its own connection.
79 traceGetConn(req, addr)
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] {
89 if st := cc.idleState(); st.canTakeNewRequest {
90 if p.shouldTraceGetConn(st) {
91 traceGetConn(req, addr)
92 }
93 p.mu.Unlock()
94 return cc, nil
95 }
96 }
97 if !dialOnMiss {
98 p.mu.Unlock()
99 return nil, ErrNoCachedConn
100 }
101 traceGetConn(req, addr)
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.
109type dialCall struct {
Andrea Campanella3614a922021-02-25 12:40:42 +0100110 _ incomparable
khenaidooac637102019-01-14 15:44:34 -0500111 p *clientConnPool
112 done chan struct{} // closed when done
113 res *ClientConn // valid after done is closed
114 err error // valid after done is closed
115}
116
117// requires p.mu is held.
118func (p *clientConnPool) getStartDialLocked(addr string) *dialCall {
119 if call, ok := p.dialing[addr]; ok {
120 // A dial is already in-flight. Don't start another.
121 return call
122 }
123 call := &dialCall{p: p, done: make(chan struct{})}
124 if p.dialing == nil {
125 p.dialing = make(map[string]*dialCall)
126 }
127 p.dialing[addr] = call
128 go call.dial(addr)
129 return call
130}
131
132// run in its own goroutine.
133func (c *dialCall) dial(addr string) {
134 const singleUse = false // shared conn
135 c.res, c.err = c.p.t.dialClientConn(addr, singleUse)
136 close(c.done)
137
138 c.p.mu.Lock()
139 delete(c.p.dialing, addr)
140 if c.err == nil {
141 c.p.addConnLocked(addr, c.res)
142 }
143 c.p.mu.Unlock()
144}
145
146// addConnIfNeeded makes a NewClientConn out of c if a connection for key doesn't
147// already exist. It coalesces concurrent calls with the same key.
148// This is used by the http1 Transport code when it creates a new connection. Because
149// the http1 Transport doesn't de-dup TCP dials to outbound hosts (because it doesn't know
150// the protocol), it can get into a situation where it has multiple TLS connections.
151// This code decides which ones live or die.
152// The return value used is whether c was used.
153// c is never closed.
154func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn) (used bool, err error) {
155 p.mu.Lock()
156 for _, cc := range p.conns[key] {
157 if cc.CanTakeNewRequest() {
158 p.mu.Unlock()
159 return false, nil
160 }
161 }
162 call, dup := p.addConnCalls[key]
163 if !dup {
164 if p.addConnCalls == nil {
165 p.addConnCalls = make(map[string]*addConnCall)
166 }
167 call = &addConnCall{
168 p: p,
169 done: make(chan struct{}),
170 }
171 p.addConnCalls[key] = call
172 go call.run(t, key, c)
173 }
174 p.mu.Unlock()
175
176 <-call.done
177 if call.err != nil {
178 return false, call.err
179 }
180 return !dup, nil
181}
182
183type addConnCall struct {
Andrea Campanella3614a922021-02-25 12:40:42 +0100184 _ incomparable
khenaidooac637102019-01-14 15:44:34 -0500185 p *clientConnPool
186 done chan struct{} // closed when done
187 err error
188}
189
190func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
191 cc, err := t.NewClientConn(tc)
192
193 p := c.p
194 p.mu.Lock()
195 if err != nil {
196 c.err = err
197 } else {
198 p.addConnLocked(key, cc)
199 }
200 delete(p.addConnCalls, key)
201 p.mu.Unlock()
202 close(c.done)
203}
204
khenaidooac637102019-01-14 15:44:34 -0500205// p.mu must be held
206func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) {
207 for _, v := range p.conns[key] {
208 if v == cc {
209 return
210 }
211 }
212 if p.conns == nil {
213 p.conns = make(map[string][]*ClientConn)
214 }
215 if p.keys == nil {
216 p.keys = make(map[*ClientConn][]string)
217 }
218 p.conns[key] = append(p.conns[key], cc)
219 p.keys[cc] = append(p.keys[cc], key)
220}
221
222func (p *clientConnPool) MarkDead(cc *ClientConn) {
223 p.mu.Lock()
224 defer p.mu.Unlock()
225 for _, key := range p.keys[cc] {
226 vv, ok := p.conns[key]
227 if !ok {
228 continue
229 }
230 newList := filterOutClientConn(vv, cc)
231 if len(newList) > 0 {
232 p.conns[key] = newList
233 } else {
234 delete(p.conns, key)
235 }
236 }
237 delete(p.keys, cc)
238}
239
240func (p *clientConnPool) closeIdleConnections() {
241 p.mu.Lock()
242 defer p.mu.Unlock()
243 // TODO: don't close a cc if it was just added to the pool
244 // milliseconds ago and has never been used. There's currently
245 // a small race window with the HTTP/1 Transport's integration
246 // where it can add an idle conn just before using it, and
247 // somebody else can concurrently call CloseIdleConns and
248 // break some caller's RoundTrip.
249 for _, vv := range p.conns {
250 for _, cc := range vv {
251 cc.closeIfIdle()
252 }
253 }
254}
255
256func filterOutClientConn(in []*ClientConn, exclude *ClientConn) []*ClientConn {
257 out := in[:0]
258 for _, v := range in {
259 if v != exclude {
260 out = append(out, v)
261 }
262 }
263 // If we filtered it out, zero out the last item to prevent
264 // the GC from seeing it.
265 if len(in) != len(out) {
266 in[len(in)-1] = nil
267 }
268 return out
269}
270
271// noDialClientConnPool is an implementation of http2.ClientConnPool
272// which never dials. We let the HTTP/1.1 client dial and use its TLS
273// connection instead.
274type noDialClientConnPool struct{ *clientConnPool }
275
276func (p noDialClientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
277 return p.getClientConn(req, addr, noDialOnMiss)
278}