Skip to content

Commit 73058b0

Browse files
committed
http2: add support for graceful shutdown of Server
This adds support for gracefully shutting down the Server, including sending a GOAWAY frame to clients and shutting down when things are idle. For now this support is only when integrated with the standard library's Server. A future change could export some of this. Updates golang/go#4674 Change-Id: I78cd4f58ca529bf9d149054f929d9089e7685875 Reviewed-on: https://go-review.googlesource.com/32412 Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent d6520b8 commit 73058b0

File tree

2 files changed

+79
-6
lines changed

2 files changed

+79
-6
lines changed

http2/server.go

+34-6
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,13 @@ func ConfigureServer(s *http.Server, conf *Server) error {
213213
return nil
214214
}
215215

216+
// h1ServerShutdownChan if non-nil provides a func to return a channel
217+
// that will be closed when the provided *http.Server wants to shut
218+
// down. This is initialized via an init func in net/http (via its
219+
// mangled name from x/tools/cmd/bundle). This is only used when http2
220+
// is bundled into std for now.
221+
var h1ServerShutdownChan func(*http.Server) <-chan struct{}
222+
216223
// ServeConnOpts are options for the Server.ServeConn method.
217224
type ServeConnOpts struct {
218225
// BaseConfig optionally sets the base configuration
@@ -710,6 +717,11 @@ func (sc *serverConn) serve() {
710717
sc.idleTimerCh = sc.idleTimer.C
711718
}
712719

720+
var gracefulShutdownCh <-chan struct{}
721+
if sc.hs != nil && h1ServerShutdownChan != nil {
722+
gracefulShutdownCh = h1ServerShutdownChan(sc.hs)
723+
}
724+
713725
go sc.readFrames() // closed by defer sc.conn.Close above
714726

715727
settingsTimer := time.NewTimer(firstSettingsTimeout)
@@ -737,6 +749,9 @@ func (sc *serverConn) serve() {
737749
case <-settingsTimer.C:
738750
sc.logf("timeout waiting for SETTINGS frames from %v", sc.conn.RemoteAddr())
739751
return
752+
case <-gracefulShutdownCh:
753+
gracefulShutdownCh = nil
754+
sc.goAwayIn(ErrCodeNo, 0)
740755
case <-sc.shutdownTimerCh:
741756
sc.vlogf("GOAWAY close timer fired; closing conn from %v", sc.conn.RemoteAddr())
742757
return
@@ -746,6 +761,10 @@ func (sc *serverConn) serve() {
746761
case fn := <-sc.testHookCh:
747762
fn(loopNum)
748763
}
764+
765+
if sc.inGoAway && sc.curClientStreams == 0 && !sc.needToSendGoAway && !sc.writingFrame {
766+
return
767+
}
749768
}
750769
}
751770

@@ -1018,7 +1037,7 @@ func (sc *serverConn) scheduleFrameWrite() {
10181037
sc.startFrameWrite(FrameWriteRequest{write: writeSettingsAck{}})
10191038
continue
10201039
}
1021-
if !sc.inGoAway {
1040+
if !sc.inGoAway || sc.goAwayCode == ErrCodeNo {
10221041
if wr, ok := sc.writeSched.Pop(); ok {
10231042
sc.startFrameWrite(wr)
10241043
continue
@@ -1036,14 +1055,23 @@ func (sc *serverConn) scheduleFrameWrite() {
10361055

10371056
func (sc *serverConn) goAway(code ErrCode) {
10381057
sc.serveG.check()
1039-
if sc.inGoAway {
1040-
return
1041-
}
1058+
var forceCloseIn time.Duration
10421059
if code != ErrCodeNo {
1043-
sc.shutDownIn(250 * time.Millisecond)
1060+
forceCloseIn = 250 * time.Millisecond
10441061
} else {
10451062
// TODO: configurable
1046-
sc.shutDownIn(1 * time.Second)
1063+
forceCloseIn = 1 * time.Second
1064+
}
1065+
sc.goAwayIn(code, forceCloseIn)
1066+
}
1067+
1068+
func (sc *serverConn) goAwayIn(code ErrCode, forceCloseIn time.Duration) {
1069+
sc.serveG.check()
1070+
if sc.inGoAway {
1071+
return
1072+
}
1073+
if forceCloseIn != 0 {
1074+
sc.shutDownIn(forceCloseIn)
10471075
}
10481076
sc.inGoAway = true
10491077
sc.needToSendGoAway = true

http2/server_test.go

+45
Original file line numberDiff line numberDiff line change
@@ -3530,3 +3530,48 @@ func TestRequestBodyReadCloseRace(t *testing.T) {
35303530
<-done
35313531
}
35323532
}
3533+
3534+
func TestServerGracefulShutdown(t *testing.T) {
3535+
shutdownCh := make(chan struct{})
3536+
defer func() { h1ServerShutdownChan = nil }()
3537+
h1ServerShutdownChan = func(*http.Server) <-chan struct{} { return shutdownCh }
3538+
3539+
var st *serverTester
3540+
handlerDone := make(chan struct{})
3541+
st = newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
3542+
defer close(handlerDone)
3543+
close(shutdownCh)
3544+
3545+
ga := st.wantGoAway()
3546+
if ga.ErrCode != ErrCodeNo {
3547+
t.Errorf("GOAWAY error = %v; want ErrCodeNo", ga.ErrCode)
3548+
}
3549+
if ga.LastStreamID != 1 {
3550+
t.Errorf("GOAWAY LastStreamID = %v; want 1", ga.LastStreamID)
3551+
}
3552+
3553+
w.Header().Set("x-foo", "bar")
3554+
})
3555+
defer st.Close()
3556+
3557+
st.greet()
3558+
st.bodylessReq1()
3559+
3560+
<-handlerDone
3561+
hf := st.wantHeaders()
3562+
goth := st.decodeHeader(hf.HeaderBlockFragment())
3563+
wanth := [][2]string{
3564+
{":status", "200"},
3565+
{"x-foo", "bar"},
3566+
{"content-type", "text/plain; charset=utf-8"},
3567+
{"content-length", "0"},
3568+
}
3569+
if !reflect.DeepEqual(goth, wanth) {
3570+
t.Errorf("Got headers %v; want %v", goth, wanth)
3571+
}
3572+
3573+
n, err := st.cc.Read([]byte{0})
3574+
if n != 0 || err == nil {
3575+
t.Errorf("Read = %v, %v; want 0, non-nil", n, err)
3576+
}
3577+
}

0 commit comments

Comments
 (0)