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
56 func (pc *poolConn) isBroken() bool {
57 return pc.cc.Err() != nil
58 }
59
60 type clientConnPoolEntry struct {
61 key connectMethodKey
62
63
64
65
66
67
68
69
70
71 cangetc chan struct{}
72 candialc chan struct{}
73 lockc chan struct{}
74
75
76 idleConns []*poolConn
77
78
79
80
81
82
83
84
85
86
87
88 altConn *poolConn
89
90
91 numConns int
92
93
94 numWaiters int
95
96
97 dials intrusiveList[poolDial]
98
99
100
101 idleTimer *time.Timer
102 idleTimerSet bool
103
104
105 removed bool
106 }
107
108 type poolDial struct {
109 ent *clientConnPoolEntry
110 cm connectMethod
111 cancel context.CancelFunc
112 resultc chan pconnOrError
113 donec <-chan struct{}
114 link intrusiveLink[poolDial]
115 postDial func()
116 }
117
118
119 func (ent *clientConnPoolEntry) lock() {
120 select {
121 case <-ent.lockc:
122 case <-ent.cangetc:
123 case <-ent.candialc:
124 }
125 }
126
127
128
129
130
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
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
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
162 func (ent *clientConnPoolEntry) unregisterConnLocked(pconn *poolConn) {
163 if !pconn.isInPool {
164
165
166 return
167 }
168 pconn.isInPool = false
169 ent.numConns--
170 }
171
172
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
185
186
187
188
189
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
225
226 ent.unlock(t)
227 }
228 }
229
230
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
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
259
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
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
282
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
297
298
299
300
301
302
303
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
313
314 t.pool.lru.remove(pconn)
315 } else {
316
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
325
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
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
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
407
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
428 if err := ctx.Err(); err != nil {
429 return nil, getConnCancelCause(ctx)
430 }
431
432
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
440
441 ent.numWaiters++
442 }
443 return ent, pconn
444 }()
445 if pconn != nil {
446 return pconn, nil
447 }
448
449
450
451
452
453
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
474
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
488
489
490 case <-candialc:
491
492
493 func() {
494 defer ent.unlock(t)
495
496
497
498
499
500
501
502
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
516 go t.poolDialForEntry(dialCtx, dial)
517 candialc = nil
518 }()
519 defer close(getdonec)
520 case res := <-dialresultc:
521
522
523
524
525
526 waiting = false
527 return res.pconn, res.err
528 case <-ctx.Done():
529
530 return nil, getConnCancelCause(ctx)
531 }
532 }
533 }
534
535
536
537
538 func (t *Transport) poolTryGetForEntryLocked(ent *clientConnPoolEntry) *poolConn {
539 if ent.altConn != nil {
540
541 return ent.altConn
542 }
543
544
545
546
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
556
557
558 tooOld := !oldTime.IsZero() && pconn.idleAt.Round(0).Before(oldTime)
559 if tooOld {
560
561
562 go pconn.cc.CloseWithError(errIdleConnTimeout)
563 }
564 if pconn.isBroken() || tooOld {
565
566
567
568
569
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
577
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
587
588
589
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
610
611 ent.dials.remove(dial)
612 if err == nil {
613 ent.registerConnLocked(pconn)
614 }
615
616
617
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
623
624
625
626
627
628
629
630
631
632
633
634 ent.unregisterConnLocked(pconn)
635 needClose = true
636 if ent.altConn != nil {
637
638
639
640
641
642
643
644
645
646
647 deliver = pconnOrError{pconn: ent.altConn}
648 }
649 }
650 }
651
652
653
654
655
656
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
666
667 return needClose, false
668 }
669
670 err, needExpire = t.poolPutIdleConnLocked(ent, pconn)
671 if err != nil {
672
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
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
694
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
703
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
710
711
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
728
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