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[persistConn]
41 closeIdle bool
42 }
43
44 type clientConnPoolEntry struct {
45 key connectMethodKey
46
47
48
49
50
51
52
53
54
55 cangetc chan struct{}
56 candialc chan struct{}
57 lockc chan struct{}
58
59
60 idleConns []*persistConn
61
62
63
64
65
66
67
68
69
70
71
72 altConn *persistConn
73
74
75 numConns int
76
77
78 numWaiters int
79
80
81 dials intrusiveList[poolDial]
82
83
84
85 idleTimer *time.Timer
86 idleTimerSet bool
87
88
89 removed bool
90 }
91
92 type poolDial struct {
93 ent *clientConnPoolEntry
94 cm connectMethod
95 cancel context.CancelFunc
96 resultc chan pconnOrError
97 donec <-chan struct{}
98 link intrusiveLink[poolDial]
99 postDial func()
100 }
101
102
103 func (ent *clientConnPoolEntry) lock() {
104 select {
105 case <-ent.lockc:
106 case <-ent.cangetc:
107 case <-ent.candialc:
108 }
109 }
110
111
112
113
114
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
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
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
146 func (ent *clientConnPoolEntry) unregisterConnLocked(pconn *persistConn) {
147 if !pconn.isInPool {
148
149
150 return
151 }
152 pconn.isInPool = false
153 ent.numConns--
154 }
155
156
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
169
170
171
172
173
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
209
210 ent.unlock(t)
211 }
212 }
213
214
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
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
243
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
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
266
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
281
282
283
284
285
286
287
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
297
298 t.pool.lru.remove(pconn)
299 } else {
300
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
309
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
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
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
391
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
412 if err := ctx.Err(); err != nil {
413 return nil, getConnCancelCause(ctx)
414 }
415
416
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
424
425 ent.numWaiters++
426 }
427 return ent, pconn
428 }()
429 if pconn != nil {
430 return pconn, nil
431 }
432
433
434
435
436
437
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
458
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
472
473
474 case <-candialc:
475
476
477 func() {
478 defer ent.unlock(t)
479
480
481
482
483
484
485
486
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
500 go t.poolDialForEntry(dialCtx, dial)
501 candialc = nil
502 }()
503 defer close(getdonec)
504 case res := <-dialresultc:
505
506
507
508
509
510 waiting = false
511 return res.pconn, res.err
512 case <-ctx.Done():
513
514 return nil, getConnCancelCause(ctx)
515 }
516 }
517 }
518
519
520
521
522 func (t *Transport) poolTryGetForEntryLocked(ent *clientConnPoolEntry) *persistConn {
523 if ent.altConn != nil {
524
525 return ent.altConn
526 }
527
528
529
530
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
540
541
542 tooOld := !oldTime.IsZero() && pconn.idleAt.Round(0).Before(oldTime)
543 if tooOld {
544
545
546 go pconn.close(errIdleConnTimeout)
547 }
548 if pconn.isBroken() || tooOld {
549
550
551
552
553
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
561
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
571
572
573
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
586
587 ent.dials.remove(dial)
588 if err == nil {
589 ent.registerConnLocked(pconn)
590 }
591
592
593
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
599
600
601
602
603
604
605
606
607
608
609
610 ent.unregisterConnLocked(pconn)
611 needClose = true
612 if ent.altConn != nil {
613
614
615
616
617
618
619
620
621
622
623 deliver = pconnOrError{pconn: ent.altConn}
624 }
625 }
626 }
627
628
629
630
631
632
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
642
643 return needClose, false
644 }
645
646 err, needExpire = t.poolPutIdleConnLocked(ent, pconn)
647 if err != nil {
648
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
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
670
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
679
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
686
687
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
704
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