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

View as plain text