• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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