Skip to content

Commit a600b35

Browse files
committed
quic: avoid redundant MAX_DATA updates
When Stream.Read determines that we should send a MAX_DATA update, it sends a message to the Conn to mark us as needing one. If a second Read happens before the message from the first read is processed, we may send a redundant MAX_DATA update. This is harmless, but inefficient. Double check that we still need to send an update before marking one as necessary. Change-Id: I0eb5a591eae6929b91da68b1ab6834a7795323ee Reviewed-on: https://go-review.googlesource.com/c/net/+/530035 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]>
1 parent ea63359 commit a600b35

File tree

2 files changed

+55
-1
lines changed

2 files changed

+55
-1
lines changed

internal/quic/conn_flow.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ func (c *Conn) handleStreamBytesReadOffLoop(n int64) {
5454
// We should send a MAX_DATA update to the peer.
5555
// Record this on the Conn's main loop.
5656
c.sendMsg(func(now time.Time, c *Conn) {
57-
c.sendMaxDataUpdate()
57+
// A MAX_DATA update may have already happened, so check again.
58+
if c.shouldUpdateFlowControl(c.streams.inflow.credit.Load()) {
59+
c.sendMaxDataUpdate()
60+
}
5861
})
5962
}
6063
}

internal/quic/conn_flow_test.go

+51
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package quic
88

99
import (
10+
"context"
1011
"testing"
1112
)
1213

@@ -36,6 +37,56 @@ func TestConnInflowReturnOnRead(t *testing.T) {
3637
})
3738
}
3839

40+
func TestConnInflowReturnOnRacingReads(t *testing.T) {
41+
// Perform two reads at the same time,
42+
// one for half of MaxConnReadBufferSize
43+
// and one for one byte.
44+
//
45+
// We should observe a single MAX_DATA update.
46+
// Depending on the ordering of events,
47+
// this may include the credit from just the larger read
48+
// or the credit from both.
49+
ctx := canceledContext()
50+
tc := newTestConn(t, serverSide, func(c *Config) {
51+
c.MaxConnReadBufferSize = 64
52+
})
53+
tc.handshake()
54+
tc.ignoreFrame(frameTypeAck)
55+
tc.writeFrames(packetType1RTT, debugFrameStream{
56+
id: newStreamID(clientSide, uniStream, 0),
57+
data: make([]byte, 32),
58+
})
59+
tc.writeFrames(packetType1RTT, debugFrameStream{
60+
id: newStreamID(clientSide, uniStream, 1),
61+
data: make([]byte, 32),
62+
})
63+
s1, err := tc.conn.AcceptStream(ctx)
64+
if err != nil {
65+
t.Fatalf("conn.AcceptStream() = %v", err)
66+
}
67+
s2, err := tc.conn.AcceptStream(ctx)
68+
if err != nil {
69+
t.Fatalf("conn.AcceptStream() = %v", err)
70+
}
71+
read1 := runAsync(tc, func(ctx context.Context) (int, error) {
72+
return s1.ReadContext(ctx, make([]byte, 16))
73+
})
74+
read2 := runAsync(tc, func(ctx context.Context) (int, error) {
75+
return s2.ReadContext(ctx, make([]byte, 1))
76+
})
77+
// This MAX_DATA might extend the window by 16 or 17, depending on
78+
// whether the second write occurs before the update happens.
79+
tc.wantFrameType("MAX_DATA update is sent",
80+
packetType1RTT, debugFrameMaxData{})
81+
tc.wantIdle("redundant MAX_DATA is not sent")
82+
if _, err := read1.result(); err != nil {
83+
t.Errorf("ReadContext #1 = %v", err)
84+
}
85+
if _, err := read2.result(); err != nil {
86+
t.Errorf("ReadContext #2 = %v", err)
87+
}
88+
}
89+
3990
func TestConnInflowReturnOnClose(t *testing.T) {
4091
tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) {
4192
c.MaxConnReadBufferSize = 64

0 commit comments

Comments
 (0)