Skip to content

Commit c33d378

Browse files
committed
http2: add Server.IdleTimeout
Also remove some stale TODOs and finish one of the TODOs, ignoring more incoming frames when the connection is in GOAWAY mode. Also, fix independently-broken transport test from a change in std: https://golang.org/cl/31595 Updates golang/go#14204 Change-Id: Iae8b02248464d613979c30d8f86eacb329cd262f Reviewed-on: https://go-review.googlesource.com/31727 Reviewed-by: Ian Lance Taylor <iant@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
1 parent f11d712 commit c33d378

File tree

3 files changed

+100
-12
lines changed

3 files changed

+100
-12
lines changed

http2/server.go

+41-11
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,6 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// TODO: replace all <-sc.doneServing with reads from the stream's cw
6-
// instead, and make sure that on close we close all open
7-
// streams. then remove doneServing?
8-
9-
// TODO: re-audit GOAWAY support. Consider each incoming frame type and
10-
// whether it should be ignored during graceful shutdown.
11-
12-
// TODO: disconnect idle clients. GFE seems to do 4 minutes. make
13-
// configurable? or maximum number of idle clients and remove the
14-
// oldest?
15-
165
// TODO: turn off the serve goroutine when idle, so
176
// an idle conn only has the readFrames goroutine active. (which could
187
// also be optimized probably to pin less memory in crypto/tls). This
@@ -114,6 +103,11 @@ type Server struct {
114103
// PermitProhibitedCipherSuites, if true, permits the use of
115104
// cipher suites prohibited by the HTTP/2 spec.
116105
PermitProhibitedCipherSuites bool
106+
107+
// IdleTimeout specifies how long until idle clients should be
108+
// closed with a GOAWAY frame. PING frames are not considered
109+
// activity for the purposes of IdleTimeout.
110+
IdleTimeout time.Duration
117111
}
118112

119113
func (s *Server) maxReadFrameSize() uint32 {
@@ -390,6 +384,8 @@ type serverConn struct {
390384
goAwayCode ErrCode
391385
shutdownTimerCh <-chan time.Time // nil until used
392386
shutdownTimer *time.Timer // nil until used
387+
idleTimer *time.Timer // nil if unused
388+
idleTimerCh <-chan time.Time // nil if unused
393389

394390
// Owned by the writeFrameAsync goroutine:
395391
headerWriteBuf bytes.Buffer
@@ -681,6 +677,12 @@ func (sc *serverConn) serve() {
681677
sc.setConnState(http.StateActive)
682678
sc.setConnState(http.StateIdle)
683679

680+
if sc.srv.IdleTimeout != 0 {
681+
sc.idleTimer = time.NewTimer(sc.srv.IdleTimeout)
682+
defer sc.idleTimer.Stop()
683+
sc.idleTimerCh = sc.idleTimer.C
684+
}
685+
684686
go sc.readFrames() // closed by defer sc.conn.Close above
685687

686688
settingsTimer := time.NewTimer(firstSettingsTimeout)
@@ -709,6 +711,9 @@ func (sc *serverConn) serve() {
709711
case <-sc.shutdownTimerCh:
710712
sc.vlogf("GOAWAY close timer fired; closing conn from %v", sc.conn.RemoteAddr())
711713
return
714+
case <-sc.idleTimerCh:
715+
sc.vlogf("connection is idle")
716+
sc.goAway(ErrCodeNo)
712717
case fn := <-sc.testHookCh:
713718
fn(loopNum)
714719
}
@@ -1114,12 +1119,18 @@ func (sc *serverConn) processPing(f *PingFrame) error {
11141119
// PROTOCOL_ERROR."
11151120
return ConnectionError(ErrCodeProtocol)
11161121
}
1122+
if sc.inGoAway {
1123+
return nil
1124+
}
11171125
sc.writeFrame(frameWriteMsg{write: writePingAck{f}})
11181126
return nil
11191127
}
11201128

11211129
func (sc *serverConn) processWindowUpdate(f *WindowUpdateFrame) error {
11221130
sc.serveG.check()
1131+
if sc.inGoAway {
1132+
return nil
1133+
}
11231134
switch {
11241135
case f.StreamID != 0: // stream-level flow control
11251136
state, st := sc.state(f.StreamID)
@@ -1152,6 +1163,9 @@ func (sc *serverConn) processWindowUpdate(f *WindowUpdateFrame) error {
11521163

11531164
func (sc *serverConn) processResetStream(f *RSTStreamFrame) error {
11541165
sc.serveG.check()
1166+
if sc.inGoAway {
1167+
return nil
1168+
}
11551169

11561170
state, st := sc.state(f.StreamID)
11571171
if state == stateIdle {
@@ -1181,6 +1195,9 @@ func (sc *serverConn) closeStream(st *stream, err error) {
11811195
sc.setConnState(http.StateIdle)
11821196
}
11831197
delete(sc.streams, st.id)
1198+
if len(sc.streams) == 0 && sc.srv.IdleTimeout != 0 {
1199+
sc.idleTimer.Reset(sc.srv.IdleTimeout)
1200+
}
11841201
if p := st.body; p != nil {
11851202
// Return any buffered unread bytes worth of conn-level flow control.
11861203
// See golang.org/issue/16481
@@ -1204,6 +1221,9 @@ func (sc *serverConn) processSettings(f *SettingsFrame) error {
12041221
}
12051222
return nil
12061223
}
1224+
if sc.inGoAway {
1225+
return nil
1226+
}
12071227
if err := f.ForeachSetting(sc.processSetting); err != nil {
12081228
return err
12091229
}
@@ -1275,6 +1295,9 @@ func (sc *serverConn) processSettingInitialWindowSize(val uint32) error {
12751295

12761296
func (sc *serverConn) processData(f *DataFrame) error {
12771297
sc.serveG.check()
1298+
if sc.inGoAway {
1299+
return nil
1300+
}
12781301
data := f.Data()
12791302

12801303
// "If a DATA frame is received whose stream is not in "open"
@@ -1412,6 +1435,10 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
14121435
}
14131436
sc.maxStreamID = id
14141437

1438+
if sc.idleTimer != nil {
1439+
sc.idleTimer.Stop()
1440+
}
1441+
14151442
ctx, cancelCtx := contextWithCancel(sc.baseCtx)
14161443
st = &stream{
14171444
sc: sc,
@@ -1524,6 +1551,9 @@ func (st *stream) processTrailerHeaders(f *MetaHeadersFrame) error {
15241551
}
15251552

15261553
func (sc *serverConn) processPriority(f *PriorityFrame) error {
1554+
if sc.inGoAway {
1555+
return nil
1556+
}
15271557
adjustStreamPriority(sc.streams, f.StreamID, f.PriorityParam)
15281558
return nil
15291559
}

http2/server_test.go

+50-1
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,15 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{}
8686
}
8787

8888
var onlyServer, quiet bool
89+
h2server := new(Server)
8990
for _, opt := range opts {
9091
switch v := opt.(type) {
9192
case func(*tls.Config):
9293
v(tlsConfig)
9394
case func(*httptest.Server):
9495
v(ts)
96+
case func(*Server):
97+
v(h2server)
9598
case serverTesterOpt:
9699
switch v {
97100
case optOnlyServer:
@@ -106,7 +109,7 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{}
106109
}
107110
}
108111

109-
ConfigureServer(ts.Config, &Server{})
112+
ConfigureServer(ts.Config, h2server)
110113

111114
st := &serverTester{
112115
t: t,
@@ -3406,6 +3409,52 @@ func TestUnreadFlowControlReturned_Server(t *testing.T) {
34063409

34073410
}
34083411

3412+
func TestServerIdleTimeout(t *testing.T) {
3413+
if testing.Short() {
3414+
t.Skip("skipping in short mode")
3415+
}
3416+
3417+
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
3418+
}, func(h2s *Server) {
3419+
h2s.IdleTimeout = 500 * time.Millisecond
3420+
})
3421+
defer st.Close()
3422+
3423+
st.greet()
3424+
ga := st.wantGoAway()
3425+
if ga.ErrCode != ErrCodeNo {
3426+
t.Errorf("GOAWAY error = %v; want ErrCodeNo", ga.ErrCode)
3427+
}
3428+
}
3429+
3430+
func TestServerIdleTimeout_AfterRequest(t *testing.T) {
3431+
if testing.Short() {
3432+
t.Skip("skipping in short mode")
3433+
}
3434+
const timeout = 250 * time.Millisecond
3435+
3436+
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
3437+
time.Sleep(timeout * 2)
3438+
}, func(h2s *Server) {
3439+
h2s.IdleTimeout = timeout
3440+
})
3441+
defer st.Close()
3442+
3443+
st.greet()
3444+
3445+
// Send a request which takes twice the timeout. Verifies the
3446+
// idle timeout doesn't fire while we're in a request:
3447+
st.bodylessReq1()
3448+
st.wantHeaders()
3449+
3450+
// But the idle timeout should be rearmed after the request
3451+
// is done:
3452+
ga := st.wantGoAway()
3453+
if ga.ErrCode != ErrCodeNo {
3454+
t.Errorf("GOAWAY error = %v; want ErrCodeNo", ga.ErrCode)
3455+
}
3456+
}
3457+
34093458
// grpc-go closes the Request.Body currently with a Read.
34103459
// Verify that it doesn't race.
34113460
// See https://github.com/grpc/grpc-go/pull/938

http2/transport_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -1947,8 +1947,17 @@ func TestTransportNewTLSConfig(t *testing.T) {
19471947
},
19481948
}
19491949
for i, tt := range tests {
1950+
// Ignore the session ticket keys part, which ends up populating
1951+
// unexported fields in the Config:
1952+
if tt.conf != nil {
1953+
tt.conf.SessionTicketsDisabled = true
1954+
}
1955+
19501956
tr := &Transport{TLSClientConfig: tt.conf}
19511957
got := tr.newTLSConfig(tt.host)
1958+
1959+
got.SessionTicketsDisabled = false
1960+
19521961
if !reflect.DeepEqual(got, tt.want) {
19531962
t.Errorf("%d. got %#v; want %#v", i, got, tt.want)
19541963
}

0 commit comments

Comments
 (0)