Skip to content

Commit 231a5fe

Browse files
author
Chao Xu
committed
remove pingPeriod. Rely on idleTimeout to ping periodically
1 parent 5058c86 commit 231a5fe

File tree

2 files changed

+38
-81
lines changed

2 files changed

+38
-81
lines changed

http2/transport.go

+24-62
Original file line numberDiff line numberDiff line change
@@ -108,23 +108,17 @@ type Transport struct {
108108
// waiting for their turn.
109109
StrictMaxConcurrentStreams bool
110110

111-
// PingPeriod controls how often pings are sent on idle connections to
112-
// check the liveness of the connection. The connection will be closed
113-
// if response is not received within PingTimeout.
114-
// 0 means no periodic pings. Defaults to 0.
115-
PingPeriod time.Duration
116-
117111
// PingTimeout is the timeout after which the connection will be closed
118112
// if a response to Ping is not received.
119-
// Defaults to PingPeriod/2
113+
// Defaults to 1s.
120114
PingTimeout time.Duration
121115

122-
// ReadIdleTimeout is the timeout after which the periodic ping for
123-
// connection health check will begin if no frame is received on the
124-
// connection.
125-
// The health check will stop once there is a frame received on the
126-
// connection.
127-
// Defaults to 60s.
116+
// ReadIdleTimeout is the timeout after which a health check using ping
117+
// frame will be carried out if no frame is received on the connection.
118+
// Note that a ping response will is considered a received frame, so if
119+
// there is no other traffic on the connection, the health check will
120+
// be performed every ReadIdleTimeout interval.
121+
// Default to 0, which means no health check.
128122
ReadIdleTimeout time.Duration
129123

130124
// t1, if non-nil, is the standard library Transport using
@@ -160,7 +154,7 @@ func (t *Transport) readIdleTimeout() time.Duration {
160154

161155
func (t *Transport) pingTimeout() time.Duration {
162156
if t.PingTimeout == 0 {
163-
return t.PingPeriod / 2
157+
return 1 * time.Second
164158
}
165159
return t.PingTimeout
166160

@@ -274,9 +268,6 @@ type ClientConn struct {
274268

275269
wmu sync.Mutex // held while writing; acquire AFTER mu if holding both
276270
werr error // first write error that has occurred
277-
278-
hmu sync.Mutex // guard the healthCheckStopCh
279-
healthCheckStopCh chan struct{} // A close-only channel to stop the health check.
280271
}
281272

282273
// clientStream is the state for a single HTTP/2 stream. One of these
@@ -712,51 +703,18 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
712703
return cc, nil
713704
}
714705

715-
func (cc *ClientConn) healthCheck(stop chan struct{}) {
716-
pingPeriod := cc.t.PingPeriod
706+
func (cc *ClientConn) healthCheck() {
717707
pingTimeout := cc.t.pingTimeout()
718-
if pingPeriod == 0 || pingTimeout == 0 {
719-
return
720-
}
721-
ticker := time.NewTicker(pingPeriod)
722-
defer ticker.Stop()
723-
for {
724-
select {
725-
case <-stop:
726-
return
727-
case <-ticker.C:
728-
ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
729-
err := cc.Ping(ctx)
730-
cancel()
731-
if err != nil {
732-
cc.closeForLostPing()
733-
cc.t.connPool().MarkDead(cc)
734-
return
735-
}
736-
}
737-
}
738-
}
739-
740-
func (cc *ClientConn) startHealthCheck() {
741-
cc.hmu.Lock()
742-
defer cc.hmu.Unlock()
743-
if cc.healthCheckStopCh != nil {
744-
// a health check is already running
745-
return
746-
}
747-
cc.healthCheckStopCh = make(chan struct{})
748-
go cc.healthCheck(cc.healthCheckStopCh)
749-
}
750-
751-
func (cc *ClientConn) stopHealthCheck() {
752-
cc.hmu.Lock()
753-
defer cc.hmu.Unlock()
754-
if cc.healthCheckStopCh == nil {
755-
// no health check running
708+
// We don't need to periodically ping in the health check, because the readLoop of ClientConn will
709+
// trigger the healthCheck again if there is no frame received.
710+
ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
711+
err := cc.Ping(ctx)
712+
cancel()
713+
if err != nil {
714+
cc.closeForLostPing()
715+
cc.t.connPool().MarkDead(cc)
756716
return
757717
}
758-
close(cc.healthCheckStopCh)
759-
cc.healthCheckStopCh = nil
760718
}
761719

762720
func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
@@ -1804,11 +1762,15 @@ func (rl *clientConnReadLoop) run() error {
18041762
gotReply := false // ever saw a HEADERS reply
18051763
gotSettings := false
18061764
to := cc.t.readIdleTimeout()
1807-
t := time.AfterFunc(to, cc.startHealthCheck)
1765+
var t *time.Timer
1766+
if to != 0 {
1767+
t = time.AfterFunc(to, cc.healthCheck)
1768+
}
18081769
for {
18091770
f, err := cc.fr.ReadFrame()
1810-
t.Reset(to)
1811-
cc.stopHealthCheck()
1771+
if to != 0 {
1772+
t.Reset(to)
1773+
}
18121774
if err != nil {
18131775
cc.vlogf("http2: Transport readFrame error on conn %p: (%T) %v", cc, err, err)
18141776
}

http2/transport_test.go

+14-19
Original file line numberDiff line numberDiff line change
@@ -3247,16 +3247,15 @@ func TestTransportNoRaceOnRequestObjectAfterRequestComplete(t *testing.T) {
32473247
func TestTransportCloseAfterLostPing(t *testing.T) {
32483248
clientDone := make(chan struct{})
32493249
ct := newClientTester(t)
3250-
ct.tr.PingPeriod = 1 * time.Second
32513250
ct.tr.PingTimeout = 1 * time.Second
32523251
ct.tr.ReadIdleTimeout = 1 * time.Second
32533252
ct.client = func() error {
32543253
defer ct.cc.(*net.TCPConn).CloseWrite()
32553254
defer close(clientDone)
32563255
req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
32573256
_, err := ct.tr.RoundTrip(req)
3258-
if err == nil || !strings.Contains(err.Error(), "ping frame is not responded") {
3259-
return fmt.Errorf("expected to get error about \"ping frame is not responded\", got %v", err)
3257+
if err == nil || !strings.Contains(err.Error(), "ping frame was not answered") {
3258+
return fmt.Errorf("expected to get error about \"ping frame was not answered\", got %v", err)
32603259
}
32613260
return nil
32623261
}
@@ -3269,16 +3268,15 @@ func TestTransportCloseAfterLostPing(t *testing.T) {
32693268
}
32703269

32713270
func TestTransportPingWhenReading(t *testing.T) {
3272-
testTransportPingWhenReading(t, 50*time.Millisecond, 100*time.Millisecond)
3273-
testTransportPingWhenReading(t, 100*time.Millisecond, 50*time.Millisecond)
3271+
testTransportPingWhenReading(t, 50*time.Millisecond, 110*time.Millisecond, 20)
3272+
testTransportPingWhenReading(t, 100*time.Millisecond, 50*time.Millisecond, 0)
32743273
}
32753274

3276-
func testTransportPingWhenReading(t *testing.T, readIdleTimeout, serverResponseInterval time.Duration) {
3277-
var pinged bool
3275+
func testTransportPingWhenReading(t *testing.T, readIdleTimeout, serverResponseInterval time.Duration, expectedPingCount int) {
3276+
var pingCount int
32783277
clientBodyBytes := []byte("hello, this is client")
32793278
clientDone := make(chan struct{})
32803279
ct := newClientTester(t)
3281-
ct.tr.PingPeriod = 10 * time.Millisecond
32823280
ct.tr.PingTimeout = 10 * time.Millisecond
32833281
ct.tr.ReadIdleTimeout = readIdleTimeout
32843282
// guards the ct.fr.Write
@@ -3375,25 +3373,22 @@ func testTransportPingWhenReading(t *testing.T, readIdleTimeout, serverResponseI
33753373
}()
33763374
}
33773375
case *PingFrame:
3378-
pinged = true
3379-
if serverResponseInterval > readIdleTimeout {
3380-
wmu.Lock()
3381-
if err := ct.fr.WritePing(true, f.Data); err != nil {
3382-
wmu.Unlock()
3383-
return err
3384-
}
3376+
pingCount++
3377+
wmu.Lock()
3378+
if err := ct.fr.WritePing(true, f.Data); err != nil {
33853379
wmu.Unlock()
3386-
} else {
3387-
return fmt.Errorf("Unexpected ping frame: %v", f)
3380+
return err
33883381
}
3382+
wmu.Unlock()
33893383
default:
33903384
return fmt.Errorf("Unexpected client frame %v", f)
33913385
}
33923386
}
33933387
}
33943388
ct.run()
3395-
if serverResponseInterval > readIdleTimeout && !pinged {
3396-
t.Errorf("expect ping")
3389+
if e, a := expectedPingCount, pingCount; e != a {
3390+
t.Errorf("expected receiving %d pings, got %d pings", e, a)
3391+
33973392
}
33983393
}
33993394

0 commit comments

Comments
 (0)