Remove the writeFrames goroutine.
It isn't cheaper to keep it running. It only wastes memory on idle
connections. Instead, just create a goroutine for each frame write.
We still only do one at a time.
diff --git a/server.go b/server.go
index f819422..d2d2756 100644
--- a/server.go
+++ b/server.go
@@ -164,8 +164,7 @@
readFrameCh: make(chan frameAndGate),
readFrameErrCh: make(chan error, 1), // must be buffered for 1
wantWriteFrameCh: make(chan frameWriteMsg, 8),
- writeFrameCh: make(chan frameWriteMsg, 1), // may be 0 or 1, but more is useless. (max 1 in flight)
- wroteFrameCh: make(chan struct{}, 1), // TODO: consider 0. will deadlock currently in sendFrameWrite in sentReset case
+ wroteFrameCh: make(chan struct{}, 1), // TODO: consider 0. will deadlock currently in sendFrameWrite in sentReset case
flow: newFlow(initialWindowSize),
doneServing: make(chan struct{}),
advMaxStreams: srv.maxConcurrentStreams(),
@@ -211,8 +210,7 @@
readFrameCh chan frameAndGate // written by serverConn.readFrames
readFrameErrCh chan error
wantWriteFrameCh chan frameWriteMsg // from handlers -> serve
- writeFrameCh chan frameWriteMsg // from serve -> writeFrames
- wroteFrameCh chan struct{} // from writeFrames -> serve, tickles more sends on writeFrameCh
+ wroteFrameCh chan struct{} // from writeFrames -> serve, tickles more frame writes
testHookCh chan func() // code to run on the serve loop
serveG goroutineLock // used to verify funcs are on serve()
@@ -234,8 +232,8 @@
maxHeaderListSize uint32 // zero means unknown (default)
canonHeader map[string]string // http2-lower-case -> Go-Canonical-Case
req requestParam // non-zero while reading request headers
- writingFrame bool // sent on writeFrameCh but haven't heard back on wroteFrameCh yet
- needsFrameFlush bool // last frame to writeFrameCh wasn't a flush
+ writingFrame bool // started write goroutine but haven't heard back on wroteFrameCh
+ needsFrameFlush bool // last frame write wasn't a flush
writeQueue []frameWriteMsg // TODO: proper scheduler, not a queue
inGoAway bool // we've started to or sent GOAWAY
needToSendGoAway bool // we need to schedule a GOAWAY frame write
@@ -414,26 +412,25 @@
}
}
-// writeFrames is the loop that writes frames to the peer
-// and is responsible for prioritization and buffering.
-// It's run on its own goroutine.
-func (sc *serverConn) writeFrames() {
+// writeFrameAsync runs in its own goroutine and writes a single frame
+// and then reports when it's done.
+// At most one goroutine can be running writeFrameAsync at a time per
+// serverConn.
+func (sc *serverConn) writeFrameAsync(wm frameWriteMsg) {
sc.writeG = newGoroutineLock()
- for wm := range sc.writeFrameCh {
- var streamID uint32
- if wm.stream != nil {
- streamID = wm.stream.id
- }
- err := wm.write(sc, streamID, wm.v)
- if ch := wm.done; ch != nil {
- select {
- case ch <- err:
- default:
- panic(fmt.Sprintf("unbuffered done channel passed in for type %T", wm.v))
- }
- }
- sc.wroteFrameCh <- struct{}{} // tickle frame selection scheduler
+ var streamID uint32
+ if wm.stream != nil {
+ streamID = wm.stream.id
}
+ err := wm.write(sc, streamID, wm.v)
+ if ch := wm.done; ch != nil {
+ select {
+ case ch <- err:
+ default:
+ panic(fmt.Sprintf("unbuffered done channel passed in for type %T", wm.v))
+ }
+ }
+ sc.wroteFrameCh <- struct{}{} // tickle frame selection scheduler
}
func (sc *serverConn) flushFrameWriter(uint32, interface{}) error {
@@ -460,8 +457,7 @@
defer sc.conn.Close()
defer sc.closeAllStreamsOnConnClose()
defer sc.stopShutdownTimer()
- defer close(sc.doneServing) // unblocks handlers trying to send
- defer close(sc.writeFrameCh) // stop the writeFrames loop
+ defer close(sc.doneServing) // unblocks handlers trying to send
sc.vlogf("HTTP/2 connection from %v on %p", sc.conn.RemoteAddr(), sc.hs)
@@ -472,8 +468,7 @@
return
}
- go sc.readFrames() // closed by defer sc.conn.Close above
- go sc.writeFrames() // source closed in stopServing
+ go sc.readFrames() // closed by defer sc.conn.Close above
settingsTimer := time.NewTimer(firstSettingsTimeout)
for {
@@ -643,7 +638,7 @@
sc.closeStream(st, nil)
}
}
- sc.writeFrameCh <- wm
+ go sc.writeFrameAsync(wm)
}
// scheduleFrameWrite tickles the frame writing scheduler.