2026-07-25 17:58:54 -07:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"sync/atomic"
|
|
|
|
|
|
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type txStats struct {
|
2026-08-04 14:02:53 -07:00
|
|
|
errs atomic.Uint64
|
2026-08-04 20:41:16 -07:00
|
|
|
|
|
|
|
|
// The exclusive upper bound on any sequence number this stream can have put
|
|
|
|
|
// on the wire. Read by the receiving half of the same stream, which is the
|
|
|
|
|
// only thing that can tell a gap it must write off from a sequence number
|
|
|
|
|
// that was never sent at all.
|
|
|
|
|
sent atomic.Uint64
|
2026-07-25 17:58:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type txWorker struct {
|
|
|
|
|
fd int
|
|
|
|
|
stream uint16
|
|
|
|
|
spec *frameSpec
|
|
|
|
|
batch int
|
|
|
|
|
stats *txStats
|
|
|
|
|
startTx <-chan struct{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w *txWorker) run(done *atomic.Bool) {
|
2026-07-25 23:03:31 -07:00
|
|
|
// The batch is not a multiple of the pattern count, so the pairing of pattern
|
|
|
|
|
// to size rotates every pass rather than settling into a fixed one.
|
2026-07-25 17:58:54 -07:00
|
|
|
bufs := make([][]byte, w.batch)
|
2026-07-25 23:03:31 -07:00
|
|
|
pats := make([]int, w.batch)
|
2026-07-25 17:58:54 -07:00
|
|
|
for i := range bufs {
|
|
|
|
|
bufs[i] = make([]byte, w.spec.maxSize)
|
2026-07-25 23:03:31 -07:00
|
|
|
pats[i] = i % len(patterns)
|
|
|
|
|
w.spec.prefill(bufs[i], pats[i])
|
2026-07-25 17:58:54 -07:00
|
|
|
}
|
|
|
|
|
hdrs, iovs := newMmsghdrs(bufs)
|
|
|
|
|
|
|
|
|
|
<-w.startTx
|
|
|
|
|
|
|
|
|
|
var seq uint64
|
|
|
|
|
si := 0
|
|
|
|
|
for !done.Load() {
|
|
|
|
|
for i := 0; i < w.batch; i++ {
|
|
|
|
|
size := w.spec.sizes[si]
|
|
|
|
|
si++
|
|
|
|
|
if si == len(w.spec.sizes) {
|
|
|
|
|
si = 0
|
|
|
|
|
}
|
2026-08-04 12:45:04 -07:00
|
|
|
putHeader(bufs[i], pats[i], w.stream, seq+uint64(i), size-minFrame)
|
2026-07-25 17:58:54 -07:00
|
|
|
iovs[i].Len = uint64(size)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-04 20:41:16 -07:00
|
|
|
// Published before the send rather than after it, so it is never behind a
|
|
|
|
|
// frame already in flight. Overshooting a partial send is harmless: it is
|
|
|
|
|
// a bound, and one the next pass raises again.
|
|
|
|
|
w.stats.sent.Store(seq + uint64(w.batch))
|
|
|
|
|
|
2026-07-25 17:58:54 -07:00
|
|
|
n, err := sendmmsg(w.fd, hdrs)
|
|
|
|
|
if n > 0 {
|
|
|
|
|
seq += uint64(n)
|
|
|
|
|
}
|
2026-08-04 12:45:12 -07:00
|
|
|
// Taking fewer of the vector than offered is the ring's room, not a frame
|
|
|
|
|
// lost: the rest go on the next pass.
|
|
|
|
|
if n < 0 && err != unix.EINTR && err != unix.EAGAIN && err != unix.ENOBUFS {
|
|
|
|
|
w.stats.errs.Add(1)
|
2026-07-25 17:58:54 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|