Skip to content

Commit 399218d

Browse files
neildgopherbot
authored andcommitted
quic: implement stream flush
Do not commit data written to a stream to the network until the user explicitly flushes the stream, the stream output buffer fills, or the output buffer contains enough data to fill a packet. We could write data immediately (as net.TCPConn does), but this can require the user to put their own buffer in front of the stream. Since we necessarily need to maintain a retransmit buffer in the stream, this is redundant. We could do something like Nagle's algorithm, but nobody wants that. So make flushes explicit. For golang/go#58547 Change-Id: I29dc9d79556c7a358a360ef79beb38b45040b6bc Reviewed-on: https://go-review.googlesource.com/c/net/+/543083 Auto-Submit: Damien Neil <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]>
1 parent d87f99b commit 399218d

File tree

7 files changed

+171
-30
lines changed

7 files changed

+171
-30
lines changed

Diff for: internal/quic/conn.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,10 @@ func newConn(now time.Time, side connSide, cids newServerConnIDs, peerAddr netip
136136
}
137137
}
138138

139-
// The smallest allowed maximum QUIC datagram size is 1200 bytes.
140139
// TODO: PMTU discovery.
141-
const maxDatagramSize = 1200
142140
c.logConnectionStarted(cids.originalDstConnID, peerAddr)
143141
c.keysAppData.init()
144-
c.loss.init(c.side, maxDatagramSize, now)
142+
c.loss.init(c.side, smallestMaxDatagramSize, now)
145143
c.streamsInit()
146144
c.lifetimeInit()
147145
c.restartIdleTimer(now)

Diff for: internal/quic/conn_flow_test.go

+7
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ func TestConnOutflowBlocked(t *testing.T) {
262262
if n != len(data) || err != nil {
263263
t.Fatalf("s.Write() = %v, %v; want %v, nil", n, err, len(data))
264264
}
265+
s.Flush()
265266

266267
tc.wantFrame("stream writes data up to MAX_DATA limit",
267268
packetType1RTT, debugFrameStream{
@@ -310,6 +311,7 @@ func TestConnOutflowMaxDataDecreases(t *testing.T) {
310311
if n != len(data) || err != nil {
311312
t.Fatalf("s.Write() = %v, %v; want %v, nil", n, err, len(data))
312313
}
314+
s.Flush()
313315

314316
tc.wantFrame("stream writes data up to MAX_DATA limit",
315317
packetType1RTT, debugFrameStream{
@@ -337,7 +339,9 @@ func TestConnOutflowMaxDataRoundRobin(t *testing.T) {
337339
}
338340

339341
s1.Write(make([]byte, 10))
342+
s1.Flush()
340343
s2.Write(make([]byte, 10))
344+
s2.Flush()
341345

342346
tc.writeFrames(packetType1RTT, debugFrameMaxData{
343347
max: 1,
@@ -378,6 +382,7 @@ func TestConnOutflowMetaAndData(t *testing.T) {
378382

379383
data := makeTestData(32)
380384
s.Write(data)
385+
s.Flush()
381386

382387
s.CloseRead()
383388
tc.wantFrame("CloseRead sends a STOP_SENDING, not flow controlled",
@@ -405,6 +410,7 @@ func TestConnOutflowResentData(t *testing.T) {
405410

406411
data := makeTestData(15)
407412
s.Write(data[:8])
413+
s.Flush()
408414
tc.wantFrame("data is under MAX_DATA limit, all sent",
409415
packetType1RTT, debugFrameStream{
410416
id: s.id,
@@ -421,6 +427,7 @@ func TestConnOutflowResentData(t *testing.T) {
421427
})
422428

423429
s.Write(data[8:])
430+
s.Flush()
424431
tc.wantFrame("new data is sent up to the MAX_DATA limit",
425432
packetType1RTT, debugFrameStream{
426433
id: s.id,

Diff for: internal/quic/conn_loss_test.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ func TestLostStreamFrameEmpty(t *testing.T) {
183183
if err != nil {
184184
t.Fatalf("NewStream: %v", err)
185185
}
186-
c.Write(nil) // open the stream
186+
c.Flush() // open the stream
187187
tc.wantFrame("created bidirectional stream 0",
188188
packetType1RTT, debugFrameStream{
189189
id: newStreamID(clientSide, bidiStream, 0),
@@ -213,13 +213,15 @@ func TestLostStreamWithData(t *testing.T) {
213213
p.initialMaxStreamDataUni = 1 << 20
214214
})
215215
s.Write(data[:4])
216+
s.Flush()
216217
tc.wantFrame("send [0,4)",
217218
packetType1RTT, debugFrameStream{
218219
id: s.id,
219220
off: 0,
220221
data: data[:4],
221222
})
222223
s.Write(data[4:8])
224+
s.Flush()
223225
tc.wantFrame("send [4,8)",
224226
packetType1RTT, debugFrameStream{
225227
id: s.id,
@@ -263,6 +265,7 @@ func TestLostStreamPartialLoss(t *testing.T) {
263265
})
264266
for i := range data {
265267
s.Write(data[i : i+1])
268+
s.Flush()
266269
tc.wantFrame(fmt.Sprintf("send STREAM frame with byte %v", i),
267270
packetType1RTT, debugFrameStream{
268271
id: s.id,

Diff for: internal/quic/conn_streams_test.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,33 @@ func TestStreamsCreate(t *testing.T) {
1919
tc := newTestConn(t, clientSide, permissiveTransportParameters)
2020
tc.handshake()
2121

22-
c, err := tc.conn.NewStream(ctx)
22+
s, err := tc.conn.NewStream(ctx)
2323
if err != nil {
2424
t.Fatalf("NewStream: %v", err)
2525
}
26-
c.Write(nil) // open the stream
26+
s.Flush() // open the stream
2727
tc.wantFrame("created bidirectional stream 0",
2828
packetType1RTT, debugFrameStream{
2929
id: 0, // client-initiated, bidi, number 0
3030
data: []byte{},
3131
})
3232

33-
c, err = tc.conn.NewSendOnlyStream(ctx)
33+
s, err = tc.conn.NewSendOnlyStream(ctx)
3434
if err != nil {
3535
t.Fatalf("NewStream: %v", err)
3636
}
37-
c.Write(nil) // open the stream
37+
s.Flush() // open the stream
3838
tc.wantFrame("created unidirectional stream 0",
3939
packetType1RTT, debugFrameStream{
4040
id: 2, // client-initiated, uni, number 0
4141
data: []byte{},
4242
})
4343

44-
c, err = tc.conn.NewStream(ctx)
44+
s, err = tc.conn.NewStream(ctx)
4545
if err != nil {
4646
t.Fatalf("NewStream: %v", err)
4747
}
48-
c.Write(nil) // open the stream
48+
s.Flush() // open the stream
4949
tc.wantFrame("created bidirectional stream 1",
5050
packetType1RTT, debugFrameStream{
5151
id: 4, // client-initiated, uni, number 4
@@ -177,11 +177,11 @@ func TestStreamsStreamSendOnly(t *testing.T) {
177177
tc := newTestConn(t, serverSide, permissiveTransportParameters)
178178
tc.handshake()
179179

180-
c, err := tc.conn.NewSendOnlyStream(ctx)
180+
s, err := tc.conn.NewSendOnlyStream(ctx)
181181
if err != nil {
182182
t.Fatalf("NewStream: %v", err)
183183
}
184-
c.Write(nil) // open the stream
184+
s.Flush() // open the stream
185185
tc.wantFrame("created unidirectional stream 0",
186186
packetType1RTT, debugFrameStream{
187187
id: 3, // server-initiated, uni, number 0

Diff for: internal/quic/quic.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,14 @@ const defaultKeepAlivePeriod = 0
6464
// https://www.rfc-editor.org/rfc/rfc9002.html#section-6.1.2-6
6565
const timerGranularity = 1 * time.Millisecond
6666

67+
// The smallest allowed maximum datagram size.
68+
// https://www.rfc-editor.org/rfc/rfc9000#section-14
69+
const smallestMaxDatagramSize = 1200
70+
6771
// Minimum size of a UDP datagram sent by a client carrying an Initial packet,
6872
// or a server containing an ack-eliciting Initial packet.
6973
// https://www.rfc-editor.org/rfc/rfc9000#section-14.1
70-
const paddedInitialDatagramSize = 1200
74+
const paddedInitialDatagramSize = smallestMaxDatagramSize
7175

7276
// Maximum number of streams of a given type which may be created.
7377
// https://www.rfc-editor.org/rfc/rfc9000.html#section-4.6-2

Diff for: internal/quic/stream.go

+41-14
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ type Stream struct {
3838
// the write will fail.
3939
outgate gate
4040
out pipe // buffered data to send
41+
outflushed int64 // offset of last flush call
4142
outwin int64 // maximum MAX_STREAM_DATA received from the peer
4243
outmaxsent int64 // maximum data offset we've sent to the peer
4344
outmaxbuf int64 // maximum amount of data we will buffer
44-
outunsent rangeset[int64] // ranges buffered but not yet sent
45+
outunsent rangeset[int64] // ranges buffered but not yet sent (only flushed data)
4546
outacked rangeset[int64] // ranges sent and acknowledged
4647
outopened sentVal // set if we should open the stream
4748
outclosed sentVal // set by CloseWrite
@@ -240,8 +241,6 @@ func (s *Stream) Write(b []byte) (n int, err error) {
240241
// WriteContext writes data to the stream write buffer.
241242
// Buffered data is only sent when the buffer is sufficiently full.
242243
// Call the Flush method to ensure buffered data is sent.
243-
//
244-
// TODO: Implement Flush.
245244
func (s *Stream) WriteContext(ctx context.Context, b []byte) (n int, err error) {
246245
if s.IsReadOnly() {
247246
return 0, errors.New("write to read-only stream")
@@ -269,10 +268,6 @@ func (s *Stream) WriteContext(ctx context.Context, b []byte) (n int, err error)
269268
s.outUnlock()
270269
return n, errors.New("write to closed stream")
271270
}
272-
// We set outopened here rather than below,
273-
// so if this is a zero-length write we still
274-
// open the stream despite not writing any data to it.
275-
s.outopened.set()
276271
if len(b) == 0 {
277272
break
278273
}
@@ -282,13 +277,26 @@ func (s *Stream) WriteContext(ctx context.Context, b []byte) (n int, err error)
282277
// Amount to write is min(the full buffer, data up to the write limit).
283278
// This is a number of bytes.
284279
nn := min(int64(len(b)), lim-s.out.end)
285-
// Copy the data into the output buffer and mark it as unsent.
286-
if s.out.end <= s.outwin {
287-
s.outunsent.add(s.out.end, min(s.out.end+nn, s.outwin))
288-
}
280+
// Copy the data into the output buffer.
289281
s.out.writeAt(b[:nn], s.out.end)
290282
b = b[nn:]
291283
n += int(nn)
284+
// Possibly flush the output buffer.
285+
// We automatically flush if:
286+
// - We have enough data to consume the send window.
287+
// Sending this data may cause the peer to extend the window.
288+
// - We have buffered as much data as we're willing do.
289+
// We need to send data to clear out buffer space.
290+
// - We have enough data to fill a 1-RTT packet using the smallest
291+
// possible maximum datagram size (1200 bytes, less header byte,
292+
// connection ID, packet number, and AEAD overhead).
293+
const autoFlushSize = smallestMaxDatagramSize - 1 - connIDLen - 1 - aeadOverhead
294+
shouldFlush := s.out.end >= s.outwin || // peer send window is full
295+
s.out.end >= lim || // local send buffer is full
296+
(s.out.end-s.outflushed) >= autoFlushSize // enough data buffered
297+
if shouldFlush {
298+
s.flushLocked()
299+
}
292300
if s.out.end > s.outwin {
293301
// We're blocked by flow control.
294302
// Send a STREAM_DATA_BLOCKED frame to let the peer know.
@@ -301,6 +309,23 @@ func (s *Stream) WriteContext(ctx context.Context, b []byte) (n int, err error)
301309
return n, nil
302310
}
303311

312+
// Flush flushes data written to the stream.
313+
// It does not wait for the peer to acknowledge receipt of the data.
314+
// Use CloseContext to wait for the peer's acknowledgement.
315+
func (s *Stream) Flush() {
316+
s.outgate.lock()
317+
defer s.outUnlock()
318+
s.flushLocked()
319+
}
320+
321+
func (s *Stream) flushLocked() {
322+
s.outopened.set()
323+
if s.outflushed < s.outwin {
324+
s.outunsent.add(s.outflushed, min(s.outwin, s.out.end))
325+
}
326+
s.outflushed = s.out.end
327+
}
328+
304329
// Close closes the stream.
305330
// See CloseContext for more details.
306331
func (s *Stream) Close() error {
@@ -363,6 +388,7 @@ func (s *Stream) CloseWrite() {
363388
s.outgate.lock()
364389
defer s.outUnlock()
365390
s.outclosed.set()
391+
s.flushLocked()
366392
}
367393

368394
// Reset aborts writes on the stream and notifies the peer
@@ -612,8 +638,8 @@ func (s *Stream) handleMaxStreamData(maxStreamData int64) error {
612638
if maxStreamData <= s.outwin {
613639
return nil
614640
}
615-
if s.out.end > s.outwin {
616-
s.outunsent.add(s.outwin, min(maxStreamData, s.out.end))
641+
if s.outflushed > s.outwin {
642+
s.outunsent.add(s.outwin, min(maxStreamData, s.outflushed))
617643
}
618644
s.outwin = maxStreamData
619645
if s.out.end > s.outwin {
@@ -741,10 +767,11 @@ func (s *Stream) appendOutFramesLocked(w *packetWriter, pnum packetNumber, pto b
741767
}
742768
for {
743769
// STREAM
744-
off, size := dataToSend(min(s.out.start, s.outwin), min(s.out.end, s.outwin), s.outunsent, s.outacked, pto)
770+
off, size := dataToSend(min(s.out.start, s.outwin), min(s.outflushed, s.outwin), s.outunsent, s.outacked, pto)
745771
if end := off + size; end > s.outmaxsent {
746772
// This will require connection-level flow control to send.
747773
end = min(end, s.outmaxsent+s.conn.streams.outflow.avail())
774+
end = max(end, off)
748775
size = end - off
749776
}
750777
fin := s.outclosed.isSet() && off+size == s.out.end

0 commit comments

Comments
 (0)