facebook/flow · error

failed to spawn wait_for_anything thread

Error message

failed to spawn wait_for_anything thread

What it means

wait_for_anything_async bridges a blocking wait into async by spawning a wait_for_anything OS thread and awaiting a oneshot; the spawn is expected to succeed (rust_port/crates/flow_server_env/src/server_monitor_listener_state.rs:761-769). If the OS refuses the thread, the expect panics inside async code and takes down the task and usually the server. The sibling panic 'wait_for_anything task failed' covers the channel-closed case instead.

Source

Thrown at rust_port/crates/flow_server_env/src/server_monitor_listener_state.rs:769

    _process_updates: &dyn Fn(bool, &BTreeSet<String>) -> Updates,
    _get_forced: &dyn Fn() -> CheckedSet,
) {
    wait_for_anything_blocking()
}

/// Tokio wrapper for `wait_for_anything`.
pub async fn wait_for_anything_async(
    _process_updates: &dyn Fn(bool, &BTreeSet<String>) -> Updates,
    _get_forced: &dyn Fn() -> CheckedSet,
) {
    let (sender, receiver) = tokio::sync::oneshot::channel();
    let waiter = std::thread::Builder::new()
        .name("wait_for_anything".to_string())
        .spawn(move || {
            wait_for_anything_blocking();
            let _ = sender.send(());
        })
        .expect("failed to spawn wait_for_anything thread");
    match receiver.await {
        Ok(()) => {}
        Err(err) => panic!("wait_for_anything task failed: {}", err),
    }
    if let Err(err) = waiter.join() {
        std::panic::resume_unwind(err);
    }
}

View on GitHub (pinned to f88ac94bcf)

Solutions

  1. Raise thread and memory limits (ulimit -u, cgroup pids.max, memory.max) and restart the server
  2. Reduce concurrent connections and workers to leave headroom for helper threads
  3. Change the function to return Result and have callers retry the wait later instead of crashing
  4. Track /proc/<pid>/status Threads over time to catch budget exhaustion before it hits this spawn

Example fix

// before
.spawn(move || { wait_for_anything_blocking(); let _ = sender.send(()); })
.expect("failed to spawn wait_for_anything thread");

// after
let waiter = std::thread::Builder::new()
    .name("wait_for_anything".to_string())
    .spawn(move || { wait_for_anything_blocking(); let _ = sender.send(()); });
if waiter.is_err() {
    return Err(WaitError::NoThreads); // caller retries later
}
Defensive patterns

Strategy: validation

Validate before calling

if current_thread_count() >= thread_soft_limit() {
    // defer the wait instead of panicking inside async code
    tokio::time::sleep(Duration::from_secs(1)).await;
    return;
}

Prevention

When it happens

Trigger: A monitor/file-watcher wait cycle starting when the process cannot create one more thread: pids.max or RLIMIT_NPROC exhaustion, or stack mapping failure under memory pressure.

Common situations: Long-lived language servers in constrained containers; many concurrent editor connections each adding threads; memory pressure after loading large projects.

Related errors


AI-assisted analysis of facebook/flow@f88ac94bcf (2026-08-20). Data as JSON: /api/errors/a011fa65ef779ed3. Report an issue: GitHub.