Skip to content

Commit 3405706

Browse files
committed
http2: reduce the number of select cases in serverConn.server
This drops the number of select cases in serverConn.server from 10 to 6. It was 7 in Go 1.7. It increased to 10 in Go 1.8. * replace testing-only testHookCh with always-on grab-bag serveMsgCh to be used for both test purposes, and infrequently used message types * remove the settingsTimer.C case for the initial settings timer and use serveMsgCh and time.AfterFunc instead * ... and do the same for the idle timeout timer. * ... and for the shutdown timer. * remove wantStartPushCh and just send the *startPushRequest to serveMsgCh too I could go further with this (and plan to, later), but these are the safe and easy ones that don't require more work elsewhere. The speed gets better the more the request/response go via the serverConn.serve loop. (once per Request.Body.Read or ResponseWriter.Flush): name old time/op new time/op delta ServerGets-4 138µs ± 3% 134µs ± 2% -2.54% (p=0.002 n=10+10) ServerPosts-4 176µs ±27% 154µs ± 2% -12.62% (p=0.011 n=10+10) Updates kubernetes/kubernetes#45216 Updates golang/go#20302 Change-Id: I18019554089d7e3d76355d7137b5957e9597e803 Reviewed-on: https://go-review.googlesource.com/43034 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Tom Bergan <tombergan@google.com>
1 parent 84f0e6f commit 3405706

File tree

3 files changed

+62
-35
lines changed

3 files changed

+62
-35
lines changed

http2/server.go

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
292292
streams: make(map[uint32]*stream),
293293
readFrameCh: make(chan readFrameResult),
294294
wantWriteFrameCh: make(chan FrameWriteRequest, 8),
295-
wantStartPushCh: make(chan startPushRequest, 8),
295+
serveMsgCh: make(chan interface{}, 8),
296296
wroteFrameCh: make(chan frameWriteResult, 1), // buffered; one send in writeFrameAsync
297297
bodyReadCh: make(chan bodyReadMsg), // buffering doesn't matter either way
298298
doneServing: make(chan struct{}),
@@ -405,10 +405,9 @@ type serverConn struct {
405405
doneServing chan struct{} // closed when serverConn.serve ends
406406
readFrameCh chan readFrameResult // written by serverConn.readFrames
407407
wantWriteFrameCh chan FrameWriteRequest // from handlers -> serve
408-
wantStartPushCh chan startPushRequest // from handlers -> serve
409408
wroteFrameCh chan frameWriteResult // from writeFrameAsync -> serve, tickles more frame writes
410409
bodyReadCh chan bodyReadMsg // from handlers -> serve
411-
testHookCh chan func(int) // code to run on the serve loop
410+
serveMsgCh chan interface{} // misc messages & code to send to / run on the serve loop
412411
flow flow // conn-wide (not stream-specific) outbound flow control
413412
inflow flow // conn-wide inbound flow control
414413
tlsState *tls.ConnectionState // shared by all handlers, like net/http
@@ -440,10 +439,8 @@ type serverConn struct {
440439
inFrameScheduleLoop bool // whether we're in the scheduleFrameWrite loop
441440
needToSendGoAway bool // we need to schedule a GOAWAY frame write
442441
goAwayCode ErrCode
443-
shutdownTimerCh <-chan time.Time // nil until used
444-
shutdownTimer *time.Timer // nil until used
445-
idleTimer *time.Timer // nil if unused
446-
idleTimerCh <-chan time.Time // nil if unused
442+
shutdownTimer *time.Timer // nil until used
443+
idleTimer *time.Timer // nil if unused
447444

448445
// Owned by the writeFrameAsync goroutine:
449446
headerWriteBuf bytes.Buffer
@@ -748,9 +745,8 @@ func (sc *serverConn) serve() {
748745
sc.setConnState(http.StateIdle)
749746

750747
if sc.srv.IdleTimeout != 0 {
751-
sc.idleTimer = time.NewTimer(sc.srv.IdleTimeout)
748+
sc.idleTimer = time.AfterFunc(sc.srv.IdleTimeout, sc.onIdleTimer)
752749
defer sc.idleTimer.Stop()
753-
sc.idleTimerCh = sc.idleTimer.C
754750
}
755751

756752
var gracefulShutdownCh chan struct{}
@@ -764,7 +760,9 @@ func (sc *serverConn) serve() {
764760

765761
go sc.readFrames() // closed by defer sc.conn.Close above
766762

767-
settingsTimer := time.NewTimer(firstSettingsTimeout)
763+
settingsTimer := time.AfterFunc(firstSettingsTimeout, sc.onSettingsTimer)
764+
defer settingsTimer.Stop()
765+
768766
loopNum := 0
769767
for {
770768
loopNum++
@@ -775,35 +773,45 @@ func (sc *serverConn) serve() {
775773
break
776774
}
777775
sc.writeFrame(wr)
778-
case spr := <-sc.wantStartPushCh:
779-
sc.startPush(spr)
780776
case res := <-sc.wroteFrameCh:
781777
sc.wroteFrame(res)
782778
case res := <-sc.readFrameCh:
783779
if !sc.processFrameFromReader(res) {
784780
return
785781
}
786782
res.readMore()
787-
if settingsTimer.C != nil {
783+
if settingsTimer != nil {
788784
settingsTimer.Stop()
789-
settingsTimer.C = nil
785+
settingsTimer = nil
790786
}
791787
case m := <-sc.bodyReadCh:
792788
sc.noteBodyRead(m.st, m.n)
793-
case <-settingsTimer.C:
794-
sc.logf("timeout waiting for SETTINGS frames from %v", sc.conn.RemoteAddr())
795-
return
796789
case <-gracefulShutdownCh:
797790
gracefulShutdownCh = nil
798791
sc.startGracefulShutdown()
799-
case <-sc.shutdownTimerCh:
800-
sc.vlogf("GOAWAY close timer fired; closing conn from %v", sc.conn.RemoteAddr())
801-
return
802-
case <-sc.idleTimerCh:
803-
sc.vlogf("connection is idle")
804-
sc.goAway(ErrCodeNo)
805-
case fn := <-sc.testHookCh:
806-
fn(loopNum)
792+
case msg := <-sc.serveMsgCh:
793+
switch v := msg.(type) {
794+
case func(int):
795+
v(loopNum) // for testing
796+
case *timerMessage:
797+
switch v {
798+
case settingsTimerMsg:
799+
sc.logf("timeout waiting for SETTINGS frames from %v", sc.conn.RemoteAddr())
800+
return
801+
case idleTimerMsg:
802+
sc.vlogf("connection is idle")
803+
sc.goAway(ErrCodeNo)
804+
case shutdownTimerMsg:
805+
sc.vlogf("GOAWAY close timer fired; closing conn from %v", sc.conn.RemoteAddr())
806+
return
807+
default:
808+
panic("unknown timer")
809+
}
810+
case *startPushRequest:
811+
sc.startPush(v)
812+
default:
813+
panic(fmt.Sprintf("unexpected type %T", v))
814+
}
807815
}
808816

809817
if sc.inGoAway && sc.curOpenStreams() == 0 && !sc.needToSendGoAway && !sc.writingFrame {
@@ -820,6 +828,27 @@ func (sc *serverConn) awaitGracefulShutdown(sharedCh <-chan struct{}, privateCh
820828
}
821829
}
822830

831+
type timerMessage int
832+
833+
// Timeout message values sent to serveMsgCh.
834+
var (
835+
settingsTimerMsg = new(timerMessage)
836+
idleTimerMsg = new(timerMessage)
837+
shutdownTimerMsg = new(timerMessage)
838+
)
839+
840+
func (sc *serverConn) onSettingsTimer() { sc.sendServeMsg(settingsTimerMsg) }
841+
func (sc *serverConn) onIdleTimer() { sc.sendServeMsg(idleTimerMsg) }
842+
func (sc *serverConn) onShutdownTimer() { sc.sendServeMsg(shutdownTimerMsg) }
843+
844+
func (sc *serverConn) sendServeMsg(msg interface{}) {
845+
sc.serveG.checkNotOn() // NOT
846+
select {
847+
case sc.serveMsgCh <- msg:
848+
case <-sc.doneServing:
849+
}
850+
}
851+
823852
// readPreface reads the ClientPreface greeting from the peer
824853
// or returns an error on timeout or an invalid greeting.
825854
func (sc *serverConn) readPreface() error {
@@ -1172,8 +1201,7 @@ func (sc *serverConn) goAwayIn(code ErrCode, forceCloseIn time.Duration) {
11721201

11731202
func (sc *serverConn) shutDownIn(d time.Duration) {
11741203
sc.serveG.check()
1175-
sc.shutdownTimer = time.NewTimer(d)
1176-
sc.shutdownTimerCh = sc.shutdownTimer.C
1204+
sc.shutdownTimer = time.AfterFunc(d, sc.onShutdownTimer)
11771205
}
11781206

11791207
func (sc *serverConn) resetStream(se StreamError) {
@@ -2551,7 +2579,7 @@ func (w *responseWriter) push(target string, opts pushOptions) error {
25512579
return fmt.Errorf("method %q must be GET or HEAD", opts.Method)
25522580
}
25532581

2554-
msg := startPushRequest{
2582+
msg := &startPushRequest{
25552583
parent: st,
25562584
method: opts.Method,
25572585
url: u,
@@ -2564,7 +2592,7 @@ func (w *responseWriter) push(target string, opts pushOptions) error {
25642592
return errClientDisconnected
25652593
case <-st.cw:
25662594
return errStreamClosed
2567-
case sc.wantStartPushCh <- msg:
2595+
case sc.serveMsgCh <- msg:
25682596
}
25692597

25702598
select {
@@ -2586,7 +2614,7 @@ type startPushRequest struct {
25862614
done chan error
25872615
}
25882616

2589-
func (sc *serverConn) startPush(msg startPushRequest) {
2617+
func (sc *serverConn) startPush(msg *startPushRequest) {
25902618
sc.serveG.check()
25912619

25922620
// http://tools.ietf.org/html/rfc7540#section-6.6.

http2/server_push_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ func TestServer_Push_RejectAfterGoAway(t *testing.T) {
508508
return
509509
default:
510510
}
511-
st.sc.testHookCh <- func(loopNum int) {
511+
st.sc.serveMsgCh <- func(loopNum int) {
512512
if !st.sc.pushEnabled {
513513
readyOnce.Do(func() { close(ready) })
514514
}

http2/server_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{}
142142
st.scMu.Lock()
143143
defer st.scMu.Unlock()
144144
st.sc = v
145-
st.sc.testHookCh = make(chan func(int))
146145
}
147146
log.SetOutput(io.MultiWriter(stderrv(), twriter{t: t, st: st}))
148147
if !onlyServer {
@@ -187,15 +186,15 @@ func (st *serverTester) addLogFilter(phrase string) {
187186

188187
func (st *serverTester) stream(id uint32) *stream {
189188
ch := make(chan *stream, 1)
190-
st.sc.testHookCh <- func(int) {
189+
st.sc.serveMsgCh <- func(int) {
191190
ch <- st.sc.streams[id]
192191
}
193192
return <-ch
194193
}
195194

196195
func (st *serverTester) streamState(id uint32) streamState {
197196
ch := make(chan streamState, 1)
198-
st.sc.testHookCh <- func(int) {
197+
st.sc.serveMsgCh <- func(int) {
199198
state, _ := st.sc.state(id)
200199
ch <- state
201200
}
@@ -205,7 +204,7 @@ func (st *serverTester) streamState(id uint32) streamState {
205204
// loopNum reports how many times this conn's select loop has gone around.
206205
func (st *serverTester) loopNum() int {
207206
lastc := make(chan int, 1)
208-
st.sc.testHookCh <- func(loopNum int) {
207+
st.sc.serveMsgCh <- func(loopNum int) {
209208
lastc <- loopNum
210209
}
211210
return <-lastc

0 commit comments

Comments
 (0)