package http2
import (
"context"
"errors"
"net"
"net/http"
"sync"
)
type ClientConnPool interface {
GetClientConn (req *http .Request , addr string ) (*ClientConn , error )
MarkDead (*ClientConn )
}
type clientConnPoolIdleCloser interface {
ClientConnPool
closeIdleConnections()
}
var (
_ clientConnPoolIdleCloser = (*clientConnPool )(nil )
_ clientConnPoolIdleCloser = noDialClientConnPool {}
)
type clientConnPool struct {
t *Transport
mu sync .Mutex
conns map [string ][]*ClientConn
dialing map [string ]*dialCall
keys map [*ClientConn ][]string
addConnCalls map [string ]*addConnCall
}
func (p *clientConnPool ) GetClientConn (req *http .Request , addr string ) (*ClientConn , error ) {
return p .getClientConn (req , addr , dialOnMiss )
}
const (
dialOnMiss = true
noDialOnMiss = false
)
func (p *clientConnPool ) getClientConn (req *http .Request , addr string , dialOnMiss bool ) (*ClientConn , error ) {
if isConnectionCloseRequest (req ) && dialOnMiss {
traceGetConn (req , addr )
const singleUse = true
cc , err := p .t .dialClientConn (req .Context (), addr , singleUse )
if err != nil {
return nil , err
}
return cc , nil
}
for {
p .mu .Lock ()
for _ , cc := range p .conns [addr ] {
if cc .ReserveNewRequest () {
if !cc .getConnCalled {
traceGetConn (req , addr )
}
cc .getConnCalled = false
p .mu .Unlock ()
return cc , nil
}
}
if !dialOnMiss {
p .mu .Unlock ()
return nil , ErrNoCachedConn
}
traceGetConn (req , addr )
call := p .getStartDialLocked (req .Context (), addr )
p .mu .Unlock ()
<-call .done
if shouldRetryDial (call , req ) {
continue
}
cc , err := call .res , call .err
if err != nil {
return nil , err
}
if cc .ReserveNewRequest () {
return cc , nil
}
}
}
type dialCall struct {
_ incomparable
p *clientConnPool
ctx context .Context
done chan struct {}
res *ClientConn
err error
}
func (p *clientConnPool ) getStartDialLocked (ctx context .Context , addr string ) *dialCall {
if call , ok := p .dialing [addr ]; ok {
return call
}
call := &dialCall {p : p , done : make (chan struct {}), ctx : ctx }
if p .dialing == nil {
p .dialing = make (map [string ]*dialCall )
}
p .dialing [addr ] = call
go call .dial (call .ctx , addr )
return call
}
func (c *dialCall ) dial (ctx context .Context , addr string ) {
const singleUse = false
c .res , c .err = c .p .t .dialClientConn (ctx , addr , singleUse )
c .p .mu .Lock ()
delete (c .p .dialing , addr )
if c .err == nil {
c .p .addConnLocked (addr , c .res )
}
c .p .mu .Unlock ()
close (c .done )
}
func (p *clientConnPool ) addConnIfNeeded (key string , t *Transport , c net .Conn ) (used bool , err error ) {
p .mu .Lock ()
for _ , cc := range p .conns [key ] {
if cc .CanTakeNewRequest () {
p .mu .Unlock ()
return false , nil
}
}
call , dup := p .addConnCalls [key ]
if !dup {
if p .addConnCalls == nil {
p .addConnCalls = make (map [string ]*addConnCall )
}
call = &addConnCall {
p : p ,
done : make (chan struct {}),
}
p .addConnCalls [key ] = call
go call .run (t , key , c )
}
p .mu .Unlock ()
<-call .done
if call .err != nil {
return false , call .err
}
return !dup , nil
}
type addConnCall struct {
_ incomparable
p *clientConnPool
done chan struct {}
err error
}
func (c *addConnCall ) run (t *Transport , key string , nc net .Conn ) {
cc , err := t .NewClientConn (nc )
p := c .p
p .mu .Lock ()
if err != nil {
c .err = err
} else {
cc .getConnCalled = true
p .addConnLocked (key , cc )
}
delete (p .addConnCalls , key )
p .mu .Unlock ()
close (c .done )
}
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 ()
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 len (in ) != len (out ) {
in [len (in )-1 ] = nil
}
return out
}
type noDialClientConnPool struct { *clientConnPool }
func (p noDialClientConnPool ) GetClientConn (req *http .Request , addr string ) (*ClientConn , error ) {
return p .getClientConn (req , addr , noDialOnMiss )
}
func shouldRetryDial(call *dialCall , req *http .Request ) bool {
if call .err == nil {
return false
}
if call .ctx == req .Context () {
return false
}
if !errors .Is (call .err , context .Canceled ) && !errors .Is (call .err , context .DeadlineExceeded ) {
return false
}
return call .ctx .Err () != nil
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .