Skip to content

Commit

Permalink
quic: add a data structure for tracking lists of sent packets
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Jonathan Amsterdam <[email protected]>
Run-TryBot: Damien Neil <[email protected]>
  • Loading branch information
neild committed Jun 1, 2023
1 parent ccc217c commit 10cf388
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 0 deletions.
95 changes: 95 additions & 0 deletions internal/quic/sent_packet_list.go
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
}
107 changes: 107 additions & 0 deletions internal/quic/sent_packet_list_test.go
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)")
}
}

0 comments on commit 10cf388

Please sign in to comment.