-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
quic: add a data structure for tracking lists of sent packets
Store in-flight packets in a ring buffer. For golang/go#58547 Change-Id: I825c4e600bb496c2f8f6c195085aaae3e847445e Reviewed-on: https://go-review.googlesource.com/c/net/+/499285 TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Jonathan Amsterdam <jba@google.com> Run-TryBot: Damien Neil <dneil@google.com>
- Loading branch information
Showing
2 changed files
with
202 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// Copyright 2023 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. | ||
|
||
//go:build go1.21 | ||
|
||
package quic | ||
|
||
// A sentPacketList is a ring buffer of sentPackets. | ||
// | ||
// Processing an ack for a packet causes all older packets past a small threshold | ||
// to be discarded (RFC 9002, Section 6.1.1), so the list of in-flight packets is | ||
// not sparse and will contain at most a few acked/lost packets we no longer | ||
// care about. | ||
type sentPacketList struct { | ||
nextNum packetNumber // next packet number to add to the buffer | ||
off int // offset of first packet in the buffer | ||
size int // number of packets | ||
p []*sentPacket | ||
} | ||
|
||
// start is the first packet in the list. | ||
func (s *sentPacketList) start() packetNumber { | ||
return s.nextNum - packetNumber(s.size) | ||
} | ||
|
||
// end is one after the last packet in the list. | ||
// If the list is empty, start == end. | ||
func (s *sentPacketList) end() packetNumber { | ||
return s.nextNum | ||
} | ||
|
||
// discard clears the list. | ||
func (s *sentPacketList) discard() { | ||
*s = sentPacketList{} | ||
} | ||
|
||
// add appends a packet to the list. | ||
func (s *sentPacketList) add(sent *sentPacket) { | ||
if s.nextNum != sent.num { | ||
panic("inserting out-of-order packet") | ||
} | ||
s.nextNum++ | ||
if s.size >= len(s.p) { | ||
s.grow() | ||
} | ||
i := (s.off + s.size) % len(s.p) | ||
s.size++ | ||
s.p[i] = sent | ||
} | ||
|
||
// nth returns a packet by index. | ||
func (s *sentPacketList) nth(n int) *sentPacket { | ||
index := (s.off + n) % len(s.p) | ||
return s.p[index] | ||
} | ||
|
||
// num returns a packet by number. | ||
// It returns nil if the packet is not in the list. | ||
func (s *sentPacketList) num(num packetNumber) *sentPacket { | ||
i := int(num - s.start()) | ||
if i < 0 || i >= s.size { | ||
return nil | ||
} | ||
return s.nth(i) | ||
} | ||
|
||
// clean removes all acked or lost packets from the head of the list. | ||
func (s *sentPacketList) clean() { | ||
for s.size > 0 { | ||
sent := s.p[s.off] | ||
if !sent.acked && !sent.lost { | ||
return | ||
} | ||
sent.recycle() | ||
s.p[s.off] = nil | ||
s.off = (s.off + 1) % len(s.p) | ||
s.size-- | ||
} | ||
s.off = 0 | ||
} | ||
|
||
// grow increases the buffer to hold more packaets. | ||
func (s *sentPacketList) grow() { | ||
newSize := len(s.p) * 2 | ||
if newSize == 0 { | ||
newSize = 64 | ||
} | ||
p := make([]*sentPacket, newSize) | ||
for i := 0; i < s.size; i++ { | ||
p[i] = s.nth(i) | ||
} | ||
s.p = p | ||
s.off = 0 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// Copyright 2023 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. | ||
|
||
//go:build go1.21 | ||
|
||
package quic | ||
|
||
import "testing" | ||
|
||
func TestSentPacketListSlidingWindow(t *testing.T) { | ||
// Record 1000 sent packets, acking everything outside the most recent 10. | ||
list := &sentPacketList{} | ||
const window = 10 | ||
for i := packetNumber(0); i < 1000; i++ { | ||
list.add(&sentPacket{num: i}) | ||
if i < window { | ||
continue | ||
} | ||
prev := i - window | ||
sent := list.num(prev) | ||
if sent == nil { | ||
t.Fatalf("packet %v not in list", prev) | ||
} | ||
if sent.num != prev { | ||
t.Fatalf("list.num(%v) = packet %v", prev, sent.num) | ||
} | ||
if got := list.nth(0); got != sent { | ||
t.Fatalf("list.nth(0) != list.num(%v)", prev) | ||
} | ||
sent.acked = true | ||
list.clean() | ||
if got := list.num(prev); got != nil { | ||
t.Fatalf("list.num(%v) = packet %v, expected it to be discarded", prev, got.num) | ||
} | ||
if got, want := list.start(), prev+1; got != want { | ||
t.Fatalf("list.start() = %v, want %v", got, want) | ||
} | ||
if got, want := list.end(), i+1; got != want { | ||
t.Fatalf("list.end() = %v, want %v", got, want) | ||
} | ||
if got, want := list.size, window; got != want { | ||
t.Fatalf("list.size = %v, want %v", got, want) | ||
} | ||
} | ||
} | ||
|
||
func TestSentPacketListGrows(t *testing.T) { | ||
// Record 1000 sent packets. | ||
list := &sentPacketList{} | ||
const count = 1000 | ||
for i := packetNumber(0); i < count; i++ { | ||
list.add(&sentPacket{num: i}) | ||
} | ||
if got, want := list.start(), packetNumber(0); got != want { | ||
t.Fatalf("list.start() = %v, want %v", got, want) | ||
} | ||
if got, want := list.end(), packetNumber(count); got != want { | ||
t.Fatalf("list.end() = %v, want %v", got, want) | ||
} | ||
if got, want := list.size, count; got != want { | ||
t.Fatalf("list.size = %v, want %v", got, want) | ||
} | ||
for i := packetNumber(0); i < count; i++ { | ||
sent := list.num(i) | ||
if sent == nil { | ||
t.Fatalf("packet %v not in list", i) | ||
} | ||
if sent.num != i { | ||
t.Fatalf("list.num(%v) = packet %v", i, sent.num) | ||
} | ||
if got := list.nth(int(i)); got != sent { | ||
t.Fatalf("list.nth(%v) != list.num(%v)", int(i), i) | ||
} | ||
} | ||
} | ||
|
||
func TestSentPacketListCleanAll(t *testing.T) { | ||
list := &sentPacketList{} | ||
// Record 10 sent packets. | ||
const count = 10 | ||
for i := packetNumber(0); i < count; i++ { | ||
list.add(&sentPacket{num: i}) | ||
} | ||
// Mark all the packets as acked. | ||
for i := packetNumber(0); i < count; i++ { | ||
list.num(i).acked = true | ||
} | ||
list.clean() | ||
if got, want := list.size, 0; got != want { | ||
t.Fatalf("list.size = %v, want %v", got, want) | ||
} | ||
list.add(&sentPacket{num: 10}) | ||
if got, want := list.size, 1; got != want { | ||
t.Fatalf("list.size = %v, want %v", got, want) | ||
} | ||
sent := list.num(10) | ||
if sent == nil { | ||
t.Fatalf("packet %v not in list", 10) | ||
} | ||
if sent.num != 10 { | ||
t.Fatalf("list.num(10) = %v", sent.num) | ||
} | ||
if got := list.nth(0); got != sent { | ||
t.Fatalf("list.nth(0) != list.num(10)") | ||
} | ||
} |