mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-01-14 17:18:20 +00:00
daaa78056a
Under this design, *all* databases are a `DatabaseImpl<U>`, where the `U` implements `UserData` (you can use `()` if there is none). Code would default to `&dyn salsa::Database` but if you want to give access to the userdata, you can define a custom database trait `MyDatabase: salsa::Databse` so long as you * annotate `MyDatabase` trait definition of impls of `MyDatabase` with `#[salsa::db]` * implement `MyDatabase` for `DatabaseImpl<U>` where `U` is your userdata (this could be a blanket impl, if you don't know the precise userdata type). The `tests/common/mod.rs` shows the pattern.
80 lines
2 KiB
Rust
80 lines
2 KiB
Rust
//! 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;
|
|
use crate::setup::KnobsDatabase;
|
|
use expect_test::expect;
|
|
use salsa::Database as _;
|
|
use salsa::DatabaseImpl;
|
|
use salsa::Handle;
|
|
|
|
#[salsa::input]
|
|
pub(crate) struct MyInput {
|
|
field: i32,
|
|
}
|
|
|
|
#[salsa::tracked]
|
|
pub(crate) fn a(db: &dyn KnobsDatabase, input: MyInput) -> i32 {
|
|
// Wait to create the cycle until both threads have entered
|
|
db.signal(1);
|
|
db.wait_for(2);
|
|
|
|
b(db, input)
|
|
}
|
|
|
|
#[salsa::tracked]
|
|
pub(crate) fn b(db: &dyn KnobsDatabase, input: MyInput) -> 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
|
|
a(db, input)
|
|
}
|
|
|
|
#[test]
|
|
fn execute() {
|
|
let db = Handle::new(<DatabaseImpl<Knobs>>::default());
|
|
db.knobs().signal_on_will_block.store(3);
|
|
|
|
let input = MyInput::new(&*db, -1);
|
|
|
|
let thread_a = std::thread::spawn({
|
|
let db = db.clone();
|
|
move || a(&*db, input)
|
|
});
|
|
|
|
let thread_b = std::thread::spawn({
|
|
let db = db.clone();
|
|
move || b(&*db, input)
|
|
});
|
|
|
|
// 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();
|
|
db.attach(|_| {
|
|
if let Some(c) = err_b.downcast_ref::<salsa::Cycle>() {
|
|
let expected = expect![[r#"
|
|
[
|
|
a(0),
|
|
b(0),
|
|
]
|
|
"#]];
|
|
expected.assert_debug_eq(&c.all_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::Cancelled>()
|
|
.is_some());
|
|
}
|