package http2
import (
)
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 ( *clientConnPool) ( *http.Request, string) (*ClientConn, error) {
return .getClientConn(, , dialOnMiss)
}
const (
dialOnMiss = true
noDialOnMiss = false
)
func ( *clientConnPool) ( *http.Request, string, bool) (*ClientConn, error) {
if isConnectionCloseRequest() && {
traceGetConn(, )
const = true
, := .t.dialClientConn(.Context(), , )
if != nil {
return nil,
}
return , nil
}
for {
.mu.Lock()
for , := range .conns[] {
if .ReserveNewRequest() {
if !.getConnCalled {
traceGetConn(, )
}
.getConnCalled = false
.mu.Unlock()
return , nil
}
}
if ! {
.mu.Unlock()
return nil, ErrNoCachedConn
}
traceGetConn(, )
:= .getStartDialLocked(.Context(), )
.mu.Unlock()
<-.done
if shouldRetryDial(, ) {
continue
}
, := .res, .err
if != nil {
return nil,
}
if .ReserveNewRequest() {
return , nil
}
}
}
type dialCall struct {
_ incomparable
p *clientConnPool
ctx context.Context
done chan struct{}
res *ClientConn
err error
}
func ( *clientConnPool) ( context.Context, string) *dialCall {
if , := .dialing[]; {
return
}
:= &dialCall{p: , done: make(chan struct{}), ctx: }
if .dialing == nil {
.dialing = make(map[string]*dialCall)
}
.dialing[] =
go .dial(.ctx, )
return
}
func ( *dialCall) ( context.Context, string) {
const = false
.res, .err = .p.t.dialClientConn(, , )
.p.mu.Lock()
delete(.p.dialing, )
if .err == nil {
.p.addConnLocked(, .res)
}
.p.mu.Unlock()
close(.done)
}
func ( *clientConnPool) ( string, *Transport, *tls.Conn) ( bool, error) {
.mu.Lock()
for , := range .conns[] {
if .CanTakeNewRequest() {
.mu.Unlock()
return false, nil
}
}
, := .addConnCalls[]
if ! {
if .addConnCalls == nil {
.addConnCalls = make(map[string]*addConnCall)
}
= &addConnCall{
p: ,
done: make(chan struct{}),
}
.addConnCalls[] =
go .run(, , )
}
.mu.Unlock()
<-.done
if .err != nil {
return false, .err
}
return !, nil
}
type addConnCall struct {
_ incomparable
p *clientConnPool
done chan struct{}
err error
}
func ( *addConnCall) ( *Transport, string, *tls.Conn) {
, := .NewClientConn()
:= .p
.mu.Lock()
if != nil {
.err =
} else {
.getConnCalled = true
.addConnLocked(, )
}
delete(.addConnCalls, )
.mu.Unlock()
close(.done)
}
func ( *clientConnPool) ( string, *ClientConn) {
for , := range .conns[] {
if == {
return
}
}
if .conns == nil {
.conns = make(map[string][]*ClientConn)
}
if .keys == nil {
.keys = make(map[*ClientConn][]string)
}
.conns[] = append(.conns[], )
.keys[] = append(.keys[], )
}
func ( *clientConnPool) ( *ClientConn) {
.mu.Lock()
defer .mu.Unlock()
for , := range .keys[] {
, := .conns[]
if ! {
continue
}
:= filterOutClientConn(, )
if len() > 0 {
.conns[] =
} else {
delete(.conns, )
}
}
delete(.keys, )
}
func ( *clientConnPool) () {
.mu.Lock()
defer .mu.Unlock()
for , := range .conns {
for , := range {
.closeIfIdle()
}
}
}
func ( []*ClientConn, *ClientConn) []*ClientConn {
:= [:0]
for , := range {
if != {
= append(, )
}
}
if len() != len() {
[len()-1] = nil
}
return
}
type noDialClientConnPool struct{ *clientConnPool }
func ( noDialClientConnPool) ( *http.Request, string) (*ClientConn, error) {
return .getClientConn(, , noDialOnMiss)
}
func ( *dialCall, *http.Request) bool {
if .err == nil {
return false
}
if .ctx == .Context() {
return false
}
if !errors.Is(.err, context.Canceled) && !errors.Is(.err, context.DeadlineExceeded) {
return false
}
return .ctx.Err() != nil
}