github.com/useflyent/fhttp@v0.0.0-20211004035111-333f430cfbbf/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  	"sync"
    12  
    13  	http "github.com/useflyent/fhttp"
    14  )
    15  
    16  // ClientConnPool manages a pool of HTTP/2 client connections.
    17  type ClientConnPool interface {
    18  	GetClientConn(req *http.Request, addr string) (*ClientConn, error)
    19  	MarkDead(*ClientConn)
    20  }
    21  
    22  // clientConnPoolIdleCloser is the interface implemented by ClientConnPool
    23  // implementations which can close their idle connections.
    24  type clientConnPoolIdleCloser interface {
    25  	ClientConnPool
    26  	closeIdleConnections()
    27  }
    28  
    29  var (
    30  	_ clientConnPoolIdleCloser = (*clientConnPool)(nil)
    31  	_ clientConnPoolIdleCloser = noDialClientConnPool{}
    32  )
    33  
    34  // TODO: use singleflight for dialing and addConnCalls?
    35  type clientConnPool struct {
    36  	t *Transport
    37  
    38  	mu sync.Mutex // TODO: maybe switch to RWMutex
    39  	// TODO: add support for sharing conns based on cert names
    40  	// (e.g. share conn for googleapis.com and appspot.com)
    41  	conns        map[string][]*ClientConn // key is host:port
    42  	dialing      map[string]*dialCall     // currently in-flight dials
    43  	keys         map[*ClientConn][]string
    44  	addConnCalls map[string]*addConnCall // in-flight addConnIfNeede calls
    45  }
    46  
    47  func (p *clientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
    48  	return p.getClientConn(req, addr, dialOnMiss)
    49  }
    50  
    51  const (
    52  	dialOnMiss   = true
    53  	noDialOnMiss = false
    54  )
    55  
    56  // shouldTraceGetConn reports whether getClientConn should call any
    57  // ClientTrace.GetConn hook associated with the http.Request.
    58  //
    59  // This complexity is needed to avoid double calls of the GetConn hook
    60  // during the back-and-forth between net/http and x/net/http2 (when the
    61  // net/http.Transport is upgraded to also speak http2), as well as support
    62  // the case where x/net/http2 is being used directly.
    63  func (p *clientConnPool) shouldTraceGetConn(st clientConnIdleState) bool {
    64  	// If our Transport wasn't made via ConfigureTransport, always
    65  	// trace the GetConn hook if provided, because that means the
    66  	// http2 package is being used directly and it's the one
    67  	// dialing, as opposed to net/http.
    68  	if _, ok := p.t.ConnPool.(noDialClientConnPool); !ok {
    69  		return true
    70  	}
    71  	// Otherwise, only use the GetConn hook if this connection has
    72  	// been used previously for other requests. For fresh
    73  	// connections, the net/http package does the dialing.
    74  	return !st.freshConn
    75  }
    76  
    77  func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMiss bool) (*ClientConn, error) {
    78  	if isConnectionCloseRequest(req) && dialOnMiss {
    79  		// It gets its own connection.
    80  		traceGetConn(req, addr)
    81  		const singleUse = true
    82  		cc, err := p.t.dialClientConn(addr, singleUse)
    83  		if err != nil {
    84  			return nil, err
    85  		}
    86  		return cc, nil
    87  	}
    88  	p.mu.Lock()
    89  	for _, cc := range p.conns[addr] {
    90  		if st := cc.idleState(); st.canTakeNewRequest {
    91  			if p.shouldTraceGetConn(st) {
    92  				traceGetConn(req, addr)
    93  			}
    94  			p.mu.Unlock()
    95  			return cc, nil
    96  		}
    97  	}
    98  	if !dialOnMiss {
    99  		p.mu.Unlock()
   100  		return nil, ErrNoCachedConn
   101  	}
   102  	traceGetConn(req, addr)
   103  	call := p.getStartDialLocked(addr)
   104  	p.mu.Unlock()
   105  	<-call.done
   106  	return call.res, call.err
   107  }
   108  
   109  // dialCall is an in-flight Transport dial call to a host.
   110  type dialCall struct {
   111  	_    incomparable
   112  	p    *clientConnPool
   113  	done chan struct{} // closed when done
   114  	res  *ClientConn   // valid after done is closed
   115  	err  error         // valid after done is closed
   116  }
   117  
   118  // requires p.mu is held.
   119  func (p *clientConnPool) getStartDialLocked(addr string) *dialCall {
   120  	if call, ok := p.dialing[addr]; ok {
   121  		// A dial is already in-flight. Don't start another.
   122  		return call
   123  	}
   124  	call := &dialCall{p: p, done: make(chan struct{})}
   125  	if p.dialing == nil {
   126  		p.dialing = make(map[string]*dialCall)
   127  	}
   128  	p.dialing[addr] = call
   129  	go call.dial(addr)
   130  	return call
   131  }
   132  
   133  // run in its own goroutine.
   134  func (c *dialCall) dial(addr string) {
   135  	const singleUse = false // shared conn
   136  	c.res, c.err = c.p.t.dialClientConn(addr, singleUse)
   137  	close(c.done)
   138  
   139  	c.p.mu.Lock()
   140  	delete(c.p.dialing, addr)
   141  	if c.err == nil {
   142  		c.p.addConnLocked(addr, c.res)
   143  	}
   144  	c.p.mu.Unlock()
   145  }
   146  
   147  // addConnIfNeeded makes a NewClientConn out of c if a connection for key doesn't
   148  // already exist. It coalesces concurrent calls with the same key.
   149  // This is used by the http1 Transport code when it creates a new connection. Because
   150  // the http1 Transport doesn't de-dup TCP dials to outbound hosts (because it doesn't know
   151  // the protocol), it can get into a situation where it has multiple TLS connections.
   152  // This code decides which ones live or die.
   153  // The return value used is whether c was used.
   154  // c is never closed.
   155  func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn) (used bool, err error) {
   156  	p.mu.Lock()
   157  	for _, cc := range p.conns[key] {
   158  		if cc.CanTakeNewRequest() {
   159  			p.mu.Unlock()
   160  			return false, nil
   161  		}
   162  	}
   163  	call, dup := p.addConnCalls[key]
   164  	if !dup {
   165  		if p.addConnCalls == nil {
   166  			p.addConnCalls = make(map[string]*addConnCall)
   167  		}
   168  		call = &addConnCall{
   169  			p:    p,
   170  			done: make(chan struct{}),
   171  		}
   172  		p.addConnCalls[key] = call
   173  		go call.run(t, key, c)
   174  	}
   175  	p.mu.Unlock()
   176  
   177  	<-call.done
   178  	if call.err != nil {
   179  		return false, call.err
   180  	}
   181  	return !dup, nil
   182  }
   183  
   184  type addConnCall struct {
   185  	_    incomparable
   186  	p    *clientConnPool
   187  	done chan struct{} // closed when done
   188  	err  error
   189  }
   190  
   191  func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
   192  	cc, err := t.NewClientConn(tc)
   193  
   194  	p := c.p
   195  	p.mu.Lock()
   196  	if err != nil {
   197  		c.err = err
   198  	} else {
   199  		p.addConnLocked(key, cc)
   200  	}
   201  	delete(p.addConnCalls, key)
   202  	p.mu.Unlock()
   203  	close(c.done)
   204  }
   205  
   206  // p.mu must be held
   207  func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) {
   208  	for _, v := range p.conns[key] {
   209  		if v == cc {
   210  			return
   211  		}
   212  	}
   213  	if p.conns == nil {
   214  		p.conns = make(map[string][]*ClientConn)
   215  	}
   216  	if p.keys == nil {
   217  		p.keys = make(map[*ClientConn][]string)
   218  	}
   219  	p.conns[key] = append(p.conns[key], cc)
   220  	p.keys[cc] = append(p.keys[cc], key)
   221  }
   222  
   223  func (p *clientConnPool) MarkDead(cc *ClientConn) {
   224  	p.mu.Lock()
   225  	defer p.mu.Unlock()
   226  	for _, key := range p.keys[cc] {
   227  		vv, ok := p.conns[key]
   228  		if !ok {
   229  			continue
   230  		}
   231  		newList := filterOutClientConn(vv, cc)
   232  		if len(newList) > 0 {
   233  			p.conns[key] = newList
   234  		} else {
   235  			delete(p.conns, key)
   236  		}
   237  	}
   238  	delete(p.keys, cc)
   239  }
   240  
   241  func (p *clientConnPool) closeIdleConnections() {
   242  	p.mu.Lock()
   243  	defer p.mu.Unlock()
   244  	// TODO: don't close a cc if it was just added to the pool
   245  	// milliseconds ago and has never been used. There's currently
   246  	// a small race window with the HTTP/1 Transport's integration
   247  	// where it can add an idle conn just before using it, and
   248  	// somebody else can concurrently call CloseIdleConns and
   249  	// break some caller's RoundTrip.
   250  	for _, vv := range p.conns {
   251  		for _, cc := range vv {
   252  			cc.closeIfIdle()
   253  		}
   254  	}
   255  }
   256  
   257  func filterOutClientConn(in []*ClientConn, exclude *ClientConn) []*ClientConn {
   258  	out := in[:0]
   259  	for _, v := range in {
   260  		if v != exclude {
   261  			out = append(out, v)
   262  		}
   263  	}
   264  	// If we filtered it out, zero out the last item to prevent
   265  	// the GC from seeing it.
   266  	if len(in) != len(out) {
   267  		in[len(in)-1] = nil
   268  	}
   269  	return out
   270  }
   271  
   272  // noDialClientConnPool is an implementation of http2.ClientConnPool
   273  // which never dials. We let the HTTP/1.1 client dial and use its TLS
   274  // connection instead.
   275  type noDialClientConnPool struct{ *clientConnPool }
   276  
   277  func (p noDialClientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
   278  	return p.getClientConn(req, addr, noDialOnMiss)
   279  }