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

  1. Raise the process/thread limit (ulimit -u, container pids.max) or reduce CAPTURE_WORKERS to spawn fewer threads
  2. Free memory or lower per-thread stack size via thread::Builder::stack_size
  3. 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

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


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)