Judge loss by arrival order and a deadline, one tx ring per stream

This commit is contained in:
flamingcow
2026-08-31 14:33:09 -07:00
parent 06fa759b1a
commit 7fa48da73c
12 changed files with 220 additions and 254 deletions
+73 -138
View File
@@ -1,6 +1,9 @@
package main
import "testing"
import (
"testing"
"time"
)
// sent is the sender's frontier: sequence numbers at or above it were never put
// on the wire.
@@ -11,10 +14,10 @@ func newWindow(sent uint64) *lossWindow {
return &w[0]
}
func TestLossWindowContiguousLosesNothing(t *testing.T) {
w := newWindow(lossSlots + 1000)
for seq := uint64(0); seq < lossSlots+1000; seq++ {
w.observe(seq, false)
func TestLossContiguousLosesNothing(t *testing.T) {
w := newWindow(10000)
for seq := uint64(0); seq < 10000; seq++ {
w.observe(seq)
}
if got := w.lost.Load(); got != 0 {
t.Errorf("lost = %d, want 0", got)
@@ -24,149 +27,81 @@ func TestLossWindowContiguousLosesNothing(t *testing.T) {
}
}
func TestLossWindowCountsGapOnceEvicted(t *testing.T) {
w := newWindow(lossSlots + 1000)
for seq := uint64(0); seq < lossSlots+1000; seq++ {
if seq == 100 {
continue
}
w.observe(seq, false)
// An arrival is the judgment: the gap between it and the previous arrival is
// lost the moment it lands, not when some window slides.
func TestLossGapChargesOnArrival(t *testing.T) {
w := newWindow(10000)
for seq := uint64(0); seq < 100; seq++ {
w.observe(seq)
}
w.observe(105)
if got := w.lost.Load(); got != 5 {
t.Errorf("lost = %d, want 5 the moment the gap's far edge arrived", got)
}
}
// No tolerance of reordering: the newer frame writes the span off, and the
// older one arriving after it is late, not a repair.
func TestLossReorderChargesAndCountsLate(t *testing.T) {
w := newWindow(10000)
for seq := uint64(0); seq < 6; seq++ {
w.observe(seq)
}
w.observe(7)
if got := w.lost.Load(); got != 1 {
t.Errorf("lost = %d, want 1", got)
t.Errorf("lost = %d after the skip, want 1", got)
}
}
// Arriving out of order inside the window is not loss: a sequence is only
// judged once it falls out the far end.
func TestLossWindowOutOfOrderIsNotLoss(t *testing.T) {
w := newWindow(lossSlots + 1000)
for seq := uint64(99); ; seq-- {
w.observe(seq, false)
if seq == 0 {
break
}
}
for seq := uint64(100); seq < lossSlots+1000; seq++ {
w.observe(seq, false)
}
if got := w.lost.Load(); got != 0 {
t.Errorf("lost = %d, want 0", got)
}
}
// The other branch of evict: a jump past a whole window writes off everything
// the window held plus the sequences that never landed in it at all.
func TestLossWindowJumpBeyondWindow(t *testing.T) {
w := newWindow(200001)
w.observe(0, false)
w.observe(200000, false)
// Everything below the new base except the one sequence that was seen.
want := uint64(200000 - lossSlots + 1 - 1)
if got := w.lost.Load(); got != want {
t.Errorf("lost = %d, want %d", got, want)
}
if got := w.late.Load(); got != 0 {
t.Errorf("late = %d, want 0", got)
}
}
func TestLossWindowBelowBaseIsLate(t *testing.T) {
w := newWindow(100001)
w.observe(100000, false)
w.observe(1000, false)
w.observe(6)
if got := w.late.Load(); got != 1 {
t.Errorf("late = %d, want 1", got)
}
if got := w.lost.Load(); got != 0 {
t.Errorf("lost = %d, want 0", got)
}
}
// The first sequence seen starts the window half a span below it, so frames
// another worker is still holding land inside rather than arriving late.
func TestLossWindowStartsHalfAWindowBack(t *testing.T) {
w := newWindow(100001)
w.observe(100000, false)
if w.base != 100000-lossSlots/2 {
t.Errorf("base = %d, want %d", w.base, 100000-lossSlots/2)
}
}
// The failure this guard exists for. A sequence number the sender never reached
// used to drag the base up to it, write off the span in between as lost, and
// leave every real frame after it below the base and counted late for the rest
// of the run. It has to be refused outright, and the stream has to go on
// counting as if it had never arrived.
func TestLossWindowRefusesUnsentSeq(t *testing.T) {
const first = 100000
w := newWindow(first + 2000)
for seq := uint64(first); seq < first+1000; seq++ {
w.observe(seq, false)
}
base := w.base
if w.observe(1<<62, false) {
t.Error("a sequence number far past the sender's frontier was accepted")
}
if got := w.lost.Load(); got != 0 {
t.Errorf("lost = %d after one impossible sequence number, want 0", got)
}
if w.base != base {
t.Errorf("base moved to %d, want it left at %d where the real traffic put it",
w.base, base)
}
// Still tracking the real traffic, rather than reporting every frame late
// against a base that ran away.
for seq := uint64(first + 1000); seq < first+2000; seq++ {
w.observe(seq, false)
}
if got := w.late.Load(); got != 0 {
t.Errorf("late = %d, want 0", got)
}
if got := w.lost.Load(); got != 0 {
t.Errorf("lost = %d, want 0", got)
}
}
// A gap written off while masked — a cable measure's own link blip — charges
// nothing, however the eviction timing falls: even the gap's tail still
// inside the window when the measure ends is presumed delivered. Counting
// resumes seamlessly and later real gaps are still caught.
func TestLossWindowMaskedSlidesWithoutCharging(t *testing.T) {
w := newWindow(4 * lossSlots)
for seq := uint64(0); seq < 100; seq++ {
w.observe(seq, false)
}
// The blip: a huge jump arriving during the measure.
w.observe(2*lossSlots, true)
if got := w.lost.Load(); got != 0 {
t.Errorf("lost = %d after a masked write-off, want 0", got)
}
// The measure ends immediately — the worst case, with the gap's tail
// still in-window — and a single real gap follows.
for seq := uint64(2*lossSlots + 1); seq < 4*lossSlots-100; seq++ {
if seq == 2*lossSlots+500 {
continue
}
w.observe(seq, false)
}
if got := w.lost.Load(); got != 1 {
t.Errorf("lost = %d, want exactly the one real post-measure gap", got)
t.Errorf("lost = %d after the late arrival, want still 1", got)
}
}
// The sender's own frontier is the bound, so the sequence one past it is
// refused while the one below it is not.
func TestLossWindowBoundIsExclusive(t *testing.T) {
w := newWindow(500)
if !w.observe(499, false) {
t.Error("the last sequence the sender put on the wire was refused")
// A silent wire is charged by the judge within one deadline: everything the
// frontier covered a deadline ago and nothing has delivered since is lost.
func TestLossJudgeChargesSilentWire(t *testing.T) {
w := newWindow(1000)
t0 := time.Now()
w.judge(t0)
w.judge(t0.Add(lossDeadline / 2))
if got := w.lost.Load(); got != 0 {
t.Errorf("lost = %d before the deadline, want 0", got)
}
if w.observe(500, false) {
t.Error("a sequence the sender had not reached was accepted")
w.judge(t0.Add(lossDeadline))
if got := w.lost.Load(); got != 1000 {
t.Errorf("lost = %d after the deadline, want the whole silent frontier, 1000", got)
}
}
// Arrivals move next past the old frontier, so the judge has nothing to add.
func TestLossJudgeSkipsDelivered(t *testing.T) {
w := newWindow(1000)
t0 := time.Now()
w.judge(t0)
for seq := uint64(0); seq < 1000; seq++ {
w.observe(seq)
}
w.judge(t0.Add(lossDeadline))
if got := w.lost.Load(); got != 0 {
t.Errorf("lost = %d, want 0", got)
}
}
// A frame the judge already wrote off arriving afterwards is late, and the
// charge is not repaired.
func TestLossJudgedFrameArrivingIsLate(t *testing.T) {
w := newWindow(1000)
t0 := time.Now()
w.judge(t0)
w.judge(t0.Add(lossDeadline))
w.observe(500)
if got := w.late.Load(); got != 1 {
t.Errorf("late = %d, want 1", got)
}
if got := w.lost.Load(); got != 1000 {
t.Errorf("lost = %d, want unchanged 1000", got)
}
}