Skip to content

Commit 04296fa

Browse files
aojeaneild
authored andcommitted
http2: prioritize RST_STREAM frames in random write scheduler
The http2 random write scheduler should not queue RST_STREAM frames with the DATA frames, and instead treat them as control frames. There can be deadlock situations if data frames block the queue, because if the sender wants to close the stream it sends an RST frame, but if the client is not draining the queue, the RST frame is stuck and the sender is not able to finish. Fixes golang/go#49741 Change-Id: I0940a76d1aad95f1c4d3856e4d79cf5ce2a78ff2 Reviewed-on: https://go-review.googlesource.com/c/net/+/367154 Trust: Dave Cheney <[email protected]> Reviewed-by: Damien Neil <[email protected]> Trust: Damien Neil <[email protected]> Run-TryBot: Damien Neil <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 8537929 commit 04296fa

File tree

4 files changed

+66
-6
lines changed

4 files changed

+66
-6
lines changed

http2/writesched.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ type WriteScheduler interface {
3232

3333
// Pop dequeues the next frame to write. Returns false if no frames can
3434
// be written. Frames with a given wr.StreamID() are Pop'd in the same
35-
// order they are Push'd. No frames should be discarded except by CloseStream.
35+
// order they are Push'd, except RST_STREAM frames. No frames should be
36+
// discarded except by CloseStream.
3637
Pop() (wr FrameWriteRequest, ok bool)
3738
}
3839

@@ -52,6 +53,7 @@ type FrameWriteRequest struct {
5253

5354
// stream is the stream on which this frame will be written.
5455
// nil for non-stream frames like PING and SETTINGS.
56+
// nil for RST_STREAM streams, which use the StreamError.StreamID field instead.
5557
stream *stream
5658

5759
// done, if non-nil, must be a buffered channel with space for

http2/writesched_random.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ func (ws *randomWriteScheduler) AdjustStream(streamID uint32, priority PriorityP
4545
}
4646

4747
func (ws *randomWriteScheduler) Push(wr FrameWriteRequest) {
48-
id := wr.StreamID()
49-
if id == 0 {
48+
if wr.isControl() {
5049
ws.zero.push(wr)
5150
return
5251
}
52+
id := wr.StreamID()
5353
q, ok := ws.sq[id]
5454
if !ok {
5555
q = ws.queuePool.get()
@@ -59,7 +59,7 @@ func (ws *randomWriteScheduler) Push(wr FrameWriteRequest) {
5959
}
6060

6161
func (ws *randomWriteScheduler) Pop() (FrameWriteRequest, bool) {
62-
// Control frames first.
62+
// Control and RST_STREAM frames first.
6363
if !ws.zero.empty() {
6464
return ws.zero.shift(), true
6565
}

http2/writesched_random_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ func TestRandomScheduler(t *testing.T) {
1414
ws.Push(makeWriteHeadersRequest(2))
1515
ws.Push(makeWriteNonStreamRequest())
1616
ws.Push(makeWriteNonStreamRequest())
17+
ws.Push(makeWriteRSTStream(1))
1718

18-
// Pop all frames. Should get the non-stream requests first,
19+
// Pop all frames. Should get the non-stream and RST stream requests first,
1920
// followed by the stream requests in any order.
2021
var order []FrameWriteRequest
2122
for {
@@ -26,12 +27,15 @@ func TestRandomScheduler(t *testing.T) {
2627
order = append(order, wr)
2728
}
2829
t.Logf("got frames: %v", order)
29-
if len(order) != 6 {
30+
if len(order) != 7 {
3031
t.Fatalf("got %d frames, expected 6", len(order))
3132
}
3233
if order[0].StreamID() != 0 || order[1].StreamID() != 0 {
3334
t.Fatal("expected non-stream frames first", order[0], order[1])
3435
}
36+
if _, ok := order[2].write.(StreamError); !ok {
37+
t.Fatal("expected RST stream frames first", order[2])
38+
}
3539
got := make(map[uint32]bool)
3640
for _, wr := range order[2:] {
3741
got[wr.StreamID()] = true

http2/writesched_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ func makeHandlerPanicRST(streamID uint32) FrameWriteRequest {
2525
return FrameWriteRequest{&handlerPanicRST{StreamID: streamID}, st, nil}
2626
}
2727

28+
func makeWriteRSTStream(streamID uint32) FrameWriteRequest {
29+
return FrameWriteRequest{write: streamError(streamID, ErrCodeInternal)}
30+
}
31+
2832
func checkConsume(wr FrameWriteRequest, nbytes int32, want []FrameWriteRequest) error {
2933
consumed, rest, n := wr.Consume(nbytes)
3034
var wantConsumed, wantRest FrameWriteRequest
@@ -52,6 +56,56 @@ func TestFrameWriteRequestNonData(t *testing.T) {
5256
if err := checkConsume(wr, 0, []FrameWriteRequest{wr}); err != nil {
5357
t.Errorf("Consume:\n%v", err)
5458
}
59+
60+
wr = makeWriteRSTStream(123)
61+
if got, want := wr.DataSize(), 0; got != want {
62+
t.Errorf("DataSize: got %v, want %v", got, want)
63+
}
64+
65+
// RST_STREAM frames are always consumed whole.
66+
if err := checkConsume(wr, 0, []FrameWriteRequest{wr}); err != nil {
67+
t.Errorf("Consume:\n%v", err)
68+
}
69+
}
70+
71+
// #49741 RST_STREAM and Control frames should have more priority than data
72+
// frames to avoid blocking streams caused by clients not able to drain the
73+
// queue.
74+
func TestFrameWriteRequestWithData(t *testing.T) {
75+
st := &stream{
76+
id: 1,
77+
sc: &serverConn{maxFrameSize: 16},
78+
}
79+
const size = 32
80+
wr := FrameWriteRequest{&writeData{st.id, make([]byte, size), true}, st, make(chan error)}
81+
if got, want := wr.DataSize(), size; got != want {
82+
t.Errorf("DataSize: got %v, want %v", got, want)
83+
}
84+
85+
// No flow-control bytes available: cannot consume anything.
86+
if err := checkConsume(wr, math.MaxInt32, []FrameWriteRequest{}); err != nil {
87+
t.Errorf("Consume(limited by flow control):\n%v", err)
88+
}
89+
90+
wr = makeWriteNonStreamRequest()
91+
if got, want := wr.DataSize(), 0; got != want {
92+
t.Errorf("DataSize: got %v, want %v", got, want)
93+
}
94+
95+
// Non-DATA frames are always consumed whole.
96+
if err := checkConsume(wr, 0, []FrameWriteRequest{wr}); err != nil {
97+
t.Errorf("Consume:\n%v", err)
98+
}
99+
100+
wr = makeWriteRSTStream(1)
101+
if got, want := wr.DataSize(), 0; got != want {
102+
t.Errorf("DataSize: got %v, want %v", got, want)
103+
}
104+
105+
// RST_STREAM frames are always consumed whole.
106+
if err := checkConsume(wr, 0, []FrameWriteRequest{wr}); err != nil {
107+
t.Errorf("Consume:\n%v", err)
108+
}
55109
}
56110

57111
func TestFrameWriteRequestData(t *testing.T) {

0 commit comments

Comments
 (0)