rust-lang/rust-analyzer · error

failed to spawn thread

Error message

failed to spawn thread

What it means

rust-analyzer's thread pool constructor (stdx::thread::Pool::new) spawns N worker threads and expects std::thread::Builder::spawn to succeed. If the OS refuses to create a thread (resource exhaustion), the .expect panics with 'failed to spawn thread'. The library treats an inability to create workers as an unrecoverable startup failure rather than degrading the pool silently.

Source

Thrown at crates/stdx/src/thread/pool.rs:73

            let handle = Builder::new(INITIAL_INTENT, format!("Worker{idx}",))
                .allow_leak(true)
                .spawn({
                    let extant_tasks = Arc::clone(&extant_tasks);
                    let job_receiver: Receiver<Job> = job_receiver.clone();
                    move || {
                        let mut current_intent = INITIAL_INTENT;
                        for job in job_receiver {
                            if job.requested_intent != current_intent {
                                job.requested_intent.apply_to_current_thread();
                                current_intent = job.requested_intent;
                            }
                            // discard the panic, we should've logged the backtrace already
                            drop(panic::catch_unwind(job.f));
                            extant_tasks.fetch_sub(1, Ordering::SeqCst);
                        }
                    }
                })
                .expect("failed to spawn thread");

            handles.push(handle);
        }

        Self { _handles: handles.into_boxed_slice(), extant_tasks, job_sender }
    }

    pub fn spawn<F>(&self, intent: ThreadIntent, f: F)
    where
        F: FnOnce() + Send + UnwindSafe + 'static,
    {
        let f = Box::new(move || {
            if cfg!(debug_assertions) {
                intent.assert_is_used_on_current_thread();
            }
            f();
        });

View on GitHub (pinned to e8f7e90aa3)

Solutions

  1. Raise the process thread limit (ulimit -u) or the container's pids.max and retry.
  2. Reduce the requested pool size so fewer threads need to be spawned.
  3. Free memory or threads held by other processes in the same container/pod.
  4. If this is your code wrapping the pool, pre-check capacity or construct the pool earlier at startup where failure is easier to diagnose.

Example fix

// before
let pool = Pool::new(1024);
// after
let pool = Pool::new(num_cpus::get().min(64));
Defensive patterns

Strategy: validation

Validate before calling

// Check headroom before constructing the pool
let limit = std::fs::read_to_string("/proc/self/limits")
    .ok()
    .and_then(|s| s.lines().find(|l| l.contains("Max processes")))
    .map(|l| l.split_whitespace().nth(3).and_then(|n| n.parse::<u64>().ok()))
    .flatten();
if let Some(limit) = limit {
    assert!(pool_size < limit, "pool size {} exceeds thread limit {}", pool_size, limit);
}

Try / catch

// The pool panics via expect; run construction in a subprocess or guard the limit beforehand.
let pool = std::panic::catch_unwind(|| Pool::new(n));
match pool {
    Ok(p) => p,
    Err(_) => fallback_to_single_threaded_execution(),
}

Prevention

When it happens

Trigger: Calling stdx::thread::Pool::new(n) when the process has hit its RLIMIT_NPROC/thread limit, when cgroup pids.max is exhausted (containers), or when the system is out of memory needed for thread stacks. Any spawn() returning Err triggers the expect at the point the handle is created.

Common situations: Running rust-analyzer inside Docker/Kubernetes containers with low pids limits; spawning huge pools on machines with many configured workers; environments with low `ulimit -u`; memory-constrained CI runners.

Related errors


AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03). Data as JSON: /api/errors/1bdd1293e3d724d6. Report an issue: GitHub.