github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/net/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  	"context"
    11  	"errors"
    12  	"sync"
    13  
    14  	http "github.com/hxx258456/ccgo/gmhttp"
    15  
    16  	tls "github.com/hxx258456/ccgo/gmtls"
    17  )
    18  
    19  // ClientConnPool manages a pool of HTTP/2 client connections.
    20  type ClientConnPool interface {
    21  	// GetClientConn returns a specific HTTP/2 connection (usually
    22  	// a TLS-TCP connection) to an HTTP/2 server. On success, the
    23  	// returned ClientConn accounts for the upcoming RoundTrip
    24  	// call, so the caller should not omit it. If the caller needs
    25  	// to, ClientConn.RoundTrip can be called with a bogus
    26  	// new(http.Request) to release the stream reservation.
    27  	GetClientConn(req *http.Request, addr string) (*ClientConn, error)
    28  	MarkDead(*ClientConn)
    29  }
    30  
    31  // clientConnPoolIdleCloser is the interface implemented by ClientConnPool
    32  // implementations which can close their idle connections.
    33  type clientConnPoolIdleCloser interface {
    34  	ClientConnPool
    35  	closeIdleConnections()
    36  }
    37  
    38  var (
    39  	_ clientConnPoolIdleCloser = (*clientConnPool)(nil)
    40  	_ clientConnPoolIdleCloser = noDialClientConnPool{}
    41  )
    42  
    43  // TODO: use singleflight for dialing and addConnCalls?
    44  type clientConnPool struct {
    45  	t *Transport
    46  
    47  	mu sync.Mutex // TODO: maybe switch to RWMutex
    48  	// TODO: add support for sharing conns based on cert names
    49  	// (e.g. share conn for googleapis.com and appspot.com)
    50  	conns        map[string][]*ClientConn // key is host:port
    51  	dialing      map[string]*dialCall     // currently in-flight dials
    52  	keys         map[*ClientConn][]string
    53  	addConnCalls map[string]*addConnCall // in-flight addConnIfNeeded calls
    54  }
    55  
    56  func (p *clientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
    57  	return p.getClientConn(req, addr, dialOnMiss)
    58  }
    59  
    60  const (
    61  	dialOnMiss   = true
    62  	noDialOnMiss = false
    63  )
    64  
    65  func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMiss bool) (*ClientConn, error) {
    66  	// TODO(dneil): Dial a new connection when t.DisableKeepAlives is set?
    67  	if isConnectionCloseRequest(req) && dialOnMiss {
    68  		// It gets its own connection.
    69  		traceGetConn(req, addr)
    70  		const singleUse = true
    71  		cc, err := p.t.dialClientConn(req.Context(), addr, singleUse)
    72  		if err != nil {
    73  			return nil, err
    74  		}
    75  		return cc, nil
    76  	}
    77  	for {
    78  		p.mu.Lock()
    79  		for _, cc := range p.conns[addr] {
    80  			if cc.ReserveNewRequest() {
    81  				// When a connection is presented to us by the net/http package,
    82  				// the GetConn hook has already been called.
    83  				// Don't call it a second time here.
    84  				if !cc.getConnCalled {
    85  					traceGetConn(req, addr)
    86  				}
    87  				cc.getConnCalled = false
    88  				p.mu.Unlock()
    89  				return cc, nil
    90  			}
    91  		}
    92  		if !dialOnMiss {
    93  			p.mu.Unlock()
    94  			return nil, ErrNoCachedConn
    95  		}
    96  		traceGetConn(req, addr)
    97  		call := p.getStartDialLocked(req.Context(), addr)
    98  		p.mu.Unlock()
    99  		<-call.done
   100  		if shouldRetryDial(call, req) {
   101  			continue
   102  		}
   103  		cc, err := call.res, call.err
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  		if cc.ReserveNewRequest() {
   108  			return cc, nil
   109  		}
   110  	}
   111  }
   112  
   113  // dialCall is an in-flight Transport dial call to a host.
   114  type dialCall struct {
   115  	_ incomparable
   116  	p *clientConnPool
   117  	// the context associated with the request
   118  	// that created this dialCall
   119  	ctx  context.Context
   120  	done chan struct{} // closed when done
   121  	res  *ClientConn   // valid after done is closed
   122  	err  error         // valid after done is closed
   123  }
   124  
   125  // requires p.mu is held.
   126  func (p *clientConnPool) getStartDialLocked(ctx context.Context, addr string) *dialCall {
   127  	if call, ok := p.dialing[addr]; ok {
   128  		// A dial is already in-flight. Don't start another.
   129  		return call
   130  	}
   131  	call := &dialCall{p: p, done: make(chan struct{}), ctx: ctx}
   132  	if p.dialing == nil {
   133  		p.dialing = make(map[string]*dialCall)
   134  	}
   135  	p.dialing[addr] = call
   136  	go call.dial(call.ctx, addr)
   137  	return call
   138  }
   139  
   140  // run in its own goroutine.
   141  func (c *dialCall) dial(ctx context.Context, addr string) {
   142  	const singleUse = false // shared conn
   143  	c.res, c.err = c.p.t.dialClientConn(ctx, addr, singleUse)
   144  	close(c.done)
   145  
   146  	c.p.mu.Lock()
   147  	delete(c.p.dialing, addr)
   148  	if c.err == nil {
   149  		c.p.addConnLocked(addr, c.res)
   150  	}
   151  	c.p.mu.Unlock()
   152  }
   153  
   154  // addConnIfNeeded makes a NewClientConn out of c if a connection for key doesn't
   155  // already exist. It coalesces concurrent calls with the same key.
   156  // This is used by the http1 Transport code when it creates a new connection. Because
   157  // the http1 Transport doesn't de-dup TCP dials to outbound hosts (because it doesn't know
   158  // the protocol), it can get into a situation where it has multiple TLS connections.
   159  // This code decides which ones live or die.
   160  // The return value used is whether c was used.
   161  // c is never closed.
   162  func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn) (used bool, err error) {
   163  	p.mu.Lock()
   164  	for _, cc := range p.conns[key] {
   165  		if cc.CanTakeNewRequest() {
   166  			p.mu.Unlock()
   167  			return false, nil
   168  		}
   169  	}
   170  	call, dup := p.addConnCalls[key]
   171  	if !dup {
   172  		if p.addConnCalls == nil {
   173  			p.addConnCalls = make(map[string]*addConnCall)
   174  		}
   175  		call = &addConnCall{
   176  			p:    p,
   177  			done: make(chan struct{}),
   178  		}
   179  		p.addConnCalls[key] = call
   180  		go call.run(t, key, c)
   181  	}
   182  	p.mu.Unlock()
   183  
   184  	<-call.done
   185  	if call.err != nil {
   186  		return false, call.err
   187  	}
   188  	return !dup, nil
   189  }
   190  
   191  type addConnCall struct {
   192  	_    incomparable
   193  	p    *clientConnPool
   194  	done chan struct{} // closed when done
   195  	err  error
   196  }
   197  
   198  func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
   199  	cc, err := t.NewClientConn(tc)
   200  
   201  	p := c.p
   202  	p.mu.Lock()
   203  	if err != nil {
   204  		c.err = err
   205  	} else {
   206  		cc.getConnCalled = true // already called by the net/http package
   207  		p.addConnLocked(key, cc)
   208  	}
   209  	delete(p.addConnCalls, key)
   210  	p.mu.Unlock()
   211  	close(c.done)
   212  }
   213  
   214  // p.mu must be held
   215  func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) {
   216  	for _, v := range p.conns[key] {
   217  		if v == cc {
   218  			return
   219  		}
   220  	}
   221  	if p.conns == nil {
   222  		p.conns = make(map[string][]*ClientConn)
   223  	}
   224  	if p.keys == nil {
   225  		p.keys = make(map[*ClientConn][]string)
   226  	}
   227  	p.conns[key] = append(p.conns[key], cc)
   228  	p.keys[cc] = append(p.keys[cc], key)
   229  }
   230  
   231  func (p *clientConnPool) MarkDead(cc *ClientConn) {
   232  	p.mu.Lock()
   233  	defer p.mu.Unlock()
   234  	for _, key := range p.keys[cc] {
   235  		vv, ok := p.conns[key]
   236  		if !ok {
   237  			continue
   238  		}
   239  		newList := filterOutClientConn(vv, cc)
   240  		if len(newList) > 0 {
   241  			p.conns[key] = newList
   242  		} else {
   243  			delete(p.conns, key)
   244  		}
   245  	}
   246  	delete(p.keys, cc)
   247  }
   248  
   249  func (p *clientConnPool) closeIdleConnections() {
   250  	p.mu.Lock()
   251  	defer p.mu.Unlock()
   252  	// TODO: don't close a cc if it was just added to the pool
   253  	// milliseconds ago and has never been used. There's currently
   254  	// a small race window with the HTTP/1 Transport's integration
   255  	// where it can add an idle conn just before using it, and
   256  	// somebody else can concurrently call CloseIdleConns and
   257  	// break some caller's RoundTrip.
   258  	for _, vv := range p.conns {
   259  		for _, cc := range vv {
   260  			cc.closeIfIdle()
   261  		}
   262  	}
   263  }
   264  
   265  func filterOutClientConn(in []*ClientConn, exclude *ClientConn) []*ClientConn {
   266  	out := in[:0]
   267  	for _, v := range in {
   268  		if v != exclude {
   269  			out = append(out, v)
   270  		}
   271  	}
   272  	// If we filtered it out, zero out the last item to prevent
   273  	// the GC from seeing it.
   274  	if len(in) != len(out) {
   275  		in[len(in)-1] = nil
   276  	}
   277  	return out
   278  }
   279  
   280  // noDialClientConnPool is an implementation of http2.ClientConnPool
   281  // which never dials. We let the HTTP/1.1 client dial and use its TLS
   282  // connection instead.
   283  type noDialClientConnPool struct{ *clientConnPool }
   284  
   285  func (p noDialClientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
   286  	return p.getClientConn(req, addr, noDialOnMiss)
   287  }
   288  
   289  // shouldRetryDial reports whether the current request should
   290  // retry dialing after the call finished unsuccessfully, for example
   291  // if the dial was canceled because of a context cancellation or
   292  // deadline expiry.
   293  func shouldRetryDial(call *dialCall, req *http.Request) bool {
   294  	if call.err == nil {
   295  		// No error, no need to retry
   296  		return false
   297  	}
   298  	if call.ctx == req.Context() {
   299  		// If the call has the same context as the request, the dial
   300  		// should not be retried, since any cancellation will have come
   301  		// from this request.
   302  		return false
   303  	}
   304  	if !errors.Is(call.err, context.Canceled) && !errors.Is(call.err, context.DeadlineExceeded) {
   305  		// If the call error is not because of a context cancellation or a deadline expiry,
   306  		// the dial should not be retried.
   307  		return false
   308  	}
   309  	// Only retry if the error is a context cancellation error or deadline expiry
   310  	// and the context associated with the call was canceled or expired.
   311  	return call.ctx.Err() != nil
   312  }