github.com/Andyfoo/golang/x/net@v0.0.0-20190901054642-57c1bf301704/http2/client_conn_pool.go (about)

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