@@ -202,6 +202,20 @@ func (t *Transport) markNewGoroutine() {
202
202
}
203
203
}
204
204
205
+ func (t * Transport ) now () time.Time {
206
+ if t != nil && t .transportTestHooks != nil {
207
+ return t .transportTestHooks .group .Now ()
208
+ }
209
+ return time .Now ()
210
+ }
211
+
212
+ func (t * Transport ) timeSince (when time.Time ) time.Duration {
213
+ if t != nil && t .transportTestHooks != nil {
214
+ return t .now ().Sub (when )
215
+ }
216
+ return time .Since (when )
217
+ }
218
+
205
219
// newTimer creates a new time.Timer, or a synthetic timer in tests.
206
220
func (t * Transport ) newTimer (d time.Duration ) timer {
207
221
if t .transportTestHooks != nil {
@@ -343,7 +357,7 @@ type ClientConn struct {
343
357
t * Transport
344
358
tconn net.Conn // usually *tls.Conn, except specialized impls
345
359
tlsState * tls.ConnectionState // nil only for specialized impls
346
- reused uint32 // whether conn is being reused; atomic
360
+ atomicReused uint32 // whether conn is being reused; atomic
347
361
singleUse bool // whether being used for a single http.Request
348
362
getConnCalled bool // used by clientConnPool
349
363
@@ -609,7 +623,7 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
609
623
t .vlogf ("http2: Transport failed to get client conn for %s: %v" , addr , err )
610
624
return nil , err
611
625
}
612
- reused := ! atomic .CompareAndSwapUint32 (& cc .reused , 0 , 1 )
626
+ reused := ! atomic .CompareAndSwapUint32 (& cc .atomicReused , 0 , 1 )
613
627
traceGotConn (req , cc , reused )
614
628
res , err := cc .RoundTrip (req )
615
629
if err != nil && retry <= 6 {
@@ -634,6 +648,22 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
634
648
}
635
649
}
636
650
}
651
+ if err == errClientConnNotEstablished {
652
+ // This ClientConn was created recently,
653
+ // this is the first request to use it,
654
+ // and the connection is closed and not usable.
655
+ //
656
+ // In this state, cc.idleTimer will remove the conn from the pool
657
+ // when it fires. Stop the timer and remove it here so future requests
658
+ // won't try to use this connection.
659
+ //
660
+ // If the timer has already fired and we're racing it, the redundant
661
+ // call to MarkDead is harmless.
662
+ if cc .idleTimer != nil {
663
+ cc .idleTimer .Stop ()
664
+ }
665
+ t .connPool ().MarkDead (cc )
666
+ }
637
667
if err != nil {
638
668
t .vlogf ("RoundTrip failure: %v" , err )
639
669
return nil , err
@@ -652,9 +682,10 @@ func (t *Transport) CloseIdleConnections() {
652
682
}
653
683
654
684
var (
655
- errClientConnClosed = errors .New ("http2: client conn is closed" )
656
- errClientConnUnusable = errors .New ("http2: client conn not usable" )
657
- errClientConnGotGoAway = errors .New ("http2: Transport received Server's graceful shutdown GOAWAY" )
685
+ errClientConnClosed = errors .New ("http2: client conn is closed" )
686
+ errClientConnUnusable = errors .New ("http2: client conn not usable" )
687
+ errClientConnNotEstablished = errors .New ("http2: client conn could not be established" )
688
+ errClientConnGotGoAway = errors .New ("http2: Transport received Server's graceful shutdown GOAWAY" )
658
689
)
659
690
660
691
// shouldRetryRequest is called by RoundTrip when a request fails to get
@@ -793,6 +824,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
793
824
pingTimeout : conf .PingTimeout ,
794
825
pings : make (map [[8 ]byte ]chan struct {}),
795
826
reqHeaderMu : make (chan struct {}, 1 ),
827
+ lastActive : t .now (),
796
828
}
797
829
var group synctestGroupInterface
798
830
if t .transportTestHooks != nil {
@@ -1041,6 +1073,16 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) {
1041
1073
! cc .doNotReuse &&
1042
1074
int64 (cc .nextStreamID )+ 2 * int64 (cc .pendingRequests ) < math .MaxInt32 &&
1043
1075
! cc .tooIdleLocked ()
1076
+
1077
+ // If this connection has never been used for a request and is closed,
1078
+ // then let it take a request (which will fail).
1079
+ //
1080
+ // This avoids a situation where an error early in a connection's lifetime
1081
+ // goes unreported.
1082
+ if cc .nextStreamID == 1 && cc .streamsReserved == 0 && cc .closed {
1083
+ st .canTakeNewRequest = true
1084
+ }
1085
+
1044
1086
return
1045
1087
}
1046
1088
@@ -1062,7 +1104,7 @@ func (cc *ClientConn) tooIdleLocked() bool {
1062
1104
// times are compared based on their wall time. We don't want
1063
1105
// to reuse a connection that's been sitting idle during
1064
1106
// VM/laptop suspend if monotonic time was also frozen.
1065
- return cc .idleTimeout != 0 && ! cc .lastIdle .IsZero () && time . Since (cc .lastIdle .Round (0 )) > cc .idleTimeout
1107
+ return cc .idleTimeout != 0 && ! cc .lastIdle .IsZero () && cc . t . timeSince (cc .lastIdle .Round (0 )) > cc .idleTimeout
1066
1108
}
1067
1109
1068
1110
// onIdleTimeout is called from a time.AfterFunc goroutine. It will
@@ -1706,7 +1748,12 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
1706
1748
// Must hold cc.mu.
1707
1749
func (cc * ClientConn ) awaitOpenSlotForStreamLocked (cs * clientStream ) error {
1708
1750
for {
1709
- cc .lastActive = time .Now ()
1751
+ if cc .closed && cc .nextStreamID == 1 && cc .streamsReserved == 0 {
1752
+ // This is the very first request sent to this connection.
1753
+ // Return a fatal error which aborts the retry loop.
1754
+ return errClientConnNotEstablished
1755
+ }
1756
+ cc .lastActive = cc .t .now ()
1710
1757
if cc .closed || ! cc .canTakeNewRequestLocked () {
1711
1758
return errClientConnUnusable
1712
1759
}
@@ -2253,10 +2300,10 @@ func (cc *ClientConn) forgetStreamID(id uint32) {
2253
2300
if len (cc .streams ) != slen - 1 {
2254
2301
panic ("forgetting unknown stream id" )
2255
2302
}
2256
- cc .lastActive = time . Now ()
2303
+ cc .lastActive = cc . t . now ()
2257
2304
if len (cc .streams ) == 0 && cc .idleTimer != nil {
2258
2305
cc .idleTimer .Reset (cc .idleTimeout )
2259
- cc .lastIdle = time . Now ()
2306
+ cc .lastIdle = cc . t . now ()
2260
2307
}
2261
2308
// Wake up writeRequestBody via clientStream.awaitFlowControl and
2262
2309
// wake up RoundTrip if there is a pending request.
@@ -2316,7 +2363,6 @@ func isEOFOrNetReadError(err error) bool {
2316
2363
2317
2364
func (rl * clientConnReadLoop ) cleanup () {
2318
2365
cc := rl .cc
2319
- cc .t .connPool ().MarkDead (cc )
2320
2366
defer cc .closeConn ()
2321
2367
defer close (cc .readerDone )
2322
2368
@@ -2340,6 +2386,24 @@ func (rl *clientConnReadLoop) cleanup() {
2340
2386
}
2341
2387
cc .closed = true
2342
2388
2389
+ // If the connection has never been used, and has been open for only a short time,
2390
+ // leave it in the connection pool for a little while.
2391
+ //
2392
+ // This avoids a situation where new connections are constantly created,
2393
+ // added to the pool, fail, and are removed from the pool, without any error
2394
+ // being surfaced to the user.
2395
+ const unusedWaitTime = 5 * time .Second
2396
+ idleTime := cc .t .now ().Sub (cc .lastActive )
2397
+ if atomic .LoadUint32 (& cc .atomicReused ) == 0 && idleTime < unusedWaitTime {
2398
+ cc .idleTimer = cc .t .afterFunc (unusedWaitTime - idleTime , func () {
2399
+ cc .t .connPool ().MarkDead (cc )
2400
+ })
2401
+ } else {
2402
+ cc .mu .Unlock () // avoid any deadlocks in MarkDead
2403
+ cc .t .connPool ().MarkDead (cc )
2404
+ cc .mu .Lock ()
2405
+ }
2406
+
2343
2407
for _ , cs := range cc .streams {
2344
2408
select {
2345
2409
case <- cs .peerClosed :
@@ -3332,7 +3396,7 @@ func traceGotConn(req *http.Request, cc *ClientConn, reused bool) {
3332
3396
cc .mu .Lock ()
3333
3397
ci .WasIdle = len (cc .streams ) == 0 && reused
3334
3398
if ci .WasIdle && ! cc .lastActive .IsZero () {
3335
- ci .IdleTime = time . Since (cc .lastActive )
3399
+ ci .IdleTime = cc . t . timeSince (cc .lastActive )
3336
3400
}
3337
3401
cc .mu .Unlock ()
3338
3402
0 commit comments