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 }