@@ -202,6 +202,20 @@ func (t *Transport) markNewGoroutine() {
202202 }
203203}
204204
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+
205219// newTimer creates a new time.Timer, or a synthetic timer in tests.
206220func (t * Transport ) newTimer (d time.Duration ) timer {
207221 if t .transportTestHooks != nil {
@@ -343,7 +357,7 @@ type ClientConn struct {
343357 t * Transport
344358 tconn net.Conn // usually *tls.Conn, except specialized impls
345359 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
347361 singleUse bool // whether being used for a single http.Request
348362 getConnCalled bool // used by clientConnPool
349363
@@ -609,7 +623,7 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
609623 t .vlogf ("http2: Transport failed to get client conn for %s: %v" , addr , err )
610624 return nil , err
611625 }
612- reused := ! atomic .CompareAndSwapUint32 (& cc .reused , 0 , 1 )
626+ reused := ! atomic .CompareAndSwapUint32 (& cc .atomicReused , 0 , 1 )
613627 traceGotConn (req , cc , reused )
614628 res , err := cc .RoundTrip (req )
615629 if err != nil && retry <= 6 {
@@ -634,6 +648,22 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
634648 }
635649 }
636650 }
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+ }
637667 if err != nil {
638668 t .vlogf ("RoundTrip failure: %v" , err )
639669 return nil , err
@@ -652,9 +682,10 @@ func (t *Transport) CloseIdleConnections() {
652682}
653683
654684var (
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" )
658689)
659690
660691// 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
793824 pingTimeout : conf .PingTimeout ,
794825 pings : make (map [[8 ]byte ]chan struct {}),
795826 reqHeaderMu : make (chan struct {}, 1 ),
827+ lastActive : t .now (),
796828 }
797829 var group synctestGroupInterface
798830 if t .transportTestHooks != nil {
@@ -1041,6 +1073,16 @@ func (cc *ClientConn) idleStateLocked() (st clientConnIdleState) {
10411073 ! cc .doNotReuse &&
10421074 int64 (cc .nextStreamID )+ 2 * int64 (cc .pendingRequests ) < math .MaxInt32 &&
10431075 ! 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+
10441086 return
10451087}
10461088
@@ -1062,7 +1104,7 @@ func (cc *ClientConn) tooIdleLocked() bool {
10621104 // times are compared based on their wall time. We don't want
10631105 // to reuse a connection that's been sitting idle during
10641106 // 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
10661108}
10671109
10681110// onIdleTimeout is called from a time.AfterFunc goroutine. It will
@@ -1706,7 +1748,12 @@ func (cs *clientStream) cleanupWriteRequest(err error) {
17061748// Must hold cc.mu.
17071749func (cc * ClientConn ) awaitOpenSlotForStreamLocked (cs * clientStream ) error {
17081750 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 ()
17101757 if cc .closed || ! cc .canTakeNewRequestLocked () {
17111758 return errClientConnUnusable
17121759 }
@@ -2253,10 +2300,10 @@ func (cc *ClientConn) forgetStreamID(id uint32) {
22532300 if len (cc .streams ) != slen - 1 {
22542301 panic ("forgetting unknown stream id" )
22552302 }
2256- cc .lastActive = time . Now ()
2303+ cc .lastActive = cc . t . now ()
22572304 if len (cc .streams ) == 0 && cc .idleTimer != nil {
22582305 cc .idleTimer .Reset (cc .idleTimeout )
2259- cc .lastIdle = time . Now ()
2306+ cc .lastIdle = cc . t . now ()
22602307 }
22612308 // Wake up writeRequestBody via clientStream.awaitFlowControl and
22622309 // wake up RoundTrip if there is a pending request.
@@ -2316,7 +2363,6 @@ func isEOFOrNetReadError(err error) bool {
23162363
23172364func (rl * clientConnReadLoop ) cleanup () {
23182365 cc := rl .cc
2319- cc .t .connPool ().MarkDead (cc )
23202366 defer cc .closeConn ()
23212367 defer close (cc .readerDone )
23222368
@@ -2340,6 +2386,24 @@ func (rl *clientConnReadLoop) cleanup() {
23402386 }
23412387 cc .closed = true
23422388
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+
23432407 for _ , cs := range cc .streams {
23442408 select {
23452409 case <- cs .peerClosed :
@@ -3332,7 +3396,7 @@ func traceGotConn(req *http.Request, cc *ClientConn, reused bool) {
33323396 cc .mu .Lock ()
33333397 ci .WasIdle = len (cc .streams ) == 0 && reused
33343398 if ci .WasIdle && ! cc .lastActive .IsZero () {
3335- ci .IdleTime = time . Since (cc .lastActive )
3399+ ci .IdleTime = cc . t . timeSince (cc .lastActive )
33363400 }
33373401 cc .mu .Unlock ()
33383402
0 commit comments