darwin: pipeline listenIn's sends so write batches can form

On darwin, listenIn encrypts into one SendBatch while a sender goroutine
writes the other (batch.SendPipeline). The sender takes whatever built up
during its last write, so writes overlap tun reads and packets queued
behind a write leave in one WriteBatch, which a connected socket sends as
one sendmsg_x. Two batches bound memory; a full producer waits for the
swap rather than dropping. One producer and one sender keep per-peer order
equal to message-counter order.

Everywhere else the branch is a false constant and listenIn is unchanged.
-tags send_pipeline builds the pipeline on any platform, so its tests and
the e2e suite can run through it on linux.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Ryan HuberandClaude Opus 5.5 committed 2026-09-30 11:11:48 +00:00
1 parent 81f991f1fd
commit 64bec56404
6 files changed
+726

No files matched your search

+10
View File
@@ -432,6 +432,11 @@ func (f *Interface) pinThisThread(i int) {
}
func (f *Interface) listenIn(queue tio.Queue, i int) {
if sendPipelinePlatform {
listenInPipelined(f, queue, i)
return
}
// Pinning this thread (and goroutine) to a single CPU keeps every sendmmsg from this goroutine going through the
// same TX ring on the nic, so the wire sees per-flow order. Skip entirely when tun.pin_threads is false.
if f.pinThreads {
@@ -478,6 +483,11 @@ func (f *Interface) listenIn(queue tio.Queue, i int) {
func (f *Interface) flushSendBatch(sb *batch.SendBatch, q int) {
queued := sb.Len()
written, err := sb.Flush()
f.accountSendBatch(queued, written, err, q)
}
// accountSendBatch is flushSendBatch's accounting, also for the writes a send pipeline makes.
func (f *Interface) accountSendBatch(queued, written int, err error, q int) {
if err != nil {
f.l.Error("Failed to write outgoing batch", "error", err, "writer", q)
}