From 56d2a3841dd993c343355ebb26cfec739a82bfd8 Mon Sep 17 00:00:00 2001 From: JackDoan Date: Mon, 27 Jul 2026 15:39:14 -0500 Subject: [PATCH] tun: default pin_threads off Thread pinning trades scheduler freedom for TX-ring ordering; that's the right trade on dedicated forwarders but not as a surprise default on hosts sharing cores with other workloads. Make it opt-in and document the default in the example config. Co-Authored-By: Claude Fable 5 --- examples/config.yml | 5 +++-- interface.go | 4 ++-- main.go | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/config.yml b/examples/config.yml index 606e373b..b9e96235 100644 --- a/examples/config.yml +++ b/examples/config.yml @@ -264,8 +264,9 @@ tun: # Linux only. pin_threads pins each tun reader/encrypt OS thread to a single CPU. This keeps every goroutine's # batched sends flowing through one XPS-selected NIC TX ring, so packets within a flow stay ordered on the wire - # instead of being sprayed across multiple TX rings and reordered. Not reloadable. - #pin_threads: true + # instead of being sprayed across multiple TX rings and reordered. Off by default; pinning takes CPUs away from + # the scheduler, which can hurt hosts that share cores with other workloads. Not reloadable. + #pin_threads: false # Linux only. cpu_affinity overrides which CPUs the tun reader threads pin to: a list of CPU IDs, one per routine # (see the top-level `routines` setting). Lists shorter than `routines` are modulo-cycled across the queues; extra diff --git a/interface.go b/interface.go index 4508e6ad..a7bda180 100644 --- a/interface.go +++ b/interface.go @@ -59,7 +59,7 @@ type InterfaceConfig struct { // pin-to-(i % NumCPU) behavior. Only consulted when PinThreads is true. CpuAffinity []int // PinThreads controls whether each TUN reader OS thread is pinned to a - // single CPU (via tun.pin_threads, default true). Pinning keeps each + // single CPU (via tun.pin_threads, default false). Pinning keeps each // goroutine's sendmmsg on one XPS-selected NIC TX ring so per-flow // packets stay ordered on the wire. PinThreads bool @@ -94,7 +94,7 @@ type Interface struct { // Only consulted when pinThreads is true. cpuAffinity []int // pinThreads controls whether listenIn pins each TUN reader OS thread to - // a CPU at all (tun.pin_threads, default true). When false, threads are + // a CPU at all (tun.pin_threads, default false). When false, threads are // left free to migrate as on stock nebula. pinThreads bool // ecnEnabled gates RFC 6040 underlay ECN propagation. When true, diff --git a/main.go b/main.go index f03028c5..93745f45 100644 --- a/main.go +++ b/main.go @@ -236,7 +236,7 @@ func Main(c *config.C, configTest bool, buildVersion string, l *slog.Logger, dev punchy: punchy, ConntrackCacheTimeout: conntrackCacheTimeout, CpuAffinity: parseCpuAffinity(c, l, routines), - PinThreads: c.GetBool("tun.pin_threads", true), + PinThreads: c.GetBool("tun.pin_threads", false), l: l, }