//! 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; use test_log::test; #[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::() { expect![[r#" [ "a(-1)", "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::().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) }