Skip to content

Commit f7858bc

Browse files
author
Chao Xu
committed
fix
1 parent bc0d6c6 commit f7858bc

File tree

2 files changed

+76
-54
lines changed

2 files changed

+76
-54
lines changed

http2/client_conn_pool.go

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@
77
package http2
88

99
import (
10-
"context"
1110
"crypto/tls"
1211
"net/http"
1312
"sync"
14-
"time"
1513
)
1614

1715
// ClientConnPool manages a pool of HTTP/2 client connections.
@@ -43,16 +41,6 @@ type clientConnPool struct {
4341
dialing map[string]*dialCall // currently in-flight dials
4442
keys map[*ClientConn][]string
4543
addConnCalls map[string]*addConnCall // in-flight addConnIfNeede calls
46-
47-
// TODO: figure out a way to allow user to configure pingPeriod and
48-
// pingTimeout.
49-
pingPeriod time.Duration // how often pings are sent on idle
50-
// connections. The connection will be closed if response is not
51-
// received within pingTimeout. 0 means no periodic pings.
52-
pingTimeout time.Duration // connection will be force closed if a Ping
53-
// response is not received within pingTimeout.
54-
pingStops map[*ClientConn]chan struct{} // channels to stop the
55-
// periodic Pings.
5644
}
5745

5846
func (p *clientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
@@ -231,54 +219,13 @@ func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) {
231219
if p.keys == nil {
232220
p.keys = make(map[*ClientConn][]string)
233221
}
234-
if p.pingStops == nil {
235-
p.pingStops = make(map[*ClientConn]chan struct{})
236-
}
237222
p.conns[key] = append(p.conns[key], cc)
238223
p.keys[cc] = append(p.keys[cc], key)
239-
if p.pingPeriod != 0 {
240-
p.pingStops[cc] = p.pingConnection(key, cc)
241-
}
242-
}
243-
244-
// TODO: ping all connections at the same tick to save tickers?
245-
func (p *clientConnPool) pingConnection(key string, cc *ClientConn) chan struct{} {
246-
done := make(chan struct{})
247-
go func() {
248-
ticker := time.NewTicker(p.pingPeriod)
249-
defer ticker.Stop()
250-
for {
251-
select {
252-
case <-done:
253-
return
254-
default:
255-
}
256-
257-
select {
258-
case <-done:
259-
return
260-
case <-ticker.C:
261-
ctx, _ := context.WithTimeout(context.Background(), p.pingTimeout)
262-
err := cc.Ping(ctx)
263-
if err != nil {
264-
cc.closeForLostPing()
265-
p.MarkDead(cc)
266-
}
267-
}
268-
}
269-
}()
270-
return done
271224
}
272225

273226
func (p *clientConnPool) MarkDead(cc *ClientConn) {
274227
p.mu.Lock()
275228
defer p.mu.Unlock()
276-
277-
if done, ok := p.pingStops[cc]; ok {
278-
close(done)
279-
delete(p.pingStops, cc)
280-
}
281-
282229
for _, key := range p.keys[cc] {
283230
vv, ok := p.conns[key]
284231
if !ok {

http2/transport.go

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ type ClientConn struct {
243243

244244
wmu sync.Mutex // held while writing; acquire AFTER mu if holding both
245245
werr error // first write error that has occurred
246+
247+
healthCheckCancel chan struct{}
246248
}
247249

248250
// clientStream is the state for a single HTTP/2 stream. One of these
@@ -678,6 +680,44 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
678680
return cc, nil
679681
}
680682

683+
func (cc *ClientConn) healthCheck(cancel chan struct{}) {
684+
// TODO: CHAO: configurable
685+
pingPeriod := 15 * time.Second
686+
ticker := time.NewTicker(pingPeriod)
687+
defer ticker.Stop()
688+
for {
689+
select {
690+
case <-cancel:
691+
return
692+
case <-ticker.C:
693+
ctx, _ := context.WithTimeout(context.Background(), p.pingTimeout)
694+
err := cc.Ping(ctx)
695+
if err != nil {
696+
cc.closeForLostPing()
697+
cc.t.connPool().MarkDead(cc)
698+
}
699+
}
700+
}
701+
}
702+
703+
func (cc *ClientConn) startHealthCheck() {
704+
if cc.healthCheckCancel != nil {
705+
// a health check is already running
706+
return
707+
}
708+
cc.healthCheckCancel = make(chan struct{})
709+
go cc.healthCheck(cc.healthCheckCancel)
710+
}
711+
712+
func (cc *ClientConn) stopHealthCheck() {
713+
if cc.healthCheckCancel == nil {
714+
// no health check running
715+
return
716+
}
717+
close(cc.healthCheckCancel)
718+
cc.healthCheckCancel = nil
719+
}
720+
681721
func (cc *ClientConn) setGoAway(f *GoAwayFrame) {
682722
cc.mu.Lock()
683723
defer cc.mu.Unlock()
@@ -1717,13 +1757,48 @@ func (rl *clientConnReadLoop) cleanup() {
17171757
cc.mu.Unlock()
17181758
}
17191759

1760+
func ReadFrameAndProbablyStartOrStopPingLoop() {
1761+
select {
1762+
case <-timer:
1763+
// start ping loop
1764+
case <-read:
1765+
// stop ping loop
1766+
}
1767+
}
1768+
1769+
type frameAndError struct {
1770+
f Frame
1771+
err error
1772+
}
1773+
1774+
func nonBlockingReadFrame(f *Framer) chan frameAndError {
1775+
feCh := make(chan FrameAndError)
1776+
go func() {
1777+
f, err := fr.ReadFrame()
1778+
feCh <- frameAndError{frame: f, err: err}
1779+
}()
1780+
return feCh
1781+
}
1782+
17201783
func (rl *clientConnReadLoop) run() error {
17211784
cc := rl.cc
17221785
rl.closeWhenIdle = cc.t.disableKeepAlives() || cc.singleUse
17231786
gotReply := false // ever saw a HEADERS reply
17241787
gotSettings := false
17251788
for {
1726-
f, err := cc.fr.ReadFrame()
1789+
var fe frameAndError
1790+
feCh := nonBlockingReadFrame(cc.fr)
1791+
// TODO: CHAO: make it configurable
1792+
readIdleTimer := time.NewTimer(15 * time.Second)
1793+
select {
1794+
case fe <- feCh:
1795+
cc.stopHealthCheck()
1796+
readIdleTimer.Stop()
1797+
case <-readIdleTimer.C:
1798+
cc.startHealthCheck()
1799+
fe <- feCh
1800+
}
1801+
f, err := fe.f, fe.err
17271802
if err != nil {
17281803
cc.vlogf("http2: Transport readFrame error on conn %p: (%T) %v", cc, err, err)
17291804
}

0 commit comments

Comments
 (0)