Source file
src/net/http/transport_pool.go
1
2
3
4
5 package http
6
7 import (
8 "context"
9 "maps"
10 "slices"
11 "sync"
12 "time"
13 )
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
48 lruLink intrusiveLink[poolConn]
49
50
51 poolEnt *clientConnPoolEntry
52 isInPool bool
53 idleAt time.Time
54
55 putIdleHook func(error)
56 }
57
58 func (pc *poolConn) isBroken() bool {
59 return pc.cc.Err() != nil
60 }
61
62 type clientConnPoolEntry struct {
63 key connectMethodKey
64
65
66
67
68
69
70
71
72
73 cangetc chan struct{}
74 candialc chan struct{}
75 lockc chan struct{}
76
77
78 idleConns []*poolConn
79
80
81
82
83
84
85
86
87
88
89
90 altConn *poolConn
91
92
93 numConns int
94
95
96 numWaiters int
97
98
99 dials intrusiveList[poolDial]
100
101
102
103 idleTimer *time.Timer
104 idleTimerSet bool
105
106
107 removed bool
108 }
109
110 type poolDial struct {
111 ent *clientConnPoolEntry
112 cm connectMethod
113 cancel context.CancelFunc
114 resultc chan pconnOrError
115 donec <-chan struct{}
116 link intrusiveLink[poolDial]
117 }
118
119
120 func (ent *clientConnPoolEntry) lock() {
121 select {
122 case <-ent.lockc:
123 case <-ent.cangetc:
124 case <-ent.candialc:
125 }
126 }
127
128
129
130
131
132 func (ent *clientConnPoolEntry) unlock(tr *Transport) {
133 if !ent.removed && ent.numConns == 0 && ent.numWaiters == 0 && ent.dials.size == 0 && len(ent.idleConns) == 0 {
134
135 ent.removed = true
136 if ent.idleTimer != nil {
137 ent.idleTimer.Stop()
138 }
139 tr.pool.mu.Lock()
140 delete(tr.pool.ents, ent.key)
141 tr.pool.mu.Unlock()
142 }
143 switch {
144 case len(ent.idleConns) > 0 || ent.altConn != nil:
145 ent.cangetc <- struct{}{}
146 case tr.MaxConnsPerHost <= 0 || ent.numConns+ent.dials.size < tr.MaxConnsPerHost:
147 ent.candialc <- struct{}{}
148 default:
149 ent.lockc <- struct{}{}
150 }
151 }
152
153
154 func (ent *clientConnPoolEntry) registerConnLocked(pconn *poolConn) {
155 if pconn.isBroken() || pconn.isInPool {
156 return
157 }
158 pconn.isInPool = true
159 ent.numConns++
160 }
161
162
163 func (ent *clientConnPoolEntry) unregisterConnLocked(pconn *poolConn) {
164 if !pconn.isInPool {
165
166
167 return
168 }
169 pconn.isInPool = false
170 ent.numConns--
171 }
172
173
174 func (ent *clientConnPoolEntry) deleteIdleLocked(t *Transport, f func(*poolConn) bool) {
175 ent.idleConns = slices.DeleteFunc(ent.idleConns, func(pconn *poolConn) bool {
176 delete := f(pconn)
177 if delete {
178 ent.unregisterConnLocked(pconn)
179 t.removeFromIdleLRU(pconn)
180 }
181 return delete
182 })
183 }
184
185
186
187
188
189
190
191 func (t *Transport) getLockedPoolEntry(key connectMethodKey, clearCloseIdle bool) *clientConnPoolEntry {
192 getEnt := func() (*clientConnPoolEntry, bool) {
193 t.pool.mu.Lock()
194 defer t.pool.mu.Unlock()
195 if clearCloseIdle {
196 t.pool.closeIdle = false
197 }
198 if t.pool.ents == nil {
199 t.pool.ents = map[connectMethodKey]*clientConnPoolEntry{}
200 t.pool.lru.link = func(pconn *poolConn) *intrusiveLink[poolConn] {
201 return &pconn.lruLink
202 }
203 }
204 if ent, ok := t.pool.ents[key]; ok {
205 return ent, true
206 }
207 ent := &clientConnPoolEntry{
208 key: key,
209 lockc: make(chan struct{}, 1),
210 cangetc: make(chan struct{}, 1),
211 candialc: make(chan struct{}, 1),
212 }
213 ent.dials.link = func(dial *poolDial) *intrusiveLink[poolDial] { return &dial.link }
214 t.pool.ents[key] = ent
215 return ent, false
216 }
217 for {
218 ent, needLock := getEnt()
219 if needLock {
220 ent.lock()
221 }
222 if !ent.removed {
223 return ent
224 }
225
226
227 ent.unlock(t)
228 }
229 }
230
231
232 func (t *Transport) poolCopyEntries() []*clientConnPoolEntry {
233 t.pool.mu.Lock()
234 defer t.pool.mu.Unlock()
235 return slices.Collect(maps.Values(t.pool.ents))
236 }
237
238
239 func (t *Transport) poolCloseIdleConnections() {
240 t.pool.mu.Lock()
241 t.pool.closeIdle = true
242 t.pool.mu.Unlock()
243
244 for _, ent := range t.poolCopyEntries() {
245 var toClose []*poolConn
246 func() {
247 ent.lock()
248 defer ent.unlock(t)
249 if ent.altConn != nil && ent.numWaiters == 0 {
250 ent.unregisterConnLocked(ent.altConn)
251 toClose = append(toClose, ent.altConn)
252 ent.altConn = nil
253 }
254 ent.deleteIdleLocked(t, func(pconn *poolConn) bool {
255 toClose = append(toClose, pconn)
256 return true
257 })
258
259
260
261 for dial := range ent.dials.all() {
262 select {
263 case <-dial.donec:
264 dial.cancel()
265 default:
266 }
267 }
268 }()
269 for _, pconn := range toClose {
270 pconn.cc.CloseWithError(errCloseIdleConns)
271 }
272 }
273 }
274
275
276 func (t *Transport) poolConnStateHook(pconn *poolConn) {
277 var (
278 closeErr error
279 needExpire bool
280 putIdleHook func(error)
281 )
282 func() {
283 ent := pconn.poolEnt
284 ent.lock()
285 defer ent.unlock(t)
286 if pconn.cc == nil || pconn.cc.IsAlt() {
287 return
288 }
289 if pconn.isBroken() {
290 t.poolRemoveConnLocked(ent, pconn)
291 return
292 }
293 if pconn.cc.Available() == 0 {
294 return
295 }
296 putIdleHook = pconn.putIdleHook
297 pconn.putIdleHook = nil
298 closeErr, needExpire = t.poolPutIdleConnLocked(ent, pconn)
299 if closeErr != nil {
300 t.poolRemoveConnLocked(ent, pconn)
301 }
302 }()
303 if closeErr != nil {
304 pconn.cc.CloseWithError(closeErr)
305 }
306 if putIdleHook != nil && closeErr != errKeepAlivesDisabled {
307 putIdleHook(closeErr)
308 }
309 if needExpire {
310 t.expireIdleLRU()
311 }
312 }
313
314
315
316
317
318
319
320
321
322 func (t *Transport) poolPutIdleConnLocked(ent *clientConnPoolEntry, pconn *poolConn) (_ error, needExpire bool) {
323 t.pool.mu.Lock()
324 defer t.pool.mu.Unlock()
325
326 if pconn.isBroken() || !pconn.isInPool {
327 return errConnBroken, false
328 }
329 if t.pool.lru.contains(pconn) {
330
331
332 t.pool.lru.remove(pconn)
333 } else {
334
335 if t.keepAlivesDisabled() {
336 return errKeepAlivesDisabled, false
337 }
338 if len(ent.idleConns) >= t.maxIdleConnsPerHost() {
339 return errTooManyIdleHost, false
340 }
341 if pconn.cc.IsAlt() && ent.altConn != nil {
342
343
344 return errTooManyIdleHost, false
345 }
346 if t.pool.closeIdle {
347 return errCloseIdle, false
348 }
349 if !pconn.cc.IsAlt() {
350 ent.idleConns = append(ent.idleConns, pconn)
351 } else {
352 ent.altConn = pconn
353 }
354 }
355
356 if !pconn.cc.IsAlt() {
357 t.pool.lru.pushBack(pconn)
358 }
359
360
361 pconn.idleAt = time.Now()
362 if t.IdleConnTimeout > 0 && !ent.idleTimerSet && !pconn.cc.IsAlt() {
363 if ent.idleTimer != nil {
364 ent.idleTimer.Reset(t.IdleConnTimeout)
365 } else {
366 ent.idleTimer = time.AfterFunc(t.IdleConnTimeout, func() {
367 t.poolIdleTimeout(ent)
368 })
369 }
370 ent.idleTimerSet = true
371 }
372 needExpire = t.MaxIdleConns != 0 && t.pool.lru.size > t.MaxIdleConns
373 return nil, needExpire
374 }
375
376 func (t *Transport) poolIdleTimeout(ent *clientConnPoolEntry) {
377 ent.lock()
378 defer ent.unlock(t)
379 ent.idleTimerSet = false
380 timeout := t.IdleConnTimeout
381 if timeout <= 0 {
382 return
383 }
384 now := time.Now()
385 var next time.Time
386 ent.deleteIdleLocked(t, func(pconn *poolConn) bool {
387 if expiry := pconn.idleAt.Add(timeout); !expiry.After(now) {
388 go pconn.cc.CloseWithError(errIdleConnTimeout)
389 return true
390 } else if next.IsZero() || expiry.Before(next) {
391 next = expiry
392 }
393 return false
394 })
395 if !next.IsZero() {
396 ent.idleTimer.Reset(next.Sub(now))
397 ent.idleTimerSet = true
398 }
399 }
400
401
402 func (t *Transport) poolRemoveConn(pconn *poolConn) {
403 if pconn == nil || pconn.poolEnt == nil {
404 return
405 }
406 ent := pconn.poolEnt
407 ent.lock()
408 defer ent.unlock(t)
409 t.poolRemoveConnLocked(ent, pconn)
410 }
411
412 func (t *Transport) poolRemoveConnLocked(ent *clientConnPoolEntry, pconn *poolConn) {
413 if ent.altConn == pconn {
414 ent.altConn = nil
415 }
416 if idx := slices.Index(ent.idleConns, pconn); idx >= 0 {
417 ent.idleConns = slices.Delete(ent.idleConns, idx, idx+1)
418 t.removeFromIdleLRU(pconn)
419 }
420 ent.unregisterConnLocked(pconn)
421 }
422
423 type pconnOrError struct {
424 pconn *poolConn
425 err error
426 }
427
428
429
430 func (t *Transport) poolGetConn(ctx context.Context, cm connectMethod) (*poolConn, error) {
431 getConnCancelCause := func(ctx context.Context) error {
432 err := context.Cause(ctx)
433 if err == errRequestCanceled {
434 err = errRequestCanceledConn
435 }
436 return err
437 }
438
439
440 if err := ctx.Err(); err != nil {
441 return nil, getConnCancelCause(ctx)
442 }
443
444
445 const clearCloseIdle = true
446 ent, pconn := func() (*clientConnPoolEntry, *poolConn) {
447 ent := t.getLockedPoolEntry(cm.key(), clearCloseIdle)
448 defer ent.unlock(t)
449 pconn := t.poolTryGetForEntryLocked(ent)
450 if pconn == nil {
451
452
453 ent.numWaiters++
454 }
455 return ent, pconn
456 }()
457 if pconn != nil {
458 return pconn, nil
459 }
460
461
462
463
464
465
466 waiting := true
467 defer func() {
468 if !waiting {
469 return
470 }
471 ent.lock()
472 defer ent.unlock(t)
473 ent.numWaiters--
474 }()
475
476 var (
477 candialc = ent.candialc
478 dialresultc chan pconnOrError
479 getdonec chan struct{}
480 )
481 for {
482
483 select {
484 case <-ent.cangetc:
485
486
487 pconn := func() *poolConn {
488 defer ent.unlock(t)
489 pconn := t.poolTryGetForEntryLocked(ent)
490 if pconn != nil {
491 ent.numWaiters--
492 waiting = false
493 }
494 return pconn
495 }()
496 if pconn != nil {
497 return pconn, nil
498 }
499
500
501
502 case <-candialc:
503
504
505 func() {
506 defer ent.unlock(t)
507
508
509
510
511
512
513
514
515 dialCtx, cancel := context.WithCancel(context.WithoutCancel(ctx))
516 dialresultc = make(chan pconnOrError)
517 getdonec = make(chan struct{})
518 dial := &poolDial{
519 ent: ent,
520 cm: cm,
521 cancel: cancel,
522 resultc: dialresultc,
523 donec: getdonec,
524 }
525 ent.dials.pushBack(dial)
526 go t.poolDialForEntry(dialCtx, dial)
527 candialc = nil
528 }()
529 defer close(getdonec)
530 case res := <-dialresultc:
531
532
533
534
535
536 waiting = false
537 return res.pconn, res.err
538 case <-ctx.Done():
539
540 return nil, getConnCancelCause(ctx)
541 }
542 }
543 }
544
545
546
547
548 func (t *Transport) poolTryGetForEntryLocked(ent *clientConnPoolEntry) *poolConn {
549 if ent.altConn != nil {
550
551 return ent.altConn
552 }
553
554
555
556
557 var oldTime time.Time
558 if t.IdleConnTimeout > 0 {
559 oldTime = time.Now().Add(-t.IdleConnTimeout)
560 }
561
562 for len(ent.idleConns) > 0 {
563 pconn := ent.idleConns[len(ent.idleConns)-1]
564
565
566
567
568 tooOld := !oldTime.IsZero() && pconn.idleAt.Round(0).Before(oldTime)
569 if tooOld {
570
571
572 go pconn.cc.CloseWithError(errIdleConnTimeout)
573 }
574 if pconn.isBroken() || tooOld {
575
576
577
578
579
580 idx := len(ent.idleConns) - 1
581 ent.idleConns = slices.Delete(ent.idleConns, idx, idx+1)
582 ent.unregisterConnLocked(pconn)
583 t.removeFromIdleLRU(pconn)
584 continue
585 }
586
587
588 idx := len(ent.idleConns) - 1
589 ent.idleConns = slices.Delete(ent.idleConns, idx, idx+1)
590 t.removeFromIdleLRU(pconn)
591 return pconn
592 }
593 return nil
594 }
595
596
597
598
599
600 func (t *Transport) poolDialForEntry(ctx context.Context, dial *poolDial) {
601 ent := dial.ent
602 pconn := &poolConn{
603 poolEnt: ent,
604 }
605 cc, err := t.dialConn(ctx, dial.cm, false, func() { t.poolConnStateHook(pconn) })
606 if err != nil {
607 pconn = nil
608 }
609 needClose, needExpire := func() (needClose, needExpire bool) {
610 ent.lock()
611 defer ent.unlock(t)
612
613
614
615 ent.dials.remove(dial)
616 if err == nil {
617 pconn.cc = cc
618 ent.registerConnLocked(pconn)
619 }
620
621
622
623 deliver := pconnOrError{pconn: pconn, err: err}
624 if err == nil && pconn.cc.IsAlt() {
625 err, needExpire = t.poolPutIdleConnLocked(ent, pconn)
626 if err != nil {
627
628
629
630
631
632
633
634
635
636
637
638
639 ent.unregisterConnLocked(pconn)
640 needClose = true
641 if ent.altConn != nil {
642
643
644
645
646
647
648
649
650
651
652 deliver = pconnOrError{pconn: ent.altConn}
653 }
654 }
655 }
656
657
658
659
660
661
662 delivered := false
663 select {
664 case dial.resultc <- deliver:
665 ent.numWaiters--
666 delivered = true
667 case <-dial.donec:
668 }
669 if err != nil || delivered || needClose || pconn.cc.IsAlt() {
670
671
672 return needClose, false
673 }
674
675 err, needExpire = t.poolPutIdleConnLocked(ent, pconn)
676 if err != nil {
677
678 ent.unregisterConnLocked(pconn)
679 return true, false
680 }
681 return false, needExpire
682 }()
683 if needClose {
684 pconn.cc.CloseWithError(err)
685 }
686 if needExpire {
687 t.expireIdleLRU()
688 }
689 }
690
691
692 func (t *Transport) removeFromIdleLRU(pconn *poolConn) {
693 t.pool.mu.Lock()
694 defer t.pool.mu.Unlock()
695 t.pool.lru.remove(pconn)
696 }
697
698
699
700 func (t *Transport) expireIdleLRU() {
701 t.pool.mu.Lock()
702 defer t.pool.mu.Unlock()
703 if t.MaxIdleConns == 0 {
704 return
705 }
706 maxIdle := max(t.MaxIdleConns, 0)
707
708
709 expireEntry := func(ent *clientConnPoolEntry) {
710 ent.lock()
711 defer ent.unlock(t)
712 t.pool.mu.Lock()
713 defer t.pool.mu.Unlock()
714
715
716
717 for t.pool.lru.size > maxIdle {
718 pconn := t.pool.lru.head
719 if pconn.poolEnt != ent {
720 break
721 }
722 if idx := slices.Index(ent.idleConns, pconn); idx >= 0 {
723 ent.idleConns = slices.Delete(ent.idleConns, idx, idx+1)
724 ent.unregisterConnLocked(pconn)
725 go pconn.cc.CloseWithError(errTooManyIdle)
726 }
727 t.pool.lru.remove(pconn)
728 }
729 }
730 for t.pool.lru.size > maxIdle {
731 func() {
732
733
734 ent := t.pool.lru.head.poolEnt
735 t.pool.mu.Unlock()
736 defer t.pool.mu.Lock()
737 expireEntry(ent)
738 }()
739 }
740 }
741
View as plain text