Help us shape the future of Go! Take the 2026 Go Developer Survey

Source file src/net/http/transport_pool.go

     1  // Copyright 2026 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  package http
     6  
     7  import (
     8  	"context"
     9  	"maps"
    10  	"slices"
    11  	"sync"
    12  	"time"
    13  )
    14  
    15  // clientConnPool is the connection pool state for Transport,
    16  // split out into a separate struct for organizational purposes.
    17  //
    18  // Every Transport has a pool.
    19  //
    20  // The pool has a set of entries for targets (things we can dial).
    21  // Entries are removed from the pool when nothing references them any more
    22  // (no conns, no dials, no waiting requests for a conn, etc.)
    23  // An entry manages the set of connections for a target.
    24  //
    25  // The pool also has an LRU of connections, used to maintain the cross-target MaxIdleConn limit.
    26  //
    27  // Pool entries have a channel-based lock which permits get-connection requests
    28  // to wait for available connections and/or dial slots; see clientConnPoolEntry for details.
    29  //
    30  // Lock ordering is (first to last): clientConnPoolEntry >> clientConnPool >> persistConn.
    31  //
    32  // Locking and unlocking entries is a channel operation, so we take some care to minimize
    33  // the number of lock operations required in the hot path.
    34  //
    35  // The pool and entry locks should never be held during blocking operations
    36  // (such as dials or closing a net.Conn).
    37  type clientConnPool struct {
    38  	mu        sync.Mutex
    39  	ents      map[connectMethodKey]*clientConnPoolEntry
    40  	lru       intrusiveList[poolConn]
    41  	closeIdle bool
    42  }
    43  
    44  type poolConn struct {
    45  	cc genericClientConn
    46  
    47  	// Guarded by clientConnPool.mu.
    48  	lruLink intrusiveLink[poolConn]
    49  
    50  	// Guarded by clientConnPoolEntry's lock.
    51  	poolEnt  *clientConnPoolEntry
    52  	isInPool bool      // set when counted in pool entry's numConns
    53  	idleAt   time.Time // time it last became idle
    54  }
    55  
    56  func (pc *poolConn) isBroken() bool {
    57  	return pc.cc.Err() != nil
    58  }
    59  
    60  type clientConnPoolEntry struct {
    61  	key connectMethodKey
    62  
    63  	// These three channels (each with a 1-element buffer) act as a lock/condition variable.
    64  	//
    65  	//   - if the entry is locked, no channel contains a value; otherwise
    66  	//   - if idleConns contains a usable conn, cangetc contains a value; otherwise
    67  	//   - if a new conn may be dialed, candialc contains a value; otherwise
    68  	//   - lockc contains a value.
    69  	//
    70  	// Lock ordering: Only lock one entry at a time. Lock entries before the pool mutex.
    71  	cangetc  chan struct{}
    72  	candialc chan struct{}
    73  	lockc    chan struct{}
    74  
    75  	// Idle (available for use) connections, most recently used last.
    76  	idleConns []*poolConn
    77  
    78  	// Connection with IsAlt() == true.
    79  	//
    80  	// This is historically a pseudo-"connection" backed by an http2.Transport,
    81  	// which manages its own connection pool. Lack of visibility into this pool
    82  	// causes a variety of bugs, such as connections-per-host tracking not accounting
    83  	// for individual HTTP/2 connections.
    84  	//
    85  	// This may also be an HTTP/3 connection.
    86  	//
    87  	// TODO: Improve management of HTTP/2 and HTTP/3 connections.
    88  	altConn *poolConn
    89  
    90  	// Total connections, idle and otherwise.
    91  	numConns int
    92  
    93  	// Requests waiting for a connection.
    94  	numWaiters int
    95  
    96  	// Dials in progress.
    97  	dials intrusiveList[poolDial]
    98  
    99  	// Idle timer, fires when we should next check to see if conns
   100  	// should be closed for idleness.
   101  	idleTimer    *time.Timer
   102  	idleTimerSet bool
   103  
   104  	// Set when the entry is removed from its pool.
   105  	removed bool
   106  }
   107  
   108  type poolDial struct {
   109  	ent      *clientConnPoolEntry
   110  	cm       connectMethod
   111  	cancel   context.CancelFunc // call to cancel the dial
   112  	resultc  chan pconnOrError  // sends the dial result to the requester
   113  	donec    <-chan struct{}    // closed when the requester no longer needs the dial result
   114  	link     intrusiveLink[poolDial]
   115  	postDial func()
   116  }
   117  
   118  // lock unconditionally locks the entry.
   119  func (ent *clientConnPoolEntry) lock() {
   120  	select {
   121  	case <-ent.lockc:
   122  	case <-ent.cangetc:
   123  	case <-ent.candialc:
   124  	}
   125  }
   126  
   127  // unlock unlocks the entry.
   128  //
   129  // If the entry is no longer in use, unlock removes it from the pool.
   130  // The pool mutex must not be held when calling unlock.
   131  func (ent *clientConnPoolEntry) unlock(tr *Transport) {
   132  	if !ent.removed && ent.numConns == 0 && ent.numWaiters == 0 && ent.dials.size == 0 && len(ent.idleConns) == 0 {
   133  		// This entry is no longer in use; remove it from the pool.
   134  		ent.removed = true
   135  		if ent.idleTimer != nil {
   136  			ent.idleTimer.Stop()
   137  		}
   138  		tr.pool.mu.Lock()
   139  		delete(tr.pool.ents, ent.key)
   140  		tr.pool.mu.Unlock()
   141  	}
   142  	switch {
   143  	case len(ent.idleConns) > 0 || ent.altConn != nil:
   144  		ent.cangetc <- struct{}{}
   145  	case tr.MaxConnsPerHost <= 0 || ent.numConns+ent.dials.size < tr.MaxConnsPerHost:
   146  		ent.candialc <- struct{}{}
   147  	default:
   148  		ent.lockc <- struct{}{}
   149  	}
   150  }
   151  
   152  // registerConnLocked adds pconn to the entry's bookkeeping.
   153  func (ent *clientConnPoolEntry) registerConnLocked(pconn *poolConn) {
   154  	if pconn.isBroken() || pconn.isInPool {
   155  		return
   156  	}
   157  	pconn.isInPool = true
   158  	ent.numConns++
   159  }
   160  
   161  // unregisterConnLocked disassociates pconn from the entry.
   162  func (ent *clientConnPoolEntry) unregisterConnLocked(pconn *poolConn) {
   163  	if !pconn.isInPool {
   164  		// This can happen if a newly-created connection breaks before we finish
   165  		// registering it with the pool.
   166  		return
   167  	}
   168  	pconn.isInPool = false
   169  	ent.numConns--
   170  }
   171  
   172  // deleteIdleLocked deletes every element in idleConns that matches f.
   173  func (ent *clientConnPoolEntry) deleteIdleLocked(t *Transport, f func(*poolConn) bool) {
   174  	ent.idleConns = slices.DeleteFunc(ent.idleConns, func(pconn *poolConn) bool {
   175  		delete := f(pconn)
   176  		if delete {
   177  			ent.unregisterConnLocked(pconn)
   178  			t.removeFromIdleLRU(pconn)
   179  		}
   180  		return delete
   181  	})
   182  }
   183  
   184  // getLockedPoolEntry returns the pool entry for key, creating it if necessary.
   185  // The entry will be locked.
   186  //
   187  // If clearCloseIdle is set, pool.closeIdle is set to false.
   188  // (This isn't especially principled from an abstraction perspective,
   189  // but it lets us get the entry and update closeIdle under one mutex acquisition.)
   190  func (t *Transport) getLockedPoolEntry(key connectMethodKey, clearCloseIdle bool) *clientConnPoolEntry {
   191  	getEnt := func() (*clientConnPoolEntry, bool) {
   192  		t.pool.mu.Lock()
   193  		defer t.pool.mu.Unlock()
   194  		if clearCloseIdle {
   195  			t.pool.closeIdle = false
   196  		}
   197  		if t.pool.ents == nil {
   198  			t.pool.ents = map[connectMethodKey]*clientConnPoolEntry{}
   199  			t.pool.lru.link = func(pconn *poolConn) *intrusiveLink[poolConn] {
   200  				return &pconn.lruLink
   201  			}
   202  		}
   203  		if ent, ok := t.pool.ents[key]; ok {
   204  			return ent, true
   205  		}
   206  		ent := &clientConnPoolEntry{
   207  			key:      key,
   208  			lockc:    make(chan struct{}, 1),
   209  			cangetc:  make(chan struct{}, 1),
   210  			candialc: make(chan struct{}, 1),
   211  		}
   212  		ent.dials.link = func(dial *poolDial) *intrusiveLink[poolDial] { return &dial.link }
   213  		t.pool.ents[key] = ent
   214  		return ent, false
   215  	}
   216  	for {
   217  		ent, needLock := getEnt()
   218  		if needLock {
   219  			ent.lock()
   220  		}
   221  		if !ent.removed {
   222  			return ent
   223  		}
   224  		// This entry was removed from the pool in the time between the lookup
   225  		// and us locking it. Try again.
   226  		ent.unlock(t)
   227  	}
   228  }
   229  
   230  // poolCopyEntries returns the current set of pool entries as a slice.
   231  func (t *Transport) poolCopyEntries() []*clientConnPoolEntry {
   232  	t.pool.mu.Lock()
   233  	defer t.pool.mu.Unlock()
   234  	return slices.Collect(maps.Values(t.pool.ents))
   235  }
   236  
   237  // poolCloseIdleConnections closes idle connections in the pool.
   238  func (t *Transport) poolCloseIdleConnections() {
   239  	t.pool.mu.Lock()
   240  	t.pool.closeIdle = true
   241  	t.pool.mu.Unlock()
   242  
   243  	for _, ent := range t.poolCopyEntries() {
   244  		var toClose []*poolConn
   245  		func() {
   246  			ent.lock()
   247  			defer ent.unlock(t)
   248  			if ent.altConn != nil && ent.numWaiters == 0 {
   249  				ent.unregisterConnLocked(ent.altConn)
   250  				toClose = append(toClose, ent.altConn)
   251  				ent.altConn = nil
   252  			}
   253  			ent.deleteIdleLocked(t, func(pconn *poolConn) bool {
   254  				toClose = append(toClose, pconn)
   255  				return true
   256  			})
   257  
   258  			// Cancel dials in progress where the initiating request
   259  			// is no longer waiting.
   260  			for dial := range ent.dials.all() {
   261  				select {
   262  				case <-dial.donec:
   263  					dial.cancel()
   264  				default:
   265  				}
   266  			}
   267  		}()
   268  		for _, pconn := range toClose {
   269  			pconn.cc.CloseWithError(errCloseIdleConns)
   270  		}
   271  	}
   272  }
   273  
   274  // poolPutIdleConn indicates that a connection has become idle and may accept new requests.
   275  func (t *Transport) poolPutIdleConn(pconn *poolConn) error {
   276  	if pconn == nil || pconn.poolEnt == nil {
   277  		return errNotInPool
   278  	}
   279  	ent := pconn.poolEnt
   280  	if pconn.cc.IsAlt() {
   281  		// HTTP/2 and HTTP/3 connections should never appear here.
   282  		// Ignore them if they do.
   283  		return nil
   284  	}
   285  	err, needExpire := func() (_ error, needExpire bool) {
   286  		ent.lock()
   287  		defer ent.unlock(t)
   288  		return t.poolPutIdleConnLocked(ent, pconn)
   289  	}()
   290  	if needExpire {
   291  		t.expireIdleLRU()
   292  	}
   293  	return err
   294  }
   295  
   296  // poolPutIdleConnLocked places pconn in its entry's idle list.
   297  // The entry must be locked.
   298  //
   299  // It returns an error if pconn should be closed.
   300  //
   301  // It returns needExpire=true if the caller should call t.expireIdleLRU
   302  // after dropping the entry lock. (We check inside poolPutIdleConnLocked to avoid
   303  // a needless pool mutex acquisition in the common case when expiry is not needed.)
   304  func (t *Transport) poolPutIdleConnLocked(ent *clientConnPoolEntry, pconn *poolConn) (_ error, needExpire bool) {
   305  	t.pool.mu.Lock()
   306  	defer t.pool.mu.Unlock()
   307  
   308  	if pconn.isBroken() || !pconn.isInPool {
   309  		return errConnBroken, false
   310  	}
   311  	if t.pool.lru.contains(pconn) {
   312  		// This conn is already idle.
   313  		// Move it to the back of the idle list.
   314  		t.pool.lru.remove(pconn)
   315  	} else {
   316  		// This conn is entering the idle list.
   317  		if t.keepAlivesDisabled() {
   318  			return errKeepAlivesDisabled, false
   319  		}
   320  		if len(ent.idleConns) >= t.maxIdleConnsPerHost() {
   321  			return errTooManyIdleHost, false
   322  		}
   323  		if pconn.cc.IsAlt() && ent.altConn != nil {
   324  			// We already have a reusable, shared connection;
   325  			// discard this one.
   326  			return errTooManyIdleHost, false
   327  		}
   328  		if t.pool.closeIdle {
   329  			return errCloseIdle, false
   330  		}
   331  		if !pconn.cc.IsAlt() {
   332  			ent.idleConns = append(ent.idleConns, pconn)
   333  		} else {
   334  			ent.altConn = pconn
   335  		}
   336  	}
   337  
   338  	if !pconn.cc.IsAlt() {
   339  		t.pool.lru.pushBack(pconn)
   340  	}
   341  
   342  	// Arm the idle timer (if needed).
   343  	pconn.idleAt = time.Now()
   344  	if t.IdleConnTimeout > 0 && !ent.idleTimerSet && !pconn.cc.IsAlt() {
   345  		if ent.idleTimer != nil {
   346  			ent.idleTimer.Reset(t.IdleConnTimeout)
   347  		} else {
   348  			ent.idleTimer = time.AfterFunc(t.IdleConnTimeout, func() {
   349  				t.poolIdleTimeout(ent)
   350  			})
   351  		}
   352  		ent.idleTimerSet = true
   353  	}
   354  	needExpire = t.MaxIdleConns != 0 && t.pool.lru.size > t.MaxIdleConns
   355  	return nil, needExpire
   356  }
   357  
   358  func (t *Transport) poolIdleTimeout(ent *clientConnPoolEntry) {
   359  	ent.lock()
   360  	defer ent.unlock(t)
   361  	ent.idleTimerSet = false
   362  	timeout := t.IdleConnTimeout
   363  	if timeout <= 0 {
   364  		return
   365  	}
   366  	now := time.Now()
   367  	var next time.Time
   368  	ent.deleteIdleLocked(t, func(pconn *poolConn) bool {
   369  		if expiry := pconn.idleAt.Add(timeout); !expiry.After(now) {
   370  			go pconn.cc.CloseWithError(errIdleConnTimeout)
   371  			return true
   372  		} else if next.IsZero() || expiry.Before(next) {
   373  			next = expiry
   374  		}
   375  		return false
   376  	})
   377  	if !next.IsZero() {
   378  		ent.idleTimer.Reset(next.Sub(now))
   379  		ent.idleTimerSet = true
   380  	}
   381  }
   382  
   383  // poolRemoveConn entirely removes pconn from the pool.
   384  func (t *Transport) poolRemoveConn(pconn *poolConn) {
   385  	if pconn == nil || pconn.poolEnt == nil {
   386  		return
   387  	}
   388  	ent := pconn.poolEnt
   389  	ent.lock()
   390  	defer ent.unlock(t)
   391  	if ent.altConn == pconn {
   392  		ent.altConn = nil
   393  	}
   394  	if idx := slices.Index(ent.idleConns, pconn); idx >= 0 {
   395  		ent.idleConns = slices.Delete(ent.idleConns, idx, idx+1)
   396  		t.removeFromIdleLRU(pconn)
   397  	}
   398  	ent.unregisterConnLocked(pconn)
   399  }
   400  
   401  type pconnOrError struct {
   402  	pconn *poolConn
   403  	err   error
   404  }
   405  
   406  // poolGetConn gets a usable connection from the pool,
   407  // dialing a new one if necessary.
   408  func (t *Transport) poolGetConn(ctx context.Context, cm connectMethod) (*poolConn, error) {
   409  	getConnCancelCause := func(ctx context.Context) error {
   410  		err := context.Cause(ctx)
   411  		if err == errRequestCanceled {
   412  			err = errRequestCanceledConn
   413  		}
   414  		return err
   415  	}
   416  
   417  	if testHookPrePendingDial != nil {
   418  		testHookPrePendingDial()
   419  	}
   420  	postDialHook := testHookPostPendingDial
   421  	defer func() {
   422  		if postDialHook != nil {
   423  			postDialHook()
   424  		}
   425  	}()
   426  
   427  	// If the request context is already canceled, don't go looking for a conn.
   428  	if err := ctx.Err(); err != nil {
   429  		return nil, getConnCancelCause(ctx)
   430  	}
   431  
   432  	// Fast path: Check to see if there's a usable conn.
   433  	const clearCloseIdle = true
   434  	ent, pconn := func() (*clientConnPoolEntry, *poolConn) {
   435  		ent := t.getLockedPoolEntry(cm.key(), clearCloseIdle)
   436  		defer ent.unlock(t)
   437  		pconn := t.poolTryGetForEntryLocked(ent)
   438  		if pconn == nil {
   439  			// Increment the waiters count to keep this pool entry alive
   440  			// while we're using it.
   441  			ent.numWaiters++
   442  		}
   443  		return ent, pconn
   444  	}()
   445  	if pconn != nil {
   446  		return pconn, nil
   447  	}
   448  
   449  	// Remove ourselves from the waiters before returning.
   450  	// Running this in a defer ensures it happens after any later defers.
   451  	// In particular, we must close getdonec before trying to lock the entry
   452  	// to decrement numWaiters, to avoid a deadlock race when the request context
   453  	// expires at the same time as a dial finishes.
   454  	waiting := true
   455  	defer func() {
   456  		if !waiting {
   457  			return
   458  		}
   459  		ent.lock()
   460  		defer ent.unlock(t)
   461  		ent.numWaiters--
   462  	}()
   463  
   464  	var (
   465  		candialc    = ent.candialc
   466  		dialresultc chan pconnOrError
   467  		getdonec    chan struct{}
   468  	)
   469  	for {
   470  
   471  		select {
   472  		case <-ent.cangetc:
   473  			// Reading from cangetc locks the entry, and indicates that there is
   474  			// an available connection in the idle list.
   475  			pconn := func() *poolConn {
   476  				defer ent.unlock(t)
   477  				pconn := t.poolTryGetForEntryLocked(ent)
   478  				if pconn != nil {
   479  					ent.numWaiters--
   480  					waiting = false
   481  				}
   482  				return pconn
   483  			}()
   484  			if pconn != nil {
   485  				return pconn, nil
   486  			}
   487  			// We didn't find a usable connection. Most likely we lost a race against
   488  			// a closing connection (the conn is now dead, but it hadn't finished
   489  			// removing itself from the pool). Go back and try again.
   490  		case <-candialc:
   491  			// Reading from candialc locks the entry, and indicates that we may
   492  			// start a new dial.
   493  			func() {
   494  				defer ent.unlock(t)
   495  				// Create a context for the dial which inherits the
   496  				// request context's values, but not its cancellation
   497  				// (since the dial may outlive the request).
   498  				//
   499  				// Note that we only cancel this context to cancel the dial.
   500  				// If the dial finishes, we leave it uncanceled since cancelling
   501  				// it can (for some user-provided DialContext functions)
   502  				// close the connection.
   503  				dialCtx, cancel := context.WithCancel(context.WithoutCancel(ctx))
   504  				dialresultc = make(chan pconnOrError)
   505  				getdonec = make(chan struct{})
   506  				dial := &poolDial{
   507  					ent:      ent,
   508  					cm:       cm,
   509  					cancel:   cancel,
   510  					resultc:  dialresultc,
   511  					donec:    getdonec,
   512  					postDial: postDialHook,
   513  				}
   514  				ent.dials.pushBack(dial)
   515  				postDialHook = nil // will be called by poolDialForEntry
   516  				go t.poolDialForEntry(dialCtx, dial)
   517  				candialc = nil // don't dial again
   518  			}()
   519  			defer close(getdonec)
   520  		case res := <-dialresultc:
   521  			// A dial we started has completed and provided us with
   522  			// either a connection, or a dial failure.
   523  			//
   524  			// poolDialForEntry decrements numWaiters in this case
   525  			// (avoiding a redundant lock on the entry).
   526  			waiting = false
   527  			return res.pconn, res.err
   528  		case <-ctx.Done():
   529  			// The request has timed out.
   530  			return nil, getConnCancelCause(ctx)
   531  		}
   532  	}
   533  }
   534  
   535  // poolTryGetForEntryLocked attempts to fetch an idle connection from ent.
   536  // ent must be locked.
   537  // It returns nil if no usable connection was found.
   538  func (t *Transport) poolTryGetForEntryLocked(ent *clientConnPoolEntry) *poolConn {
   539  	if ent.altConn != nil {
   540  		// If we've got an HTTP/2 or HTTP/3 connection, use it.
   541  		return ent.altConn
   542  	}
   543  
   544  	// If IdleConnTimeout is set, calculate the oldest
   545  	// poolConn.idleAt time we're willing to use a cached idle
   546  	// conn.
   547  	var oldTime time.Time
   548  	if t.IdleConnTimeout > 0 {
   549  		oldTime = time.Now().Add(-t.IdleConnTimeout)
   550  	}
   551  
   552  	for len(ent.idleConns) > 0 {
   553  		pconn := ent.idleConns[len(ent.idleConns)-1]
   554  
   555  		// See whether this connection has been idle too long, considering
   556  		// only the wall time (the Round(0)), in case this is a laptop or VM
   557  		// coming out of suspend with previously cached idle connections.
   558  		tooOld := !oldTime.IsZero() && pconn.idleAt.Round(0).Before(oldTime)
   559  		if tooOld {
   560  			// Async cleanup. Launch in its own goroutine (as if a
   561  			// time.AfterFunc called it).
   562  			go pconn.cc.CloseWithError(errIdleConnTimeout)
   563  		}
   564  		if pconn.isBroken() || tooOld {
   565  			// If either persistConn.readLoop has marked the connection
   566  			// broken, but Transport.removeIdleConn has not yet removed it
   567  			// from the idle list, or if this persistConn is too old (it was
   568  			// idle too long), then ignore it and look for another. In both
   569  			// cases it's already in the process of being closed.
   570  			idx := len(ent.idleConns) - 1
   571  			ent.idleConns = slices.Delete(ent.idleConns, idx, idx+1)
   572  			ent.unregisterConnLocked(pconn)
   573  			t.removeFromIdleLRU(pconn)
   574  			continue
   575  		}
   576  		// HTTP/1: only one client can use pconn.
   577  		// Remove it from the list.
   578  		idx := len(ent.idleConns) - 1
   579  		ent.idleConns = slices.Delete(ent.idleConns, idx, idx+1)
   580  		t.removeFromIdleLRU(pconn)
   581  		return pconn
   582  	}
   583  	return nil
   584  }
   585  
   586  // poolDialForEntry dials a new connection.
   587  //
   588  // Note that dials always use a context derived from a request context,
   589  // although a request may use a connection that was dialed with a different request's context.
   590  func (t *Transport) poolDialForEntry(ctx context.Context, dial *poolDial) {
   591  	ent := dial.ent
   592  	if dial.postDial != nil {
   593  		defer dial.postDial()
   594  	}
   595  
   596  	pconn := &poolConn{
   597  		poolEnt: ent,
   598  	}
   599  	cc, err := t.dialConn(ctx, dial.cm, false, pconn, nil)
   600  	if err != nil {
   601  		pconn = nil
   602  	} else {
   603  		pconn.cc = cc
   604  	}
   605  	needClose, needExpire := func() (needClose, needExpire bool) {
   606  		ent.lock()
   607  		defer ent.unlock(t)
   608  
   609  		// The dial is complete: Remove the dial from the entry and register
   610  		// the new connection (if any).
   611  		ent.dials.remove(dial)
   612  		if err == nil {
   613  			ent.registerConnLocked(pconn)
   614  		}
   615  
   616  		// HTTP/2 and HTTP/3 connections can be shared.
   617  		// Add to the idle connection pool before trying to deliver to w.
   618  		deliver := pconnOrError{pconn: pconn, err: err}
   619  		if err == nil && pconn.cc.IsAlt() {
   620  			err, needExpire = t.poolPutIdleConnLocked(ent, pconn)
   621  			if err != nil {
   622  				// This connection should not go into the idle list.
   623  				//
   624  				// Note: Setting needClose replicates some odd behaviors
   625  				// from the earlier pool implementation. If this is an HTTP/2
   626  				// connection, calling pconn.close does not actually close
   627  				// anything: The "connection" is a reference to the HTTP/2
   628  				// transport, which manages its own connection pool.
   629  				// If this is an HTTP/3 connection, pconn.close does close
   630  				// the connection and may immediately break it while a RoundTrip
   631  				// is in progress.
   632  				//
   633  				// TODO: Fix the above behaviors.
   634  				ent.unregisterConnLocked(pconn)
   635  				needClose = true
   636  				if ent.altConn != nil {
   637  					// We already have a sharable connection in the pool.
   638  					// Keep the existing one, and discard this new one.
   639  					//
   640  					// Try to deliver the existing conn to poolGetConn.
   641  					// We must always deliver a result to poolGetConn
   642  					// to avoid it potentially hanging forever waiting
   643  					// for a pooled conn. (In this branch, we observe that
   644  					// there is currently a pooled conn, but it could become
   645  					// unusable between now and poolGetConn trying to
   646  					// retrieve it.)
   647  					deliver = pconnOrError{pconn: ent.altConn}
   648  				}
   649  			}
   650  		}
   651  
   652  		// Try to deliver the result back to poolGetConn.
   653  		//
   654  		// Note that we're doing this with the entry still locked, so poolGetConn
   655  		// needs to take care not to block on locking the entry until after it
   656  		// closes donec. poolGetConn is free to lock the entry in a non-blocking fashion.
   657  		delivered := false
   658  		select {
   659  		case dial.resultc <- deliver:
   660  			ent.numWaiters--
   661  			delivered = true
   662  		case <-dial.donec:
   663  		}
   664  		if err != nil || delivered || needClose || pconn.cc.IsAlt() {
   665  			// Either an error, or an unsharable HTTP/1 connection which was delivered
   666  			// to the waiter, or a shared HTTP/2 or /3 connection already in the pool.
   667  			return needClose, false
   668  		}
   669  
   670  		err, needExpire = t.poolPutIdleConnLocked(ent, pconn)
   671  		if err != nil {
   672  			// This connection should not go into the idle list.
   673  			ent.unregisterConnLocked(pconn)
   674  			return true, false
   675  		}
   676  		return false, needExpire
   677  	}()
   678  	if needClose {
   679  		pconn.cc.CloseWithError(err)
   680  	}
   681  	if needExpire {
   682  		t.expireIdleLRU()
   683  	}
   684  }
   685  
   686  // removeFromIdleLRU removes pconn from the idle-conns LRU.
   687  func (t *Transport) removeFromIdleLRU(pconn *poolConn) {
   688  	t.pool.mu.Lock()
   689  	defer t.pool.mu.Unlock()
   690  	t.pool.lru.remove(pconn)
   691  }
   692  
   693  // expireIdleLRU removes idle conns from the pool until the number of idle conns
   694  // is less than or equal to MaxIdleConns.
   695  func (t *Transport) expireIdleLRU() {
   696  	t.pool.mu.Lock()
   697  	defer t.pool.mu.Unlock()
   698  	if t.MaxIdleConns == 0 {
   699  		return
   700  	}
   701  	maxIdle := max(t.MaxIdleConns, 0)
   702  	// expireEntry expires conns from the head of the LRU which belong to ent.
   703  	// It locks the entry and pool.
   704  	expireEntry := func(ent *clientConnPoolEntry) {
   705  		ent.lock()
   706  		defer ent.unlock(t)
   707  		t.pool.mu.Lock()
   708  		defer t.pool.mu.Unlock()
   709  		// Expire conns for the entry we currently have locked.
   710  		// It's possible the head of the LRU changed while we were juggling locks;
   711  		// that's okay. Worst case, we loop back and try again with the new head.
   712  		for t.pool.lru.size > maxIdle {
   713  			pconn := t.pool.lru.head
   714  			if pconn.poolEnt != ent {
   715  				break
   716  			}
   717  			if idx := slices.Index(ent.idleConns, pconn); idx >= 0 {
   718  				ent.idleConns = slices.Delete(ent.idleConns, idx, idx+1)
   719  				ent.unregisterConnLocked(pconn)
   720  				go pconn.cc.CloseWithError(errTooManyIdle)
   721  			}
   722  			t.pool.lru.remove(pconn)
   723  		}
   724  	}
   725  	for t.pool.lru.size > maxIdle {
   726  		func() {
   727  			// Lock ordering is ent >> pool, so we need to drop the pool mutex
   728  			// before locking the entry.
   729  			ent := t.pool.lru.head.poolEnt
   730  			t.pool.mu.Unlock()
   731  			defer t.pool.mu.Lock()
   732  			expireEntry(ent)
   733  		}()
   734  	}
   735  }
   736  

View as plain text