Collect active query jobs into struct QueryJobMap

This commit is contained in:
Zalathar 2026-02-11 15:31:55 +11:00
parent 7ad4e69ad5
commit b9b28ba1b5
5 changed files with 95 additions and 86 deletions

View file

@ -248,7 +248,7 @@ internal compiler error: query cycle handler thread panicked, aborting process";
tls::with(|tcx| {
// Accessing session globals is sound as they outlive `GlobalCtxt`.
// They are needed to hash query keys containing spans or symbols.
let query_map = rustc_span::set_session_globals_then(
let job_map = rustc_span::set_session_globals_then(
unsafe { &*(session_globals as *const SessionGlobals) },
|| {
// Ensure there were no errors collecting all active jobs.
@ -258,7 +258,7 @@ internal compiler error: query cycle handler thread panicked, aborting process";
)
},
);
break_query_cycles(query_map, &registry);
break_query_cycles(job_map, &registry);
})
})
});

View file

@ -15,7 +15,7 @@ use rustc_query_system::query::{
use rustc_span::{DUMMY_SP, Span};
use crate::dep_graph::{DepContext, DepNode, DepNodeIndex};
use crate::job::{QueryJobInfo, QueryMap, find_cycle_in_stack, report_cycle};
use crate::job::{QueryJobInfo, QueryJobMap, find_cycle_in_stack, report_cycle};
use crate::{QueryCtxt, QueryFlags, SemiDynamicQueryDispatcher};
#[inline]
@ -45,8 +45,8 @@ pub(crate) fn gather_active_jobs_inner<'tcx, K: Copy>(
state: &QueryState<'tcx, K>,
tcx: TyCtxt<'tcx>,
make_frame: fn(TyCtxt<'tcx>, K) -> QueryStackFrame<QueryStackDeferred<'tcx>>,
jobs: &mut QueryMap<'tcx>,
require_complete: bool,
job_map_out: &mut QueryJobMap<'tcx>, // Out-param; job info is gathered into this map
) -> Option<()> {
let mut active = Vec::new();
@ -77,7 +77,7 @@ pub(crate) fn gather_active_jobs_inner<'tcx, K: Copy>(
// queries leading to a deadlock.
for (key, job) in active {
let frame = make_frame(tcx, key);
jobs.insert(job.id, QueryJobInfo { frame, job });
job_map_out.insert(job.id, QueryJobInfo { frame, job });
}
Some(())
@ -213,12 +213,12 @@ fn cycle_error<'tcx, C: QueryCache, const FLAGS: QueryFlags>(
) -> (C::Value, Option<DepNodeIndex>) {
// Ensure there was no errors collecting all active jobs.
// We need the complete map to ensure we find a cycle to break.
let query_map = qcx
let job_map = qcx
.collect_active_jobs_from_all_queries(false)
.ok()
.expect("failed to collect active queries");
let error = find_cycle_in_stack(try_execute, query_map, &qcx.current_query_job(), span);
let error = find_cycle_in_stack(try_execute, job_map, &qcx.current_query_job(), span);
(mk_cycle(query, qcx, error.lift()), None)
}

View file

@ -17,39 +17,45 @@ use crate::dep_graph::DepContext;
/// Map from query job IDs to job information collected by
/// `collect_active_jobs_from_all_queries`.
pub type QueryMap<'tcx> = FxHashMap<QueryJobId, QueryJobInfo<'tcx>>;
fn query_job_id_frame<'a, 'tcx>(
id: QueryJobId,
map: &'a QueryMap<'tcx>,
) -> QueryStackFrame<QueryStackDeferred<'tcx>> {
map.get(&id).unwrap().frame.clone()
#[derive(Debug, Default)]
pub struct QueryJobMap<'tcx> {
map: FxHashMap<QueryJobId, QueryJobInfo<'tcx>>,
}
fn query_job_id_span<'a, 'tcx>(id: QueryJobId, map: &'a QueryMap<'tcx>) -> Span {
map.get(&id).unwrap().job.span
}
impl<'tcx> QueryJobMap<'tcx> {
/// Adds information about a job ID to the job map.
///
/// Should only be called by `gather_active_jobs_inner`.
pub(crate) fn insert(&mut self, id: QueryJobId, info: QueryJobInfo<'tcx>) {
self.map.insert(id, info);
}
fn query_job_id_parent<'a, 'tcx>(id: QueryJobId, map: &'a QueryMap<'tcx>) -> Option<QueryJobId> {
map.get(&id).unwrap().job.parent
}
fn frame_of(&self, id: QueryJobId) -> &QueryStackFrame<QueryStackDeferred<'tcx>> {
&self.map[&id].frame
}
fn query_job_id_latch<'a, 'tcx>(
id: QueryJobId,
map: &'a QueryMap<'tcx>,
) -> Option<&'a QueryLatch<'tcx>> {
map.get(&id).unwrap().job.latch.as_ref()
fn span_of(&self, id: QueryJobId) -> Span {
self.map[&id].job.span
}
fn parent_of(&self, id: QueryJobId) -> Option<QueryJobId> {
self.map[&id].job.parent
}
fn latch_of(&self, id: QueryJobId) -> Option<&QueryLatch<'tcx>> {
self.map[&id].job.latch.as_ref()
}
}
#[derive(Clone, Debug)]
pub struct QueryJobInfo<'tcx> {
pub frame: QueryStackFrame<QueryStackDeferred<'tcx>>,
pub job: QueryJob<'tcx>,
pub(crate) struct QueryJobInfo<'tcx> {
pub(crate) frame: QueryStackFrame<QueryStackDeferred<'tcx>>,
pub(crate) job: QueryJob<'tcx>,
}
pub(crate) fn find_cycle_in_stack<'tcx>(
id: QueryJobId,
query_map: QueryMap<'tcx>,
job_map: QueryJobMap<'tcx>,
current_job: &Option<QueryJobId>,
span: Span,
) -> CycleError<QueryStackDeferred<'tcx>> {
@ -58,7 +64,7 @@ pub(crate) fn find_cycle_in_stack<'tcx>(
let mut current_job = Option::clone(current_job);
while let Some(job) = current_job {
let info = query_map.get(&job).unwrap();
let info = &job_map.map[&job];
cycle.push(QueryInfo { span: info.job.span, frame: info.frame.clone() });
if job == id {
@ -70,11 +76,10 @@ pub(crate) fn find_cycle_in_stack<'tcx>(
// Replace it with the span which caused the cycle to form
cycle[0].span = span;
// Find out why the cycle itself was used
let usage = info
.job
.parent
.as_ref()
.map(|parent| (info.job.span, query_job_id_frame(*parent, &query_map)));
let usage = try {
let parent = info.job.parent?;
(info.job.span, job_map.frame_of(parent).clone())
};
return CycleError { usage, cycle };
}
@ -88,16 +93,16 @@ pub(crate) fn find_cycle_in_stack<'tcx>(
#[inline(never)]
pub(crate) fn find_dep_kind_root<'tcx>(
id: QueryJobId,
query_map: QueryMap<'tcx>,
job_map: QueryJobMap<'tcx>,
) -> (QueryJobInfo<'tcx>, usize) {
let mut depth = 1;
let info = query_map.get(&id).unwrap();
let info = &job_map.map[&id];
let dep_kind = info.frame.dep_kind;
let mut current_id = info.job.parent;
let mut last_layout = (info.clone(), depth);
while let Some(id) = current_id {
let info = query_map.get(&id).unwrap();
let info = &job_map.map[&id];
if info.frame.dep_kind == dep_kind {
depth += 1;
last_layout = (info.clone(), depth);
@ -120,7 +125,7 @@ type Waiter = (QueryJobId, usize);
/// required information to resume the waiter.
/// If all `visit` calls returns None, this function also returns None.
fn visit_waiters<'tcx, F>(
query_map: &QueryMap<'tcx>,
job_map: &QueryJobMap<'tcx>,
query: QueryJobId,
mut visit: F,
) -> Option<Option<Waiter>>
@ -128,14 +133,14 @@ where
F: FnMut(Span, QueryJobId) -> Option<Option<Waiter>>,
{
// Visit the parent query which is a non-resumable waiter since it's on the same stack
if let Some(parent) = query_job_id_parent(query, query_map)
&& let Some(cycle) = visit(query_job_id_span(query, query_map), parent)
if let Some(parent) = job_map.parent_of(query)
&& let Some(cycle) = visit(job_map.span_of(query), parent)
{
return Some(cycle);
}
// Visit the explicit waiters which use condvars and are resumable
if let Some(latch) = query_job_id_latch(query, query_map) {
if let Some(latch) = job_map.latch_of(query) {
for (i, waiter) in latch.info.lock().waiters.iter().enumerate() {
if let Some(waiter_query) = waiter.query {
if visit(waiter.span, waiter_query).is_some() {
@ -154,7 +159,7 @@ where
/// If a cycle is detected, this initial value is replaced with the span causing
/// the cycle.
fn cycle_check<'tcx>(
query_map: &QueryMap<'tcx>,
job_map: &QueryJobMap<'tcx>,
query: QueryJobId,
span: Span,
stack: &mut Vec<(Span, QueryJobId)>,
@ -178,8 +183,8 @@ fn cycle_check<'tcx>(
stack.push((span, query));
// Visit all the waiters
let r = visit_waiters(query_map, query, |span, successor| {
cycle_check(query_map, successor, span, stack, visited)
let r = visit_waiters(job_map, query, |span, successor| {
cycle_check(job_map, successor, span, stack, visited)
});
// Remove the entry in our stack if we didn't find a cycle
@ -194,7 +199,7 @@ fn cycle_check<'tcx>(
/// from `query` without going through any of the queries in `visited`.
/// This is achieved with a depth first search.
fn connected_to_root<'tcx>(
query_map: &QueryMap<'tcx>,
job_map: &QueryJobMap<'tcx>,
query: QueryJobId,
visited: &mut FxHashSet<QueryJobId>,
) -> bool {
@ -204,18 +209,18 @@ fn connected_to_root<'tcx>(
}
// This query is connected to the root (it has no query parent), return true
if query_job_id_parent(query, query_map).is_none() {
if job_map.parent_of(query).is_none() {
return true;
}
visit_waiters(query_map, query, |_, successor| {
connected_to_root(query_map, successor, visited).then_some(None)
visit_waiters(job_map, query, |_, successor| {
connected_to_root(job_map, successor, visited).then_some(None)
})
.is_some()
}
// Deterministically pick an query from a list
fn pick_query<'a, 'tcx, T, F>(query_map: &QueryMap<'tcx>, queries: &'a [T], f: F) -> &'a T
fn pick_query<'a, 'tcx, T, F>(job_map: &QueryJobMap<'tcx>, queries: &'a [T], f: F) -> &'a T
where
F: Fn(&T) -> (Span, QueryJobId),
{
@ -225,7 +230,7 @@ where
.iter()
.min_by_key(|v| {
let (span, query) = f(v);
let hash = query_job_id_frame(query, query_map).hash;
let hash = job_map.frame_of(query).hash;
// Prefer entry points which have valid spans for nicer error messages
// We add an integer to the tuple ensuring that entry points
// with valid spans are picked first
@ -241,7 +246,7 @@ where
/// If a cycle was not found, the starting query is removed from `jobs` and
/// the function returns false.
fn remove_cycle<'tcx>(
query_map: &QueryMap<'tcx>,
job_map: &QueryJobMap<'tcx>,
jobs: &mut Vec<QueryJobId>,
wakelist: &mut Vec<Arc<QueryWaiter<'tcx>>>,
) -> bool {
@ -249,7 +254,7 @@ fn remove_cycle<'tcx>(
let mut stack = Vec::new();
// Look for a cycle starting with the last query in `jobs`
if let Some(waiter) =
cycle_check(query_map, jobs.pop().unwrap(), DUMMY_SP, &mut stack, &mut visited)
cycle_check(job_map, jobs.pop().unwrap(), DUMMY_SP, &mut stack, &mut visited)
{
// The stack is a vector of pairs of spans and queries; reverse it so that
// the earlier entries require later entries
@ -273,17 +278,17 @@ fn remove_cycle<'tcx>(
let entry_points = stack
.iter()
.filter_map(|&(span, query)| {
if query_job_id_parent(query, query_map).is_none() {
if job_map.parent_of(query).is_none() {
// This query is connected to the root (it has no query parent)
Some((span, query, None))
} else {
let mut waiters = Vec::new();
// Find all the direct waiters who lead to the root
visit_waiters(query_map, query, |span, waiter| {
visit_waiters(job_map, query, |span, waiter| {
// Mark all the other queries in the cycle as already visited
let mut visited = FxHashSet::from_iter(stack.iter().map(|q| q.1));
if connected_to_root(query_map, waiter, &mut visited) {
if connected_to_root(job_map, waiter, &mut visited) {
waiters.push((span, waiter));
}
@ -293,7 +298,7 @@ fn remove_cycle<'tcx>(
None
} else {
// Deterministically pick one of the waiters to show to the user
let waiter = *pick_query(query_map, &waiters, |s| *s);
let waiter = *pick_query(job_map, &waiters, |s| *s);
Some((span, query, Some(waiter)))
}
}
@ -301,7 +306,7 @@ fn remove_cycle<'tcx>(
.collect::<Vec<(Span, QueryJobId, Option<(Span, QueryJobId)>)>>();
// Deterministically pick an entry point
let (_, entry_point, usage) = pick_query(query_map, &entry_points, |e| (e.0, e.1));
let (_, entry_point, usage) = pick_query(job_map, &entry_points, |e| (e.0, e.1));
// Shift the stack so that our entry point is first
let entry_point_pos = stack.iter().position(|(_, query)| query == entry_point);
@ -309,15 +314,14 @@ fn remove_cycle<'tcx>(
stack.rotate_left(pos);
}
let usage =
usage.as_ref().map(|(span, query)| (*span, query_job_id_frame(*query, query_map)));
let usage = usage.map(|(span, job)| (span, job_map.frame_of(job).clone()));
// Create the cycle error
let error = CycleError {
usage,
cycle: stack
.iter()
.map(|&(s, ref q)| QueryInfo { span: s, frame: query_job_id_frame(*q, query_map) })
.map(|&(span, job)| QueryInfo { span, frame: job_map.frame_of(job).clone() })
.collect(),
};
@ -326,8 +330,7 @@ fn remove_cycle<'tcx>(
let (waitee_query, waiter_idx) = waiter.unwrap();
// Extract the waiter we want to resume
let waiter =
query_job_id_latch(waitee_query, query_map).unwrap().extract_waiter(waiter_idx);
let waiter = job_map.latch_of(waitee_query).unwrap().extract_waiter(waiter_idx);
// Set the cycle error so it will be picked up when resumed
*waiter.cycle.lock() = Some(error);
@ -346,18 +349,21 @@ fn remove_cycle<'tcx>(
/// uses a query latch and then resuming that waiter.
/// There may be multiple cycles involved in a deadlock, so this searches
/// all active queries for cycles before finally resuming all the waiters at once.
pub fn break_query_cycles<'tcx>(query_map: QueryMap<'tcx>, registry: &rustc_thread_pool::Registry) {
pub fn break_query_cycles<'tcx>(
job_map: QueryJobMap<'tcx>,
registry: &rustc_thread_pool::Registry,
) {
let mut wakelist = Vec::new();
// It is OK per the comments:
// - https://github.com/rust-lang/rust/pull/131200#issuecomment-2798854932
// - https://github.com/rust-lang/rust/pull/131200#issuecomment-2798866392
#[allow(rustc::potential_query_instability)]
let mut jobs: Vec<QueryJobId> = query_map.keys().cloned().collect();
let mut jobs: Vec<QueryJobId> = job_map.map.keys().copied().collect();
let mut found_cycle = false;
while jobs.len() > 0 {
if remove_cycle(&query_map, &mut jobs, &mut wakelist) {
if remove_cycle(&job_map, &mut jobs, &mut wakelist) {
found_cycle = true;
}
}
@ -372,8 +378,7 @@ pub fn break_query_cycles<'tcx>(query_map: QueryMap<'tcx>, registry: &rustc_thre
if !found_cycle {
panic!(
"deadlock detected as we're unable to find a query cycle to break\n\
current query map:\n{:#?}",
query_map
current query map:\n{job_map:#?}",
);
}
@ -402,17 +407,16 @@ pub fn print_query_stack<'tcx>(
let mut count_printed = 0;
let mut count_total = 0;
// Make use of a partial query map if we fail to take locks collecting active queries.
let query_map = match qcx.collect_active_jobs_from_all_queries(false) {
Ok(query_map) => query_map,
Err(query_map) => query_map,
};
// Make use of a partial query job map if we fail to take locks collecting active queries.
let job_map: QueryJobMap<'_> = qcx
.collect_active_jobs_from_all_queries(false)
.unwrap_or_else(|partial_job_map| partial_job_map);
if let Some(ref mut file) = file {
let _ = writeln!(file, "\n\nquery stack during panic:");
}
while let Some(query) = current_query {
let Some(query_info) = query_map.get(&query) else {
let Some(query_info) = job_map.map.get(&query) else {
break;
};
let query_extra = query_info.frame.info.extract();

View file

@ -6,6 +6,7 @@
#![feature(core_intrinsics)]
#![feature(min_specialization)]
#![feature(rustc_attrs)]
#![feature(try_blocks)]
// tidy-alphabetical-end
use std::marker::ConstParamTy;
@ -26,7 +27,7 @@ use rustc_query_system::query::{
};
use rustc_span::{ErrorGuaranteed, Span};
pub use crate::job::{QueryMap, break_query_cycles, print_query_stack};
pub use crate::job::{QueryJobMap, break_query_cycles, print_query_stack};
pub use crate::plumbing::{QueryCtxt, query_key_hash_verify_all};
use crate::plumbing::{encode_all_query_results, try_mark_green};
use crate::profiling_support::QueryKeyStringCache;

View file

@ -36,7 +36,7 @@ use rustc_span::def_id::LOCAL_CRATE;
use crate::error::{QueryOverflow, QueryOverflowNote};
use crate::execution::{all_inactive, force_query};
use crate::job::{QueryMap, find_dep_kind_root};
use crate::job::{QueryJobMap, find_dep_kind_root};
use crate::{QueryDispatcherUnerased, QueryFlags, SemiDynamicQueryDispatcher};
/// Implements [`QueryContext`] for use by [`rustc_query_system`], since that
@ -53,10 +53,10 @@ impl<'tcx> QueryCtxt<'tcx> {
}
fn depth_limit_error(self, job: QueryJobId) {
let query_map = self
let job_map = self
.collect_active_jobs_from_all_queries(true)
.expect("failed to collect active queries");
let (info, depth) = find_dep_kind_root(job, query_map);
let (info, depth) = find_dep_kind_root(job, job_map);
let suggested_limit = match self.tcx.recursion_limit() {
Limit(0) => Limit(2),
@ -131,17 +131,17 @@ impl<'tcx> QueryCtxt<'tcx> {
pub fn collect_active_jobs_from_all_queries(
self,
require_complete: bool,
) -> Result<QueryMap<'tcx>, QueryMap<'tcx>> {
let mut jobs = QueryMap::default();
) -> Result<QueryJobMap<'tcx>, QueryJobMap<'tcx>> {
let mut job_map_out = QueryJobMap::default();
let mut complete = true;
for gather_fn in crate::PER_QUERY_GATHER_ACTIVE_JOBS_FNS.iter() {
if gather_fn(self.tcx, &mut jobs, require_complete).is_none() {
if gather_fn(self.tcx, require_complete, &mut job_map_out).is_none() {
complete = false;
}
}
if complete { Ok(jobs) } else { Err(jobs) }
if complete { Ok(job_map_out) } else { Err(job_map_out) }
}
}
@ -753,8 +753,8 @@ macro_rules! define_queries {
/// Should only be called through `PER_QUERY_GATHER_ACTIVE_JOBS_FNS`.
pub(crate) fn gather_active_jobs<'tcx>(
tcx: TyCtxt<'tcx>,
qmap: &mut QueryMap<'tcx>,
require_complete: bool,
job_map_out: &mut QueryJobMap<'tcx>,
) -> Option<()> {
let make_frame = |tcx: TyCtxt<'tcx>, key| {
let vtable = &tcx.query_system.query_vtables.$name;
@ -765,8 +765,8 @@ macro_rules! define_queries {
let res = crate::execution::gather_active_jobs_inner(&tcx.query_system.states.$name,
tcx,
make_frame,
qmap,
require_complete,
job_map_out,
);
// this can be called during unwinding, and the function has a `try_`-prefix, so
@ -849,9 +849,13 @@ macro_rules! define_queries {
/// each individual query, so that we have distinct function names to
/// grep for.)
const PER_QUERY_GATHER_ACTIVE_JOBS_FNS: &[
for<'tcx> fn(TyCtxt<'tcx>, &mut QueryMap<'tcx>, require_complete: bool) -> Option<()>
for<'tcx> fn(
tcx: TyCtxt<'tcx>,
require_complete: bool,
job_map_out: &mut QueryJobMap<'tcx>,
) -> Option<()>
] = &[
$(query_impl::$name::gather_active_jobs),*
$( $crate::query_impl::$name::gather_active_jobs ),*
];
const ALLOC_SELF_PROFILE_QUERY_STRINGS: &[