blob: bdf5652b01ce679fb0bdf6b6cf9e4dd82371ba2d [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001// 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}