Files
cabletest/loss.go
T

123 lines
3.3 KiB
Go
Raw Normal View History

package main
import (
"math/bits"
"sync"
"sync/atomic"
)
const (
lossSlots = 1 << 16
lossWords = lossSlots / 64
)
type lossWindow struct {
mu sync.Mutex
base uint64
inited bool
quietEra bool
bits []uint64
lost atomic.Uint64
late atomic.Uint64
// The sending half of this stream. Both halves are in this process and the
// receiving socket ignores its own outgoing frames, so this window is fed by
// that sender alone and by nothing else on the wire.
sent *atomic.Uint64
}
// Takes the sending halves rather than a count, so a window cannot be built
// without the bound it judges sequence numbers against.
func newLossWindows(tx []*txStats) []lossWindow {
w := make([]lossWindow, len(tx))
for i := range w {
w[i].bits = make([]uint64, lossWords)
w[i].sent = &tx[i].sent
}
return w
}
// A sequence number is only judged once it falls out of the window, so a frame
// that has arrived but not yet been drained is never miscounted as lost.
//
// Reports whether the sequence number could have come off the wire at all. One
// above what the sender has reached is a damaged header rather than a gap, and
// is refused before it can touch the base. Believing one would drag the window
// up past every real sequence, write off the span in between as lost, and leave
// every frame after it arriving below the base and counted late for the rest of
// the run. Nothing that arrives later is evidence enough to undo that, which is
// why the sender's own frontier is the guard rather than a limit on how far a
// single step may move.
// quiet slides the window without charging: gaps written off during a cable
// measure are its own link blip, suppressed at the source while the state
// machine stays in sync with the wire.
func (w *lossWindow) observe(seq uint64, quiet bool) bool {
if seq >= w.sent.Load() {
return false
}
w.mu.Lock()
if w.quietEra && !quiet {
// Leaving a measure: the window was full when it began, so every hole
// still inside went missing while failures were suppressed. Marking
// them delivered keeps the write-off complete however the eviction
// timing falls, instead of charging the gap's in-window tail later.
for i := range w.bits {
w.bits[i] = ^uint64(0)
}
}
w.quietEra = quiet
if !w.inited {
// Start half a window below the first sequence seen, so anything the
// sender put on the wire before it lands inside the window rather than
// below the base.
if seq > lossSlots/2 {
w.base = seq - lossSlots/2
}
w.inited = true
}
if seq < w.base {
w.mu.Unlock()
if !quiet {
w.late.Add(1)
}
return true
}
if seq >= w.base+lossSlots {
w.evict(seq-lossSlots+1, quiet)
}
idx := seq & (lossSlots - 1)
w.bits[idx>>6] |= 1 << (idx & 63)
w.mu.Unlock()
return true
}
func (w *lossWindow) evict(newBase uint64, quiet bool) {
span := newBase - w.base
if span >= lossSlots {
var missing uint64
for i := range w.bits {
missing += uint64(64 - bits.OnesCount64(w.bits[i]))
w.bits[i] = 0
}
if !quiet {
w.lost.Add(missing + span - lossSlots)
}
w.base = newBase
return
}
var missing uint64
for s := w.base; s < newBase; s++ {
idx := s & (lossSlots - 1)
word, bit := idx>>6, uint64(1)<<(idx&63)
if w.bits[word]&bit == 0 {
missing++
continue
}
w.bits[word] &^= bit
}
if !quiet {
w.lost.Add(missing)
}
w.base = newBase
}