slackhq/nebula · error
WriteBatch: len(bufs)=%d != len(addrs)=%d
Error message
WriteBatch: len(bufs)=%d != len(addrs)=%d
What it means
batchWriter.WriteBatch requires the bufs (payloads) and addrs (destinations) slices to have the same length, because each buffer maps to exactly one sendmmsg destination. The library rejects mismatched inputs up front rather than silently dropping packets.
Source
Thrown at udp/udp_linux_writebatch.go:196
minor, _ = strconv.Atoi(mp)
return
}
// WriteBatch sends bufs via sendmmsg(2), coalescing runs into UDP_SEGMENT
// entries, so one syscall can mix GSO superpackets and plain datagrams.
// Without GSO support every packet is its own entry.
// Callers shall deliver same-destination packets contiguously and in counter order
//
// Batches larger than the scratch take one sendmmsg per chunk.
// A partial success resumes the same prepared entries at the first unsent entry.
// A zero-sent error means the kernel rejected the first remaining entry:
// its packets are dropped and the rest of the chunk resumes in place.
//
// Returns the number of packets sent. An error means the call itself failed.
// A short count means some destinations were undeliverable.
func (w *batchWriter) WriteBatch(bufs [][]byte, addrs []netip.AddrPort) (int, error) {
if len(bufs) != len(addrs) {
return 0, fmt.Errorf("WriteBatch: len(bufs)=%d != len(addrs)=%d", len(bufs), len(addrs))
}
// A destination the kernel rejects results in us dropping that entry (one packet, or one same-destination GSO run).
// We count what actually made it out rather than returning an error.
written := 0
i := 0
for i < len(bufs) {
entry := 0
iovIdx := 0
for entry < len(w.msgs) && i < len(bufs) {
iovBudget := len(w.iovs) - iovIdx
if iovBudget < 1 {
break
}
runLen, segSize := w.planRun(bufs, addrs, i, iovBudget)
if runLen == 0 {
breakView on GitHub (pinned to dd8f660c0a)
Solutions
- Fix the caller so every buffer has exactly one corresponding destination address
- If intentionally dropping packets, slice both bufs and addrs to the same length before calling
- Add a debug assertion/log in the caller when the slices diverge to catch the divergence early
Example fix
// before w.WriteBatch(bufs[:n], addrs[:m]) // n != m // after n := min(n, m) w.WriteBatch(bufs[:n], addrs[:n])
Defensive patterns
Strategy: validation
Validate before calling
func safeWriteBatch(w *udp.StdConn, bufs [][]byte, addrs []netip.AddrPort) (int, error) {
n := min(len(bufs), len(addrs))
return w.WriteBatch(bufs[:n], addrs[:n])
} Try / catch
n, err := w.WriteBatch(bufs, addrs)
if err != nil {
if strings.Contains(err.Error(), "len(bufs)") {
log.Error("caller bug: bufs/addrs length mismatch", "bufs", len(bufs), "addrs", len(addrs))
}
return n, err
} Prevention
- Always append to bufs and addrs in the same loop iteration
- Trim both slices together when dropping failed entries
- Add a debug assert in caller code comparing len(bufs)==len(addrs)
- Write a unit test covering the batching emit path
When it happens
Trigger: Calling WriteBatch (via StdConn.WriteTo/udpsend batching path) with len(bufs) != len(addrs), typically from a custom caller or a bug in caller-side batching that appends to one slice but not the other.
Common situations: Custom integrations driving the udp package directly; off-by-one when trimming failed entries from addrs but not bufs; refactoring the emit loop in nebula's interface code.
Related errors
- unable to open socket: %w
- unable to set SO_REUSEPORT: %w
- unable to bind to socket: %w
- unsupported sock type: %T
- sendmmsg made no progress
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/66925e142d27e81b.
Report an issue: GitHub.