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 <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Jonathan Amsterdam <jba@google.com>
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

Lines changed: 4 additions & 1 deletion
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

Lines changed: 51 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy