rust-lang/rust · critical
non-empty waiters vec
Error message
non-empty waiters vec
What it means
`QueryLatch::extract_waiter` (job.rs:127) calls `.expect("non-empty waiters vec")` on `self.waiters` while breaking a query cycle. The waiter vector is only `take`n (emptied) when the latch is fully set; calling `extract_waiter` on an already-resolved latch violates the cycle-breaker's precondition. It is an invariant of the query deadlock detector, not a user-facing check.
Source
Thrown at compiler/rustc_middle/src/query/job.rs:127
}
}
/// Sets the latch and resumes all waiters on it
fn set(&self) {
let mut waiters_guard = self.waiters.lock();
let waiters = waiters_guard.take().unwrap(); // mark the latch as complete
let registry = rustc_thread_pool::Registry::current();
for waiter in waiters {
rustc_thread_pool::mark_unblocked(®istry);
waiter.condvar.notify_one();
}
}
/// Removes a single waiter from the list of waiters.
/// This is used to break query cycles.
pub fn extract_waiter(&self, waiter: usize) -> Arc<QueryWaiter<'tcx>> {
let mut waiters_guard = self.waiters.lock();
let waiters = waiters_guard.as_mut().expect("non-empty waiters vec");
// Remove the waiter from the list of waiters
waiters.remove(waiter)
}
}
View on GitHub (pinned to 22057b88b0)
Solutions
- Report to rust-lang/rust with `rustc -vV`, thread count, and a minimal reproducer (this is a race in the query system).
- Retry with a single thread: `RUST_MIN_STACK=... rustc -Z threads=1` or set `RUSTC_THREADS=1` via cargo to sidestep the race.
- Retry on the latest nightly; query-system races get fixed quickly.
- If building rustc itself, re-run `x.py build` after `cargo clean` to rule out a stale incremental interaction with the query scheduler.
Defensive patterns
Strategy: retry
Try / catch
// 'non-empty waiters vec' is an internal query-system invariant violation (ICE).
// Nothing in user code triggers it deterministically; treat as transient.
use std::process::Command;
fn compile_with_jitter(dir: &str) -> bool {
for attempt in 0..3u8 {
// Single-threaded rebuild avoids the concurrency edge case in the query scheduler.
let ok = Command::new("cargo")
.args(["build", "-j", "1"])
.current_dir(dir)
.status()
.map(|s| s.success())
.unwrap_or(false);
if ok { return true; }
let _ = Command::new("cargo").args(["clean"]).current_dir(dir).status();
}
false
} Prevention
- Avoid running multiple `rustc`/`cargo` invocations against the same target dir simultaneously.
- Use `-j 1` to reproduce/avoid scheduler races in the query system.
- Bump the toolchain: query-system bugs are fixed frequently in nightly.
- Report with `RUST_BACKTRACE=full`; this is a compiler invariant, not application logic.
When it happens
Trigger: Reached only inside the rustc query system's cycle handler: when the deadlock detector decides to remove one specific waiter from a latch to break a cycle, but that latch's `waiters` field has already been `take`n—meaning another thread concurrently completed the query and drained the list. Indicates a race between latch completion and cycle extraction.
Common situations: Observed by rustc developers stress-testing parallel query evaluation (`-Z threads=N` with pathological dependency graphs), running the compiler under heavy load with very deep generic recursion, or testing nightly query-system changes. End-user Rust code cannot deliberately construct it; ordinary crashes here are rustc bugs.
Related errors
- Error: trying to record dependency on DepNode {dep_node} in
- dep node {prev_index:?} is unexpectedly red
- CGU symbol name accessed before setting
- size of T must match erased type <T as Erasable>::Storage
- Incremental cache file size overflowed u64.
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/06d2b41489337d32.json.
Report an issue: GitHub.