// 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 (
	
	
	
	
	
)

// ClientConnPool manages a pool of HTTP/2 client connections.
type ClientConnPool interface {
	// GetClientConn returns a specific HTTP/2 connection (usually
	// a TLS-TCP connection) to an HTTP/2 server. On success, the
	// returned ClientConn accounts for the upcoming RoundTrip
	// call, so the caller should not omit it. If the caller needs
	// to, ClientConn.RoundTrip can be called with a bogus
	// new(http.Request) to release the stream reservation.
	GetClientConn(req *http.Request, addr string) (*ClientConn, error)
	MarkDead(*ClientConn)
}

// clientConnPoolIdleCloser is the interface implemented by ClientConnPool
// implementations which can close their idle connections.
type clientConnPoolIdleCloser interface {
	ClientConnPool
	closeIdleConnections()
}

var (
	_ clientConnPoolIdleCloser = (*clientConnPool)(nil)
	_ clientConnPoolIdleCloser = noDialClientConnPool{}
)

// TODO: use singleflight for dialing and addConnCalls?
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
	addConnCalls map[string]*addConnCall // in-flight addConnIfNeeded calls
}

func ( *clientConnPool) ( *http.Request,  string) (*ClientConn, error) {
	return .getClientConn(, , dialOnMiss)
}

const (
	dialOnMiss   = true
	noDialOnMiss = false
)

func ( *clientConnPool) ( *http.Request,  string,  bool) (*ClientConn, error) {
	// TODO(dneil): Dial a new connection when t.DisableKeepAlives is set?
	if isConnectionCloseRequest() &&  {
		// It gets its own connection.
		traceGetConn(, )
		const  = true
		,  := .t.dialClientConn(.Context(), , )
		if  != nil {
			return nil, 
		}
		return , nil
	}
	for {
		.mu.Lock()
		for ,  := range .conns[] {
			if .ReserveNewRequest() {
				// When a connection is presented to us by the net/http package,
				// the GetConn hook has already been called.
				// Don't call it a second time here.
				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
		}
	}
}

// dialCall is an in-flight Transport dial call to a host.
type dialCall struct {
	_ incomparable
	p *clientConnPool
	// the context associated with the request
	// that created this dialCall
	ctx  context.Context
	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 ( *clientConnPool) ( context.Context,  string) *dialCall {
	if ,  := .dialing[];  {
		// A dial is already in-flight. Don't start another.
		return 
	}
	 := &dialCall{p: , done: make(chan struct{}), ctx: }
	if .dialing == nil {
		.dialing = make(map[string]*dialCall)
	}
	.dialing[] = 
	go .dial(.ctx, )
	return 
}

// run in its own goroutine.
func ( *dialCall) ( context.Context,  string) {
	const  = false // shared conn
	.res, .err = .p.t.dialClientConn(, , )

	.p.mu.Lock()
	delete(.p.dialing, )
	if .err == nil {
		.p.addConnLocked(, .res)
	}
	.p.mu.Unlock()

	close(.done)
}

// addConnIfNeeded makes a NewClientConn out of c if a connection for key doesn't
// already exist. It coalesces concurrent calls with the same key.
// This is used by the http1 Transport code when it creates a new connection. Because
// the http1 Transport doesn't de-dup TCP dials to outbound hosts (because it doesn't know
// the protocol), it can get into a situation where it has multiple TLS connections.
// This code decides which ones live or die.
// The return value used is whether c was used.
// c is never closed.
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{} // closed when done
	err  error
}

func ( *addConnCall) ( *Transport,  string,  *tls.Conn) {
	,  := .NewClientConn()

	 := .p
	.mu.Lock()
	if  != nil {
		.err = 
	} else {
		.getConnCalled = true // already called by the net/http package
		.addConnLocked(, )
	}
	delete(.addConnCalls, )
	.mu.Unlock()
	close(.done)
}

// p.mu must be held
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()
	// 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 ,  := range .conns {
		for ,  := range  {
			.closeIfIdle()
		}
	}
}

func ( []*ClientConn,  *ClientConn) []*ClientConn {
	 := [:0]
	for ,  := range  {
		if  !=  {
			 = append(, )
		}
	}
	// If we filtered it out, zero out the last item to prevent
	// the GC from seeing it.
	if len() != len() {
		[len()-1] = nil
	}
	return 
}

// noDialClientConnPool is an implementation of http2.ClientConnPool
// which never dials. We let the HTTP/1.1 client dial and use its TLS
// connection instead.
type noDialClientConnPool struct{ *clientConnPool }

func ( noDialClientConnPool) ( *http.Request,  string) (*ClientConn, error) {
	return .getClientConn(, , noDialOnMiss)
}

// shouldRetryDial reports whether the current request should
// retry dialing after the call finished unsuccessfully, for example
// if the dial was canceled because of a context cancellation or
// deadline expiry.
func ( *dialCall,  *http.Request) bool {
	if .err == nil {
		// No error, no need to retry
		return false
	}
	if .ctx == .Context() {
		// If the call has the same context as the request, the dial
		// should not be retried, since any cancellation will have come
		// from this request.
		return false
	}
	if !errors.Is(.err, context.Canceled) && !errors.Is(.err, context.DeadlineExceeded) {
		// If the call error is not because of a context cancellation or a deadline expiry,
		// the dial should not be retried.
		return false
	}
	// Only retry if the error is a context cancellation error or deadline expiry
	// and the context associated with the call was canceled or expired.
	return .ctx.Err() != nil
}