//! Test a cycle where no queries recover that occurs across threads.
//! See the `../cycles.rs` for a complete listing of cycle tests,
//! both intra and cross thread.

use crate::setup::{Knobs, ParDatabaseImpl};
use expect_test::expect;
use salsa::ParallelDatabase;

#[test]
fn parallel_cycle_none_recover() {
    let db = ParDatabaseImpl::default();
    db.knobs().signal_on_will_block.set(3);

    let thread_a = std::thread::spawn({
        let db = db.snapshot();
        move || db.a(-1)
    });

    let thread_b = std::thread::spawn({
        let db = db.snapshot();
        move || db.b(-1)
    });

    // We expect B to panic because it detects a cycle (it is the one that calls A, ultimately).
    // Right now, it panics with a string.
    let err_b = thread_b.join().unwrap_err();
    if let Some(c) = err_b.downcast_ref::<salsa::Cycle>() {
        expect![[r#"
            [
                "parallel::parallel_cycle_none_recover::AQuery::a(-1)",
                "parallel::parallel_cycle_none_recover::BQuery::b(-1)",
            ]
        "#]]
        .assert_debug_eq(&c.unexpected_participants(&db));
    } else {
        panic!("b failed in an unexpected way: {err_b:?}");
    }

    // We expect A to propagate a panic, which causes us to use the sentinel
    // type `Canceled`.
    assert!(thread_a.join().unwrap_err().downcast_ref::<salsa::Cycle>().is_some());
}

#[salsa::query_group(ParallelCycleNoneRecover)]
pub(crate) trait TestDatabase: Knobs {
    fn a(&self, key: i32) -> i32;
    fn b(&self, key: i32) -> i32;
}

fn a(db: &dyn TestDatabase, key: i32) -> i32 {
    // Wait to create the cycle until both threads have entered
    db.signal(1);
    db.wait_for(2);

    db.b(key)
}

fn b(db: &dyn TestDatabase, key: i32) -> i32 {
    // Wait to create the cycle until both threads have entered
    db.wait_for(1);
    db.signal(2);

    // Wait for thread A to block on this thread
    db.wait_for(3);

    // Now try to execute A
    db.a(key)
}
