github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/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  	"net/http"
    11  	"sync"
    12  )
    13  
    14  // ClientConnPool manages a pool of HTTP/2 client connections.
    15  type ClientConnPool interface {
    16  	GetClientConn(req *http.Request, addr string) (*ClientConn, error)
    17  	MarkDead(*ClientConn)
    18  }
    19  
    20  type clientConnPool struct {
    21  	t  *Transport
    22  	mu sync.Mutex // TODO: maybe switch to RWMutex
    23  	// TODO: add support for sharing conns based on cert names
    24  	// (e.g. share conn for googleapis.com and appspot.com)
    25  	conns   map[string][]*ClientConn // key is host:port
    26  	dialing map[string]*dialCall     // currently in-flight dials
    27  	keys    map[*ClientConn][]string
    28  }
    29  
    30  func (p *clientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
    31  	return p.getClientConn(req, addr, true)
    32  }
    33  
    34  func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMiss bool) (*ClientConn, error) {
    35  	p.mu.Lock()
    36  	for _, cc := range p.conns[addr] {
    37  		if cc.CanTakeNewRequest() {
    38  			p.mu.Unlock()
    39  			return cc, nil
    40  		}
    41  	}
    42  	if !dialOnMiss {
    43  		p.mu.Unlock()
    44  		return nil, ErrNoCachedConn
    45  	}
    46  	call := p.getStartDialLocked(addr)
    47  	p.mu.Unlock()
    48  	<-call.done
    49  	return call.res, call.err
    50  }
    51  
    52  // dialCall is an in-flight Transport dial call to a host.
    53  type dialCall struct {
    54  	p    *clientConnPool
    55  	done chan struct{} // closed when done
    56  	res  *ClientConn   // valid after done is closed
    57  	err  error         // valid after done is closed
    58  }
    59  
    60  // requires p.mu is held.
    61  func (p *clientConnPool) getStartDialLocked(addr string) *dialCall {
    62  	if call, ok := p.dialing[addr]; ok {
    63  		// A dial is already in-flight. Don't start another.
    64  		return call
    65  	}
    66  	call := &dialCall{p: p, done: make(chan struct{})}
    67  	if p.dialing == nil {
    68  		p.dialing = make(map[string]*dialCall)
    69  	}
    70  	p.dialing[addr] = call
    71  	go call.dial(addr)
    72  	return call
    73  }
    74  
    75  // run in its own goroutine.
    76  func (c *dialCall) dial(addr string) {
    77  	c.res, c.err = c.p.t.dialClientConn(addr)
    78  	close(c.done)
    79  
    80  	c.p.mu.Lock()
    81  	delete(c.p.dialing, addr)
    82  	if c.err == nil {
    83  		c.p.addConnLocked(addr, c.res)
    84  	}
    85  	c.p.mu.Unlock()
    86  }
    87  
    88  func (p *clientConnPool) addConn(key string, cc *ClientConn) {
    89  	p.mu.Lock()
    90  	p.addConnLocked(key, cc)
    91  	p.mu.Unlock()
    92  }
    93  
    94  // p.mu must be held
    95  func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) {
    96  	for _, v := range p.conns[key] {
    97  		if v == cc {
    98  			return
    99  		}
   100  	}
   101  	if p.conns == nil {
   102  		p.conns = make(map[string][]*ClientConn)
   103  	}
   104  	if p.keys == nil {
   105  		p.keys = make(map[*ClientConn][]string)
   106  	}
   107  	p.conns[key] = append(p.conns[key], cc)
   108  	p.keys[cc] = append(p.keys[cc], key)
   109  }
   110  
   111  func (p *clientConnPool) MarkDead(cc *ClientConn) {
   112  	p.mu.Lock()
   113  	defer p.mu.Unlock()
   114  	for _, key := range p.keys[cc] {
   115  		vv, ok := p.conns[key]
   116  		if !ok {
   117  			continue
   118  		}
   119  		newList := filterOutClientConn(vv, cc)
   120  		if len(newList) > 0 {
   121  			p.conns[key] = newList
   122  		} else {
   123  			delete(p.conns, key)
   124  		}
   125  	}
   126  	delete(p.keys, cc)
   127  }
   128  
   129  func (p *clientConnPool) closeIdleConnections() {
   130  	p.mu.Lock()
   131  	defer p.mu.Unlock()
   132  	// TODO: don't close a cc if it was just added to the pool
   133  	// milliseconds ago and has never been used. There's currently
   134  	// a small race window with the HTTP/1 Transport's integration
   135  	// where it can add an idle conn just before using it, and
   136  	// somebody else can concurrently call CloseIdleConns and
   137  	// break some caller's RoundTrip.
   138  	for _, vv := range p.conns {
   139  		for _, cc := range vv {
   140  			cc.closeIfIdle()
   141  		}
   142  	}
   143  }
   144  
   145  func filterOutClientConn(in []*ClientConn, exclude *ClientConn) []*ClientConn {
   146  	out := in[:0]
   147  	for _, v := range in {
   148  		if v != exclude {
   149  			out = append(out, v)
   150  		}
   151  	}
   152  	// If we filtered it out, zero out the last item to prevent
   153  	// the GC from seeing it.
   154  	if len(in) != len(out) {
   155  		in[len(in)-1] = nil
   156  	}
   157  	return out
   158  }