github.com/binkynet/BinkyNet@v1.12.1-0.20240421190447-da4e34c20be0/proto_vendor/golang.org/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 "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 }