stamparm/maltrail · critical
spawn capture worker
Error message
spawn capture worker
What it means
In run(), each capture worker thread is spawned with std::thread::Builder::spawn(...).expect("spawn capture worker"). spawn only returns Err when the OS refuses to create the thread (out of resources: thread count, memory for the stack, or RLIMIT_NPROC/permissions), so this panic means the sensor cannot start its capture pipeline and it aborts instead of running degraded.
Solutions
- Raise the process/thread limit (ulimit -u, container pids.max) or reduce CAPTURE_WORKERS to spawn fewer threads
- Free memory or lower per-thread stack size via thread::Builder::stack_size
- Inspect the wrapped io::Error from the panic message to identify the exact OS resource that failed
Defensive patterns
Strategy: fallback
Validate before calling
let n_workers = cfg.capture_workers; let limits = check_thread_headroom(n_workers); // compare against ulimit -u / cgroup pids.max before spawning
Try / catch
match builder.spawn(move || worker::run_all(group, ctx)) {
Ok(handle) => threads.push(handle),
Err(e) => eprintln!("cannot spawn capture worker: {e}; reducing workers or aborting"),
} Prevention
- Size CAPTURE_WORKERS within host thread/PID limits
- Check container pids.max and ulimit -u before deployment
- Handle spawn errors gracefully instead of expect in production paths
When it happens
Trigger: spawn returns Err on systems with exhausted thread/PID limits, insufficient memory for per-thread stacks, container cgroup/pids limits, or seccomp policies blocking clone().
Common situations: Running many sensors per host so the process/thread limit is hit; hardened containers with low pids.max; low-memory VMS.
Related errors
- [!] no VERSION constant found in
- [!] no maltrail-sensor package entry found in
- [!] not built - cargo build --release --manifest-path…
- invalid USERS entry ' ' [?] (hint: add whitespace at start…
- invalid configuration
AI-assisted analysis of stamparm/maltrail@77cfb06d76 (2026-09-13).
Data as JSON: /api/errors/35f45321ea3289cd.
Report an issue: GitHub.
Appendix: source
Thrown at sensor/src/main.rs:660
handles.into_iter().map(|(h, _, _)| vec![(h, String::new())]).collect()
};
let mut threads = Vec::with_capacity(worker_handles.len());
for (id, group) in worker_handles.drain(..).enumerate() {
let ctx = WorkerContext {
id,
cfg: cfg.clone(),
whitelist: whitelist.clone(),
store: store.clone(),
output: output_cfg.clone(),
slot: registry.slots[id].clone(),
shutdown: shutdown.clone(),
};
threads.push(
std::thread::Builder::new()
.name(format!("capture-{id}"))
.spawn(move || worker::run_all(group, ctx))
.expect("spawn capture worker"),
);
}
// Watch for a signal while the workers run.
let watcher_shutdown = shutdown.clone();
std::thread::Builder::new()
.name("signals".into())
.spawn(move || loop {
if SHUTDOWN.load(Ordering::Relaxed) {
watcher_shutdown.store(true, Ordering::Relaxed);
break;
}
if watcher_shutdown.load(Ordering::Relaxed) {
break;
}
std::thread::sleep(Duration::from_millis(100));
})
.ok();View on GitHub (pinned to 77cfb06d76)