blob: 6365ec0f565ea08b0f6fd96ef3e2f515622bf199 [file] [log] [blame]
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
// Licensed under the same terms as Go itself:
// https://code.google.com/p/go/source/browse/LICENSE
package http2
// frameWriteMsg is a request to write a frame.
type frameWriteMsg struct {
// write is the function that does the writing, once the
// writeScheduler (below) has decided to select this frame
// to write. The write functions are all defined in write.go.
write func(ctx writeContext, v interface{}) error
// v is the argument passed to the write function. See each
// function in write.go to see which type they should be,
// depending on what write is.
v interface{}
cost uint32 // if DATA, number of flow control bytes required
stream *stream // used for prioritization
endStream bool // stream is being closed locally
// done, if non-nil, must be a buffered channel with space for
// 1 message and is sent the return value from write (or an
// earlier error) when the frame has been written.
done chan error
}
// writeScheduler tracks pending frames to write, priorities, and decides
// the next one to use. It is not thread-safe.
type writeScheduler struct {
// zero are frames not associated with a specific stream.
// They're sent before any stream-specific freams.
zero writeQueue
sq map[uint32]*writeQueue
}
func (ws *writeScheduler) empty() bool { return ws.zero.empty() && len(ws.sq) == 0 }
func (ws *writeScheduler) add(wm frameWriteMsg) {
st := wm.stream
if st == nil {
ws.zero.push(wm)
} else {
ws.streamQueue(st.id).push(wm)
}
}
func (ws *writeScheduler) streamQueue(streamID uint32) *writeQueue {
if q, ok := ws.sq[streamID]; ok {
return q
}
if ws.sq == nil {
ws.sq = make(map[uint32]*writeQueue)
}
q := new(writeQueue)
ws.sq[streamID] = q
return q
}
// take returns the most important frame to write and removes it from the scheduler.
// It is illegal to call this if the scheduler is empty.
func (ws *writeScheduler) take() frameWriteMsg {
// If there any frames not associated with streams, prefer those first.
// These are usually SETTINGS, etc.
if !ws.zero.empty() {
return ws.zero.shift()
}
if len(ws.sq) == 0 {
panic("internal error: take should only be called if non-empty")
}
// Next, prioritize frames on streams that aren't DATA frames (no cost).
for id, q := range ws.sq {
if q.firstIsNoCost() {
return ws.takeFrom(id, q)
}
}
// Now, all that remains are DATA frames. So pick the best one.
// TODO: do that. For now, pick a random one.
for id, q := range ws.sq {
return ws.takeFrom(id, q)
}
panic("internal error: take should only be called if non-empty")
}
func (ws *writeScheduler) takeFrom(id uint32, q *writeQueue) frameWriteMsg {
wm := q.shift()
if q.empty() {
// TODO: reclaim its slice and use it for future allocations
// in the writeScheduler.streamQueue method above when making
// the writeQueue.
delete(ws.sq, id)
}
return wm
}
type writeQueue struct {
s []frameWriteMsg
}
func (q *writeQueue) empty() bool { return len(q.s) == 0 }
func (q *writeQueue) push(wm frameWriteMsg) {
q.s = append(q.s, wm)
}
func (q *writeQueue) shift() frameWriteMsg {
if len(q.s) == 0 {
panic("invalid use of queue")
}
wm := q.s[0]
// TODO: less copy-happy queue.
copy(q.s, q.s[1:])
q.s[len(q.s)-1] = frameWriteMsg{}
q.s = q.s[:len(q.s)-1]
return wm
}
func (q *writeQueue) firstIsNoCost() bool { return q.s[0].cost == 0 }