@@ -108,6 +108,19 @@ type Transport struct {
108
108
// waiting for their turn.
109
109
StrictMaxConcurrentStreams bool
110
110
111
+ // ReadIdleTimeout is the timeout after which a health check using ping
112
+ // frame will be carried out if no frame is received on the connection.
113
+ // Note that a ping response will is considered a received frame, so if
114
+ // there is no other traffic on the connection, the health check will
115
+ // be performed every ReadIdleTimeout interval.
116
+ // If zero, no health check is performed.
117
+ ReadIdleTimeout time.Duration
118
+
119
+ // PingTimeout is the timeout after which the connection will be closed
120
+ // if a response to Ping is not received.
121
+ // Defaults to 15s.
122
+ PingTimeout time.Duration
123
+
111
124
// t1, if non-nil, is the standard library Transport using
112
125
// this transport. Its settings are used (but not its
113
126
// RoundTrip method, etc).
@@ -131,6 +144,14 @@ func (t *Transport) disableCompression() bool {
131
144
return t .DisableCompression || (t .t1 != nil && t .t1 .DisableCompression )
132
145
}
133
146
147
+ func (t * Transport ) pingTimeout () time.Duration {
148
+ if t .PingTimeout == 0 {
149
+ return 15 * time .Second
150
+ }
151
+ return t .PingTimeout
152
+
153
+ }
154
+
134
155
// ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2.
135
156
// It returns an error if t1 has already been HTTP/2-enabled.
136
157
func ConfigureTransport (t1 * http.Transport ) error {
@@ -674,6 +695,20 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
674
695
return cc , nil
675
696
}
676
697
698
+ func (cc * ClientConn ) healthCheck () {
699
+ pingTimeout := cc .t .pingTimeout ()
700
+ // We don't need to periodically ping in the health check, because the readLoop of ClientConn will
701
+ // trigger the healthCheck again if there is no frame received.
702
+ ctx , cancel := context .WithTimeout (context .Background (), pingTimeout )
703
+ defer cancel ()
704
+ err := cc .Ping (ctx )
705
+ if err != nil {
706
+ cc .closeForLostPing ()
707
+ cc .t .connPool ().MarkDead (cc )
708
+ return
709
+ }
710
+ }
711
+
677
712
func (cc * ClientConn ) setGoAway (f * GoAwayFrame ) {
678
713
cc .mu .Lock ()
679
714
defer cc .mu .Unlock ()
@@ -834,14 +869,12 @@ func (cc *ClientConn) sendGoAway() error {
834
869
return nil
835
870
}
836
871
837
- // Close closes the client connection immediately.
838
- //
839
- // In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead.
840
- func (cc * ClientConn ) Close () error {
872
+ // closes the client connection immediately. In-flight requests are interrupted.
873
+ // err is sent to streams.
874
+ func (cc * ClientConn ) closeForError (err error ) error {
841
875
cc .mu .Lock ()
842
876
defer cc .cond .Broadcast ()
843
877
defer cc .mu .Unlock ()
844
- err := errors .New ("http2: client connection force closed via ClientConn.Close" )
845
878
for id , cs := range cc .streams {
846
879
select {
847
880
case cs .resc <- resAndError {err : err }:
@@ -854,6 +887,20 @@ func (cc *ClientConn) Close() error {
854
887
return cc .tconn .Close ()
855
888
}
856
889
890
+ // Close closes the client connection immediately.
891
+ //
892
+ // In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead.
893
+ func (cc * ClientConn ) Close () error {
894
+ err := errors .New ("http2: client connection force closed via ClientConn.Close" )
895
+ return cc .closeForError (err )
896
+ }
897
+
898
+ // closes the client connection immediately. In-flight requests are interrupted.
899
+ func (cc * ClientConn ) closeForLostPing () error {
900
+ err := errors .New ("http2: client connection lost" )
901
+ return cc .closeForError (err )
902
+ }
903
+
857
904
const maxAllocFrameSize = 512 << 10
858
905
859
906
// frameBuffer returns a scratch buffer suitable for writing DATA frames.
@@ -1706,8 +1753,17 @@ func (rl *clientConnReadLoop) run() error {
1706
1753
rl .closeWhenIdle = cc .t .disableKeepAlives () || cc .singleUse
1707
1754
gotReply := false // ever saw a HEADERS reply
1708
1755
gotSettings := false
1756
+ readIdleTimeout := cc .t .ReadIdleTimeout
1757
+ var t * time.Timer
1758
+ if readIdleTimeout != 0 {
1759
+ t = time .AfterFunc (readIdleTimeout , cc .healthCheck )
1760
+ defer t .Stop ()
1761
+ }
1709
1762
for {
1710
1763
f , err := cc .fr .ReadFrame ()
1764
+ if t != nil {
1765
+ t .Reset (readIdleTimeout )
1766
+ }
1711
1767
if err != nil {
1712
1768
cc .vlogf ("http2: Transport readFrame error on conn %p: (%T) %v" , cc , err , err )
1713
1769
}
0 commit comments