Skip to content

Add coherency check for query cycle breaking#153953

Open
zetanumbers wants to merge 2 commits intorust-lang:mainfrom
zetanumbers:coherent_cycles
Open

Add coherency check for query cycle breaking#153953
zetanumbers wants to merge 2 commits intorust-lang:mainfrom
zetanumbers:coherent_cycles

Conversation

@zetanumbers
Copy link
Contributor

@zetanumbers zetanumbers commented Mar 16, 2026

With debug_assertions compare if generated CycleErrors differ between single-threaded and multi-threaded versions of query cycle breaking code and panic if so.

I reasonably assume multi-threaded cycle breaking prioritizes breaking single-threaded cycles. I've made find_cycle_in_stack to be a fallible function, always finding single-threaded cycle if there is any relative to the query argument.

There are also some changes to Symbol's Debug and Display impls to make those panic less.

Split from #153185.

r? @nnethercote

cc @Zoxc

@rustbot rustbot added A-query-system Area: The rustc query system (https://rustc-dev-guide.rust-lang.org/query.html) S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Mar 16, 2026
@zetanumbers
Copy link
Contributor Author

I got 3 errors while running parallel-rustc tests:

@zetanumbers
Copy link
Contributor Author

zetanumbers commented Mar 16, 2026

tests/ui/parallel-rustc/cycle_crash-issue-135870.rs

Cycle diff
5,24d4
<                 tagged_key: check_type_wf(
<                     (),
<                 ),
<                 dep_kind: check_type_wf,
<                 def_id: None,
<             },
<             span: Span {
<                 lo: BytePos(
<                     0,
<                 ),
<                 hi: BytePos(
<                     0,
<                 ),
<                 ctxt: #0,
<             },
<         },
<     ),
<     cycle: [
<         Spanned {
<             node: QueryStackFrame {
42a23,24
>     ),
>     cycle: [
Panic message
CycleError coherency check failed:
expected: CycleError {
    usage: Some(
        Spanned {
            node: QueryStackFrame {
                tagged_key: analysis(
                    (),
                ),
                dep_kind: analysis,
                def_id: None,
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
    ),
    cycle: [
        Spanned {
            node: QueryStackFrame {
                tagged_key: eval_to_const_value_raw(
                    PseudoCanonicalInput {
                        typing_env: TypingEnv {
                            typing_mode: PostAnalysis,
                            param_env: ParamEnv {
                                caller_bounds: [],
                            },
                        },
                        value: GlobalId {
                            instance: Instance {
                                def: Item(
                                    DefId(0:3 ~ 2802[4f9a]::2801),
                                ),
                                args: [],
                            },
                            promoted: None,
                        },
                    },
                ),
                dep_kind: eval_to_const_value_raw,
                def_id: None,
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: trivial_const(
                    DefId(0:3 ~ 2802[4f9a]::2801),
                ),
                dep_kind: trivial_const,
                def_id: Some(
                    DefId(0:3 ~ 2802[4f9a]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: mir_built(
                    DefId(0:3 ~ 2802[4f9a]::2801),
                ),
                dep_kind: mir_built,
                def_id: Some(
                    DefId(0:3 ~ 2802[4f9a]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
    ],
}
got: CycleError {
    usage: Some(
        Spanned {
            node: QueryStackFrame {
                tagged_key: eval_to_const_value_raw(
                    PseudoCanonicalInput {
                        typing_env: TypingEnv {
                            typing_mode: PostAnalysis,
                            param_env: ParamEnv {
                                caller_bounds: [],
                            },
                        },
                        value: GlobalId {
                            instance: Instance {
                                def: Item(
                                    DefId(0:3 ~ 2802[4f9a]::2801),
                                ),
                                args: [],
                            },
                            promoted: None,
                        },
                    },
                ),
                dep_kind: eval_to_const_value_raw,
                def_id: None,
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
    ),
    cycle: [
        Spanned {
            node: QueryStackFrame {
                tagged_key: trivial_const(
                    DefId(0:3 ~ 2802[4f9a]::2801),
                ),
                dep_kind: trivial_const,
                def_id: Some(
                    DefId(0:3 ~ 2802[4f9a]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: mir_built(
                    DefId(0:3 ~ 2802[4f9a]::2801),
                ),
                dep_kind: mir_built,
                def_id: Some(
                    DefId(0:3 ~ 2802[4f9a]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
    ],
}

@zetanumbers
Copy link
Contributor Author

zetanumbers commented Mar 16, 2026

tests/ui/parallel-rustc/ty-variance-issue-124423.rs

Cycle diff
5,24d4
<                 tagged_key: analysis(
<                     (),
<                 ),
<                 dep_kind: analysis,
<                 def_id: None,
<             },
<             span: Span {
<                 lo: BytePos(
<                     0,
<                 ),
<                 hi: BytePos(
<                     0,
<                 ),
<                 ctxt: #0,
<             },
<         },
<     ),
<     cycle: [
<         Spanned {
<             node: QueryStackFrame {
56a37,38
>     ),
>     cycle: [
Panic message
CycleError coherency check failed:
expected: CycleError {
    usage: Some(
        Spanned {
            node: QueryStackFrame {
                tagged_key: check_type_wf(
                    (),
                ),
                dep_kind: check_type_wf,
                def_id: None,
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
    ),
    cycle: [
        Spanned {
            node: QueryStackFrame {
                tagged_key: check_well_formed(
                    DefId(0:4 ~ 2812[5a0e]::2801),
                ),
                dep_kind: check_well_formed,
                def_id: Some(
                    DefId(0:4 ~ 2812[5a0e]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: fn_sig(
                    DefId(0:4 ~ 2812[5a0e]::2801),
                ),
                dep_kind: fn_sig,
                def_id: Some(
                    DefId(0:4 ~ 2812[5a0e]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: typeck(
                    DefId(0:4 ~ 2812[5a0e]::2801),
                ),
                dep_kind: typeck,
                def_id: Some(
                    DefId(0:4 ~ 2812[5a0e]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: variances_of(
                    DefId(0:22 ~ 2812[5a0e]::2223),
                ),
                dep_kind: variances_of,
                def_id: Some(
                    DefId(0:22 ~ 2812[5a0e]::2223),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: crate_variances(
                    (),
                ),
                dep_kind: crate_variances,
                def_id: None,
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
    ],
}
got: CycleError {
    usage: Some(
        Spanned {
            node: QueryStackFrame {
                tagged_key: check_well_formed(
                    DefId(0:4 ~ 2812[5a0e]::2801),
                ),
                dep_kind: check_well_formed,
                def_id: Some(
                    DefId(0:4 ~ 2812[5a0e]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
    ),
    cycle: [
        Spanned {
            node: QueryStackFrame {
                tagged_key: fn_sig(
                    DefId(0:4 ~ 2812[5a0e]::2801),
                ),
                dep_kind: fn_sig,
                def_id: Some(
                    DefId(0:4 ~ 2812[5a0e]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: typeck(
                    DefId(0:4 ~ 2812[5a0e]::2801),
                ),
                dep_kind: typeck,
                def_id: Some(
                    DefId(0:4 ~ 2812[5a0e]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: variances_of(
                    DefId(0:22 ~ 2812[5a0e]::2223),
                ),
                dep_kind: variances_of,
                def_id: Some(
                    DefId(0:22 ~ 2812[5a0e]::2223),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: crate_variances(
                    (),
                ),
                dep_kind: crate_variances,
                def_id: None,
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
    ],
}

@zetanumbers
Copy link
Contributor Author

tests/ui/parallel-rustc/ty-variance-issue-124423.rs

Cycle diff
5,24d4
<                 tagged_key: check_type_wf(
<                     (),
<                 ),
<                 dep_kind: check_type_wf,
<                 def_id: None,
<             },
<             span: Span {
<                 lo: BytePos(
<                     0,
<                 ),
<                 hi: BytePos(
<                     0,
<                 ),
<                 ctxt: #0,
<             },
<         },
<     ),
<     cycle: [
<         Spanned {
<             node: QueryStackFrame {
42a23,24
>     ),
>     cycle: [
Panic message
CycleError coherency check failed:
expected: CycleError {
    usage: Some(
        Spanned {
            node: QueryStackFrame {
                tagged_key: check_type_wf(
                    (),
                ),
                dep_kind: check_type_wf,
                def_id: None,
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
    ),
    cycle: [
        Spanned {
            node: QueryStackFrame {
                tagged_key: check_well_formed(
                    DefId(0:4 ~ 2806[4b15]::2801),
                ),
                dep_kind: check_well_formed,
                def_id: Some(
                    DefId(0:4 ~ 2806[4b15]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: fn_sig(
                    DefId(0:4 ~ 2806[4b15]::2801),
                ),
                dep_kind: fn_sig,
                def_id: Some(
                    DefId(0:4 ~ 2806[4b15]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: typeck(
                    DefId(0:4 ~ 2806[4b15]::2801),
                ),
                dep_kind: typeck,
                def_id: Some(
                    DefId(0:4 ~ 2806[4b15]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: variances_of(
                    DefId(0:10 ~ 2806[4b15]::2223),
                ),
                dep_kind: variances_of,
                def_id: Some(
                    DefId(0:10 ~ 2806[4b15]::2223),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: crate_variances(
                    (),
                ),
                dep_kind: crate_variances,
                def_id: None,
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
    ],
}
got: CycleError {
    usage: Some(
        Spanned {
            node: QueryStackFrame {
                tagged_key: check_well_formed(
                    DefId(0:4 ~ 2806[4b15]::2801),
                ),
                dep_kind: check_well_formed,
                def_id: Some(
                    DefId(0:4 ~ 2806[4b15]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
    ),
    cycle: [
        Spanned {
            node: QueryStackFrame {
                tagged_key: fn_sig(
                    DefId(0:4 ~ 2806[4b15]::2801),
                ),
                dep_kind: fn_sig,
                def_id: Some(
                    DefId(0:4 ~ 2806[4b15]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: typeck(
                    DefId(0:4 ~ 2806[4b15]::2801),
                ),
                dep_kind: typeck,
                def_id: Some(
                    DefId(0:4 ~ 2806[4b15]::2801),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: variances_of(
                    DefId(0:10 ~ 2806[4b15]::2223),
                ),
                dep_kind: variances_of,
                def_id: Some(
                    DefId(0:10 ~ 2806[4b15]::2223),
                ),
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
        Spanned {
            node: QueryStackFrame {
                tagged_key: crate_variances(
                    (),
                ),
                dep_kind: crate_variances,
                def_id: None,
            },
            span: Span {
                lo: BytePos(
                    0,
                ),
                hi: BytePos(
                    0,
                ),
                ctxt: #0,
            },
        },
    ],
}

@zetanumbers
Copy link
Contributor Author

zetanumbers commented Mar 16, 2026

These 3 similar mismatches leads me to believe current query cycle breaking code is inconsistent with the single-threaded implementation. In particular, multi-threaded cycle usage query is actually the first query in the cycle for single-threaded version.

I propose we fix the multi-threaded version. I have explicitly addressed this issue in #153185. However I think @Zoxc might have a strong opinion on how to better fix this.

UPDATE: This was issue with coherency check implementation

@rust-log-analyzer

This comment has been minimized.

Copy link
Contributor

@nnethercote nnethercote left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for splitting this off from #153185, that makes reviewing much easier. A few comments below.

View changes since this review

if let Some(s) = self.opt_as_str() {
fmt::Debug::fmt(s, f)
} else {
fmt::Debug::fmt(&self.0, f)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will just print a number, e.g. 123. Printing Symbol(123) would be much better.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Symbols should never be used in contexts in which this path can be triggered, it's better to continue panicking here.
@zetanumbers In which specific context was this encountered?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consult #t-compiler/parallel-rustc > Getting rid of the cycle detection thread

That thread doesn't have anything answering my question.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This context is the cycle detection thread

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment explaining this use?
Using Symbols when the interner is dead is like using dangling pointers, suspicious at least.
If the cycle detection thread is the only such context and it's removed, then it would be better to remove this logic as well.

if let Some(s) = self.opt_as_str() {
fmt::Display::fmt(s, f)
} else {
fmt::Debug::fmt(&self.0, f)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

{
if error != expected {
panic!(
"CycleError coherency check failed:\n expected: {expected:#?}\n got: {error:#?}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of "expected" and "got", would it be better to indicate the single-threaded result and the multi-threaded result? It's conceivable that the single-threaded one might be wrong.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, does "coherency" have a particular meaning. It makes me think of trait coherency, which is unrelated. "Consistency" might be better, it doesn't have a Rust-specific meaning, e.g. "inconsistent query cycle detection: ...".

.collect(),
};

if cfg!(debug_assertions)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no added comments in this commit. An explanatory comment here would be useful. Something like "compare the single-threaded cycle detection against the multi-threaded cycle detection".

@nnethercote nnethercote added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 17, 2026
@nnethercote
Copy link
Contributor

Also, presumably we can't land this PR as is due to the test failures. I assume the idea is to show the existing bug, and that prompts other fixes?

impl fmt::Debug for Symbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
if let Some(s) = self.opt_as_str() {
Copy link
Contributor

@petrochenkov petrochenkov Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if let Some(s) = self.opt_as_str() {
if are_session_globals_set() {

Together with a comment #153953 (comment) explaining why it's acceptable.
opt_as_str can be removed, it shouldn't be a generally available method.

@Zoxc
Copy link
Contributor

Zoxc commented Mar 17, 2026

The main problem with this PR would be that it tests for a property we do not have, which means it's effectively just adding unnecessary panics.

It's also unclear to me if the use of find_cycle_in_stack here results in cycle equivalent to an execution on a single thread, given that it makes assumptions that doesn't hold for the calls here.

@rust-bors
Copy link
Contributor

rust-bors bot commented Mar 18, 2026

☔ The latest upstream changes (presumably #154032) made this pull request unmergeable. Please resolve the merge conflicts.

@zetanumbers
Copy link
Contributor Author

zetanumbers commented Mar 18, 2026

I ground my changes in improvements to observable consistency between single-threaded and multi-threaded runs of "cycle detected" tests for which I compare those runs' outputs. It's WIP right now, so this isn't yet the final version.

The main problem with this PR would be that it tests for a property we do not have, which means it's effectively just adding unnecessary panics.

Why don't we have this property? Isn't it possible to add such a property? Or are you saying we shouldn't try doing this change?

It's also unclear to me if the use of find_cycle_in_stack here results in cycle equivalent to an execution on a single thread, given that it makes assumptions that doesn't hold for the calls here.

Could you elaborate on which assumptions you are talking about?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-query-system Area: The rustc query system (https://rustc-dev-guide.rust-lang.org/query.html) S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants