@@ -151,6 +151,9 @@ type ClientConn struct {
151
151
readerDone chan struct {} // closed on error
152
152
readerErr error // set before readerDone is closed
153
153
154
+ idleTimeout time.Duration // or 0 for never
155
+ idleTimer * time.Timer
156
+
154
157
mu sync.Mutex // guards following
155
158
cond * sync.Cond // hold mu; broadcast on flow/closed changes
156
159
flow flow // our conn-level flow control quota (cs.flow is per stream)
@@ -435,6 +438,10 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
435
438
wantSettingsAck : true ,
436
439
pings : make (map [[8 ]byte ]chan struct {}),
437
440
}
441
+ if d := t .idleConnTimeout (); d != 0 {
442
+ cc .idleTimeout = d
443
+ cc .idleTimer = time .AfterFunc (d , cc .onIdleTimeout )
444
+ }
438
445
if VerboseLogs {
439
446
t .vlogf ("http2: Transport creating client conn %p to %v" , cc , c .RemoteAddr ())
440
447
}
@@ -511,6 +518,16 @@ func (cc *ClientConn) canTakeNewRequestLocked() bool {
511
518
cc .nextStreamID < math .MaxInt32
512
519
}
513
520
521
+ // onIdleTimeout is called from a time.AfterFunc goroutine. It will
522
+ // only be called when we're idle, but because we're coming from a new
523
+ // goroutine, there could be a new request coming in at the same time,
524
+ // so this simply calls the synchronized closeIfIdle to shut down this
525
+ // connection. The timer could just call closeIfIdle, but this is more
526
+ // clear.
527
+ func (cc * ClientConn ) onIdleTimeout () {
528
+ cc .closeIfIdle ()
529
+ }
530
+
514
531
func (cc * ClientConn ) closeIfIdle () {
515
532
cc .mu .Lock ()
516
533
if len (cc .streams ) > 0 {
@@ -652,6 +669,9 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
652
669
if err := checkConnHeaders (req ); err != nil {
653
670
return nil , err
654
671
}
672
+ if cc .idleTimer != nil {
673
+ cc .idleTimer .Stop ()
674
+ }
655
675
656
676
trailers , err := commaSeparatedTrailers (req )
657
677
if err != nil {
@@ -1176,6 +1196,9 @@ func (cc *ClientConn) streamByID(id uint32, andRemove bool) *clientStream {
1176
1196
if andRemove && cs != nil && ! cc .closed {
1177
1197
cc .lastActive = time .Now ()
1178
1198
delete (cc .streams , id )
1199
+ if len (cc .streams ) == 0 && cc .idleTimer != nil {
1200
+ cc .idleTimer .Reset (cc .idleTimeout )
1201
+ }
1179
1202
close (cs .done )
1180
1203
cc .cond .Broadcast () // wake up checkResetOrDone via clientStream.awaitFlowControl
1181
1204
}
@@ -1232,6 +1255,10 @@ func (rl *clientConnReadLoop) cleanup() {
1232
1255
defer cc .t .connPool ().MarkDead (cc )
1233
1256
defer close (cc .readerDone )
1234
1257
1258
+ if cc .idleTimer != nil {
1259
+ cc .idleTimer .Stop ()
1260
+ }
1261
+
1235
1262
// Close any response bodies if the server closes prematurely.
1236
1263
// TODO: also do this if we've written the headers but not
1237
1264
// gotten a response yet.
0 commit comments