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
- Raise the process thread limit (ulimit -u) or the container's pids.max and retry.
- Reduce the requested pool size so fewer threads need to be spawned.
- Free memory or threads held by other processes in the same container/pod.
- 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
- Size pools from available CPUs, not arbitrary constants.
- Set ulimit -u / container pids.max generously for rust-analyzer processes.
- Monitor process thread counts in long-lived IDE sessions.
- Construct the pool early at startup so failures surface before work begins.
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
- We explicitly do not provide canonicalization API, as that i
- bad kind {other}
- bad spacing {other}
- bad tag: {other}
- profiler already started
AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03).
Data as JSON: /api/errors/1bdd1293e3d724d6.
Report an issue: GitHub.