Home | History | Annotate | Download | only in http2
      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 
     55 func (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.
     83 type 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.
     91 func (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.
    106 func (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.
    127 func (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 
    156 type addConnCall struct {
    157 	p    *clientConnPool
    158 	done chan struct{} // closed when done
    159 	err  error
    160 }
    161 
    162 func (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 
    177 func (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
    184 func (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 
    200 func (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 
    218 func (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 
    234 func 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.
    252 type noDialClientConnPool struct{ *clientConnPool }
    253 
    254 func (p noDialClientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
    255 	return p.getClientConn(req, addr, noDialOnMiss)
    256 }
    257