| // Copyright 2015 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Transport code's client connection pooling. |
| |
| package http2 |
| |
| import ( |
| "net/http" |
| "sync" |
| ) |
| |
| // ClientConnPool manages a pool of HTTP/2 client connections. |
| type ClientConnPool interface { |
| GetClientConn(req *http.Request, addr string) (*ClientConn, error) |
| MarkDead(*ClientConn) |
| } |
| |
| type clientConnPool struct { |
| t *Transport |
| mu sync.Mutex // TODO: maybe switch to RWMutex |
| // TODO: add support for sharing conns based on cert names |
| // (e.g. share conn for googleapis.com and appspot.com) |
| conns map[string][]*ClientConn // key is host:port |
| dialing map[string]*dialCall // currently in-flight dials |
| keys map[*ClientConn][]string |
| } |
| |
| func (p *clientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) { |
| return p.getClientConn(req, addr, true) |
| } |
| |
| func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMiss bool) (*ClientConn, error) { |
| p.mu.Lock() |
| for _, cc := range p.conns[addr] { |
| if cc.CanTakeNewRequest() { |
| p.mu.Unlock() |
| return cc, nil |
| } |
| } |
| if !dialOnMiss { |
| p.mu.Unlock() |
| return nil, ErrNoCachedConn |
| } |
| call := p.getStartDialLocked(addr) |
| p.mu.Unlock() |
| <-call.done |
| return call.res, call.err |
| } |
| |
| // dialCall is an in-flight Transport dial call to a host. |
| type dialCall struct { |
| p *clientConnPool |
| done chan struct{} // closed when done |
| res *ClientConn // valid after done is closed |
| err error // valid after done is closed |
| } |
| |
| // requires p.mu is held. |
| func (p *clientConnPool) getStartDialLocked(addr string) *dialCall { |
| if call, ok := p.dialing[addr]; ok { |
| // A dial is already in-flight. Don't start another. |
| return call |
| } |
| call := &dialCall{p: p, done: make(chan struct{})} |
| if p.dialing == nil { |
| p.dialing = make(map[string]*dialCall) |
| } |
| p.dialing[addr] = call |
| go call.dial(addr) |
| return call |
| } |
| |
| // run in its own goroutine. |
| func (c *dialCall) dial(addr string) { |
| c.res, c.err = c.p.t.dialClientConn(addr) |
| close(c.done) |
| |
| c.p.mu.Lock() |
| delete(c.p.dialing, addr) |
| if c.err == nil { |
| c.p.addConnLocked(addr, c.res) |
| } |
| c.p.mu.Unlock() |
| } |
| |
| func (p *clientConnPool) addConn(key string, cc *ClientConn) { |
| p.mu.Lock() |
| p.addConnLocked(key, cc) |
| p.mu.Unlock() |
| } |
| |
| // p.mu must be held |
| func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) { |
| for _, v := range p.conns[key] { |
| if v == cc { |
| return |
| } |
| } |
| if p.conns == nil { |
| p.conns = make(map[string][]*ClientConn) |
| } |
| if p.keys == nil { |
| p.keys = make(map[*ClientConn][]string) |
| } |
| p.conns[key] = append(p.conns[key], cc) |
| p.keys[cc] = append(p.keys[cc], key) |
| } |
| |
| func (p *clientConnPool) MarkDead(cc *ClientConn) { |
| p.mu.Lock() |
| defer p.mu.Unlock() |
| for _, key := range p.keys[cc] { |
| vv, ok := p.conns[key] |
| if !ok { |
| continue |
| } |
| newList := filterOutClientConn(vv, cc) |
| if len(newList) > 0 { |
| p.conns[key] = newList |
| } else { |
| delete(p.conns, key) |
| } |
| } |
| delete(p.keys, cc) |
| } |
| |
| func (p *clientConnPool) closeIdleConnections() { |
| p.mu.Lock() |
| defer p.mu.Unlock() |
| // TODO: don't close a cc if it was just added to the pool |
| // milliseconds ago and has never been used. There's currently |
| // a small race window with the HTTP/1 Transport's integration |
| // where it can add an idle conn just before using it, and |
| // somebody else can concurrently call CloseIdleConns and |
| // break some caller's RoundTrip. |
| for _, vv := range p.conns { |
| for _, cc := range vv { |
| cc.closeIfIdle() |
| } |
| } |
| } |
| |
| func filterOutClientConn(in []*ClientConn, exclude *ClientConn) []*ClientConn { |
| out := in[:0] |
| for _, v := range in { |
| if v != exclude { |
| out = append(out, v) |
| } |
| } |
| // If we filtered it out, zero out the last item to prevent |
| // the GC from seeing it. |
| if len(in) != len(out) { |
| in[len(in)-1] = nil |
| } |
| return out |
| } |