2021-10-31 10:15:37 +00:00
|
|
|
use salsa::{Cancelled, ParallelDatabase, Snapshot};
|
2021-10-24 09:51:03 +00:00
|
|
|
use test_env_log::test;
|
2019-02-03 19:10:49 +00:00
|
|
|
|
2021-10-27 08:51:30 +00:00
|
|
|
// Axes:
|
|
|
|
//
|
|
|
|
// Threading
|
|
|
|
// * Intra-thread
|
|
|
|
// * Cross-thread -- part of cycle is on one thread, part on another
|
|
|
|
//
|
|
|
|
// Recovery strategies:
|
|
|
|
// * Panic
|
|
|
|
// * Fallback
|
|
|
|
// * Mixed -- multiple strategies within cycle participants
|
|
|
|
//
|
|
|
|
// Across revisions:
|
|
|
|
// * N/A -- only one revision
|
|
|
|
// * Present in new revision, not old
|
|
|
|
// * Present in old revision, not new
|
|
|
|
// * Present in both revisions
|
|
|
|
//
|
|
|
|
// Dependencies
|
|
|
|
// * Tracked
|
|
|
|
// * Untracked -- cycle participant(s) contain untracked reads
|
|
|
|
//
|
|
|
|
// Layers
|
|
|
|
// * Direct -- cycle participant is directly invoked from test
|
|
|
|
// * Indirect -- invoked a query that invokes the cycle
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// | Thread | Recovery | Old, New | Dep style | Layers | Test Name |
|
|
|
|
// | ------ | -------- | -------- | --------- | ------ | --------- |
|
|
|
|
// | Intra | Panic | N/A | Tracked | direct | cycle_memoized |
|
|
|
|
// | Intra | Panic | N/A | Untracked | direct | cycle_volatile |
|
|
|
|
// | Intra | Fallback | N/A | Tracked | direct | cycle_cycle |
|
|
|
|
// | Intra | Fallback | N/A | Tracked | indirect | inner_cycle |
|
|
|
|
// | Intra | Fallback | Both | Tracked | direct | cycle_revalidate |
|
|
|
|
// | Intra | Fallback | New | Tracked | direct | cycle_appears |
|
|
|
|
// | Intra | Fallback | Old | Tracked | direct | cycle_disappears |
|
2021-10-31 10:35:00 +00:00
|
|
|
// | Intra | Mixed | N/A | Tracked | direct | cycle_mixed_1 |
|
|
|
|
// | Intra | Mixed | N/A | Tracked | direct | cycle_mixed_2 |
|
2021-10-27 09:49:02 +00:00
|
|
|
// | Cross | Fallback | N/A | Tracked | both | parallel/cycles.rs: recover_parallel_cycle |
|
|
|
|
// | Cross | Panic | N/A | Tracked | both | parallel/cycles.rs: panic_parallel_cycle |
|
2021-10-27 08:51:30 +00:00
|
|
|
|
2019-02-03 19:10:49 +00:00
|
|
|
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
|
|
|
struct Error {
|
|
|
|
cycle: Vec<String>,
|
|
|
|
}
|
|
|
|
|
2019-01-25 15:25:17 +00:00
|
|
|
#[salsa::database(GroupStruct)]
|
2019-01-23 02:53:06 +00:00
|
|
|
struct DatabaseImpl {
|
2020-07-02 10:31:02 +00:00
|
|
|
storage: salsa::Storage<Self>,
|
2018-10-01 10:47:24 +00:00
|
|
|
}
|
|
|
|
|
2020-07-02 10:31:02 +00:00
|
|
|
impl salsa::Database for DatabaseImpl {}
|
2018-10-01 10:47:24 +00:00
|
|
|
|
2019-02-03 19:10:49 +00:00
|
|
|
impl ParallelDatabase for DatabaseImpl {
|
|
|
|
fn snapshot(&self) -> Snapshot<Self> {
|
|
|
|
Snapshot::new(DatabaseImpl {
|
2020-07-02 10:31:02 +00:00
|
|
|
storage: self.storage.snapshot(),
|
2019-02-03 19:10:49 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-23 12:38:04 +00:00
|
|
|
impl Default for DatabaseImpl {
|
|
|
|
fn default() -> Self {
|
|
|
|
let mut res = DatabaseImpl {
|
|
|
|
storage: salsa::Storage::default(),
|
|
|
|
};
|
2021-10-31 10:35:00 +00:00
|
|
|
|
|
|
|
// Default configuration:
|
|
|
|
//
|
|
|
|
// A --> B <-- C
|
|
|
|
// ^ |
|
|
|
|
// +-----+
|
|
|
|
|
|
|
|
res.set_a_invokes(CycleQuery::B);
|
|
|
|
res.set_b_invokes(CycleQuery::A);
|
|
|
|
res.set_c_invokes(CycleQuery::B);
|
2021-03-23 12:38:04 +00:00
|
|
|
res
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-31 10:35:00 +00:00
|
|
|
/// The queries A, B, and C in `Database` can be configured
|
|
|
|
/// to invoke one another in arbitrary ways using this
|
|
|
|
/// enum.
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
|
|
enum CycleQuery {
|
|
|
|
None,
|
|
|
|
A,
|
|
|
|
B,
|
|
|
|
C,
|
2021-11-01 10:15:56 +00:00
|
|
|
AthenC,
|
2021-10-31 10:35:00 +00:00
|
|
|
}
|
|
|
|
|
2019-01-25 15:25:17 +00:00
|
|
|
#[salsa::query_group(GroupStruct)]
|
2019-01-12 10:11:59 +00:00
|
|
|
trait Database: salsa::Database {
|
|
|
|
// `a` and `b` depend on each other and form a cycle
|
|
|
|
fn memoized_a(&self) -> ();
|
|
|
|
fn memoized_b(&self) -> ();
|
|
|
|
fn volatile_a(&self) -> ();
|
|
|
|
fn volatile_b(&self) -> ();
|
2019-02-03 19:10:49 +00:00
|
|
|
|
2021-03-23 12:38:04 +00:00
|
|
|
#[salsa::input]
|
2021-10-31 10:35:00 +00:00
|
|
|
fn a_invokes(&self) -> CycleQuery;
|
|
|
|
|
|
|
|
#[salsa::input]
|
|
|
|
fn b_invokes(&self) -> CycleQuery;
|
|
|
|
|
|
|
|
#[salsa::input]
|
|
|
|
fn c_invokes(&self) -> CycleQuery;
|
2019-02-03 19:10:49 +00:00
|
|
|
|
|
|
|
#[salsa::cycle(recover_a)]
|
|
|
|
fn cycle_a(&self) -> Result<(), Error>;
|
2021-10-27 08:51:30 +00:00
|
|
|
|
2019-02-03 19:10:49 +00:00
|
|
|
#[salsa::cycle(recover_b)]
|
|
|
|
fn cycle_b(&self) -> Result<(), Error>;
|
|
|
|
|
|
|
|
fn cycle_c(&self) -> Result<(), Error>;
|
|
|
|
}
|
|
|
|
|
2021-10-31 11:21:07 +00:00
|
|
|
fn recover_a(db: &dyn Database, cycle: &salsa::Cycle) -> Result<(), Error> {
|
2019-02-03 19:10:49 +00:00
|
|
|
Err(Error {
|
2021-10-31 11:21:07 +00:00
|
|
|
cycle: cycle.all_participants(db),
|
2019-02-03 19:10:49 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-10-31 11:21:07 +00:00
|
|
|
fn recover_b(db: &dyn Database, cycle: &salsa::Cycle) -> Result<(), Error> {
|
2019-02-03 19:10:49 +00:00
|
|
|
Err(Error {
|
2021-10-31 11:21:07 +00:00
|
|
|
cycle: cycle.all_participants(db),
|
2019-02-03 19:10:49 +00:00
|
|
|
})
|
2018-10-01 10:47:24 +00:00
|
|
|
}
|
|
|
|
|
2021-06-03 11:12:38 +00:00
|
|
|
fn memoized_a(db: &dyn Database) {
|
2018-10-19 01:26:48 +00:00
|
|
|
db.memoized_b()
|
2018-10-01 10:47:24 +00:00
|
|
|
}
|
|
|
|
|
2021-06-03 11:12:38 +00:00
|
|
|
fn memoized_b(db: &dyn Database) {
|
2018-10-19 01:26:48 +00:00
|
|
|
db.memoized_a()
|
2018-10-01 10:47:24 +00:00
|
|
|
}
|
|
|
|
|
2021-06-03 11:12:38 +00:00
|
|
|
fn volatile_a(db: &dyn Database) {
|
2019-06-19 16:59:03 +00:00
|
|
|
db.salsa_runtime().report_untracked_read();
|
2018-10-19 01:26:48 +00:00
|
|
|
db.volatile_b()
|
2018-10-01 10:47:24 +00:00
|
|
|
}
|
|
|
|
|
2021-06-03 11:12:38 +00:00
|
|
|
fn volatile_b(db: &dyn Database) {
|
2019-06-19 16:59:03 +00:00
|
|
|
db.salsa_runtime().report_untracked_read();
|
2018-10-19 01:26:48 +00:00
|
|
|
db.volatile_a()
|
2018-10-01 10:47:24 +00:00
|
|
|
}
|
|
|
|
|
2021-10-31 10:35:00 +00:00
|
|
|
impl CycleQuery {
|
|
|
|
fn invoke(self, db: &dyn Database) -> Result<(), Error> {
|
|
|
|
match self {
|
|
|
|
CycleQuery::A => db.cycle_a(),
|
|
|
|
CycleQuery::B => db.cycle_b(),
|
|
|
|
CycleQuery::C => db.cycle_c(),
|
2021-11-01 10:15:56 +00:00
|
|
|
CycleQuery::AthenC => {
|
|
|
|
let _ = db.cycle_a();
|
|
|
|
db.cycle_c()
|
|
|
|
}
|
2021-10-31 10:35:00 +00:00
|
|
|
CycleQuery::None => Ok(()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-03 10:46:00 +00:00
|
|
|
fn cycle_a(db: &dyn Database) -> Result<(), Error> {
|
2021-11-01 10:15:56 +00:00
|
|
|
dbg!("cycle_a");
|
2021-10-31 10:35:00 +00:00
|
|
|
db.a_invokes().invoke(db)
|
2019-02-03 19:10:49 +00:00
|
|
|
}
|
|
|
|
|
2020-07-03 10:46:00 +00:00
|
|
|
fn cycle_b(db: &dyn Database) -> Result<(), Error> {
|
2021-11-01 10:15:56 +00:00
|
|
|
dbg!("cycle_b");
|
2021-10-31 10:35:00 +00:00
|
|
|
db.b_invokes().invoke(db)
|
2019-02-03 19:10:49 +00:00
|
|
|
}
|
|
|
|
|
2020-07-03 10:46:00 +00:00
|
|
|
fn cycle_c(db: &dyn Database) -> Result<(), Error> {
|
2021-11-01 10:15:56 +00:00
|
|
|
dbg!("cycle_c");
|
2021-10-31 10:35:00 +00:00
|
|
|
db.c_invokes().invoke(db)
|
2019-02-03 19:10:49 +00:00
|
|
|
}
|
|
|
|
|
2018-10-01 10:47:24 +00:00
|
|
|
#[test]
|
|
|
|
fn cycle_memoized() {
|
2021-10-31 10:15:37 +00:00
|
|
|
let db = DatabaseImpl::default();
|
|
|
|
match Cancelled::catch(|| {
|
|
|
|
db.memoized_a();
|
|
|
|
}) {
|
|
|
|
Err(Cancelled::UnexpectedCycle(c)) => {
|
|
|
|
insta::assert_debug_snapshot!(c.unexpected_participants(&db), @r###"
|
|
|
|
[
|
|
|
|
"memoized_a(())",
|
|
|
|
"memoized_b(())",
|
|
|
|
]
|
|
|
|
"###);
|
|
|
|
}
|
|
|
|
v => panic!("unexpected result: {:#?}", v),
|
|
|
|
}
|
2018-10-01 10:47:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn cycle_volatile() {
|
2021-10-31 10:15:37 +00:00
|
|
|
let db = DatabaseImpl::default();
|
|
|
|
match Cancelled::catch(|| {
|
|
|
|
db.volatile_a();
|
|
|
|
}) {
|
|
|
|
Err(Cancelled::UnexpectedCycle(c)) => {
|
|
|
|
insta::assert_debug_snapshot!(c.unexpected_participants(&db), @r###"
|
|
|
|
[
|
|
|
|
"volatile_a(())",
|
|
|
|
"volatile_b(())",
|
|
|
|
]
|
|
|
|
"###);
|
|
|
|
}
|
|
|
|
v => panic!("unexpected result: {:#?}", v),
|
|
|
|
}
|
2018-10-01 10:47:24 +00:00
|
|
|
}
|
2019-02-03 19:10:49 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn cycle_cycle() {
|
|
|
|
let query = DatabaseImpl::default();
|
|
|
|
assert!(query.cycle_a().is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn inner_cycle() {
|
|
|
|
let query = DatabaseImpl::default();
|
|
|
|
let err = query.cycle_c();
|
|
|
|
assert!(err.is_err());
|
|
|
|
let cycle = err.unwrap_err().cycle;
|
2021-10-31 11:21:07 +00:00
|
|
|
insta::assert_debug_snapshot!(cycle, @r###"
|
|
|
|
[
|
|
|
|
"cycle_a(())",
|
|
|
|
"cycle_b(())",
|
|
|
|
]
|
|
|
|
"###);
|
2019-02-03 19:10:49 +00:00
|
|
|
}
|
|
|
|
|
2021-03-20 14:34:33 +00:00
|
|
|
#[test]
|
|
|
|
fn cycle_revalidate() {
|
|
|
|
let mut db = DatabaseImpl::default();
|
|
|
|
assert!(db.cycle_a().is_err());
|
2021-10-31 10:35:00 +00:00
|
|
|
db.set_b_invokes(CycleQuery::A); // same value as default
|
2021-03-23 12:38:04 +00:00
|
|
|
assert!(db.cycle_a().is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn cycle_appears() {
|
|
|
|
let mut db = DatabaseImpl::default();
|
2021-10-31 10:35:00 +00:00
|
|
|
db.set_b_invokes(CycleQuery::None);
|
2021-03-23 12:38:04 +00:00
|
|
|
assert!(db.cycle_a().is_ok());
|
2021-10-31 10:35:00 +00:00
|
|
|
db.set_b_invokes(CycleQuery::A);
|
2021-10-24 09:51:03 +00:00
|
|
|
log::debug!("Set Cycle Leaf");
|
2021-03-23 12:38:04 +00:00
|
|
|
assert!(db.cycle_a().is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn cycle_disappears() {
|
|
|
|
let mut db = DatabaseImpl::default();
|
2021-03-20 14:34:33 +00:00
|
|
|
assert!(db.cycle_a().is_err());
|
2021-10-31 10:35:00 +00:00
|
|
|
db.set_b_invokes(CycleQuery::None);
|
2021-03-23 12:38:04 +00:00
|
|
|
assert!(db.cycle_a().is_ok());
|
2021-03-20 14:34:33 +00:00
|
|
|
}
|
2021-10-31 10:35:00 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn cycle_mixed_1() {
|
|
|
|
let mut db = DatabaseImpl::default();
|
|
|
|
// Configuration:
|
|
|
|
//
|
|
|
|
// A --> B <-- C
|
|
|
|
// | ^
|
|
|
|
// +-----+
|
|
|
|
db.set_b_invokes(CycleQuery::C);
|
|
|
|
match Cancelled::catch(|| db.cycle_a()) {
|
|
|
|
Err(Cancelled::UnexpectedCycle(u)) => {
|
|
|
|
insta::assert_debug_snapshot!((u.all_participants(&db), u.unexpected_participants(&db)), @r###"
|
|
|
|
(
|
|
|
|
[
|
|
|
|
"cycle_b(())",
|
|
|
|
"cycle_c(())",
|
|
|
|
],
|
|
|
|
[
|
|
|
|
"cycle_c(())",
|
|
|
|
],
|
|
|
|
)
|
|
|
|
"###);
|
|
|
|
}
|
|
|
|
v => panic!("unexpected result: {:?}", v),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn cycle_mixed_2() {
|
|
|
|
let mut db = DatabaseImpl::default();
|
|
|
|
// Configuration:
|
|
|
|
//
|
|
|
|
// A --> B --> C
|
|
|
|
// ^ |
|
|
|
|
// +-----------+
|
|
|
|
db.set_b_invokes(CycleQuery::C);
|
|
|
|
db.set_c_invokes(CycleQuery::A);
|
|
|
|
match Cancelled::catch(|| db.cycle_a()) {
|
|
|
|
Err(Cancelled::UnexpectedCycle(u)) => {
|
|
|
|
insta::assert_debug_snapshot!((u.all_participants(&db), u.unexpected_participants(&db)), @r###"
|
|
|
|
(
|
|
|
|
[
|
|
|
|
"cycle_a(())",
|
|
|
|
"cycle_b(())",
|
|
|
|
"cycle_c(())",
|
|
|
|
],
|
|
|
|
[
|
|
|
|
"cycle_c(())",
|
|
|
|
],
|
|
|
|
)
|
|
|
|
"###);
|
|
|
|
}
|
|
|
|
v => panic!("unexpected result: {:?}", v),
|
|
|
|
}
|
|
|
|
}
|
2021-10-31 11:21:07 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn cycle_deterministic_order() {
|
|
|
|
// No matter whether we start from A or B, we get the same set of participants:
|
|
|
|
let a = DatabaseImpl::default().cycle_a();
|
|
|
|
let b = DatabaseImpl::default().cycle_b();
|
|
|
|
insta::assert_debug_snapshot!((a, b), @r###"
|
|
|
|
(
|
|
|
|
Err(
|
|
|
|
Error {
|
|
|
|
cycle: [
|
|
|
|
"cycle_a(())",
|
|
|
|
"cycle_b(())",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
),
|
|
|
|
Err(
|
|
|
|
Error {
|
|
|
|
cycle: [
|
|
|
|
"cycle_a(())",
|
|
|
|
"cycle_b(())",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
"###);
|
|
|
|
}
|
2021-11-01 10:15:56 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn cycle_multiple() {
|
|
|
|
// No matter whether we start from A or B, we get the same set of participants:
|
|
|
|
let mut db = DatabaseImpl::default();
|
|
|
|
|
|
|
|
// Configuration:
|
|
|
|
//
|
|
|
|
// A --> B <-- C
|
|
|
|
// ^ | ^
|
|
|
|
// +-----+ |
|
|
|
|
// | |
|
|
|
|
// +-----+
|
|
|
|
//
|
|
|
|
// Here, conceptually, B encounters a cycle with A and then
|
|
|
|
// recovers.
|
|
|
|
|
|
|
|
db.set_b_invokes(CycleQuery::AthenC);
|
|
|
|
let c = db.cycle_c();
|
|
|
|
let b = db.cycle_b();
|
|
|
|
let a = db.cycle_a();
|
|
|
|
insta::assert_debug_snapshot!((a, b, c), @r###"
|
|
|
|
(
|
|
|
|
Err(
|
|
|
|
Error {
|
|
|
|
cycle: [
|
|
|
|
"cycle_a(())",
|
|
|
|
"cycle_b(())",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
),
|
|
|
|
Err(
|
|
|
|
Error {
|
|
|
|
cycle: [
|
|
|
|
"cycle_a(())",
|
|
|
|
"cycle_b(())",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
),
|
|
|
|
Err(
|
|
|
|
Error {
|
|
|
|
cycle: [
|
|
|
|
"cycle_a(())",
|
|
|
|
"cycle_b(())",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
"###);
|
|
|
|
}
|