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