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 }