mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-07-08 05:45:17 +00:00

Some checks are pending
Test / Test (push) Waiting to run
Test / Miri (push) Waiting to run
Test / Shuttle (push) Waiting to run
Test / Benchmarks (push) Waiting to run
Book / Book (push) Waiting to run
Book / Deploy (push) Blocked by required conditions
Release-plz / Release-plz release (push) Waiting to run
Release-plz / Release-plz PR (push) Waiting to run
* Set `validate_final` in `execute` after removing the last cycle head * Add runaway query repro * Add tracing * Fix part 1 * Fix `cycle_head_kinds` to always return provisional for memos that aren't verified final (They should be validated by `validate_same_iteration` or wait for the cycle head * Fix cycle error * Documentation * Fix await for queries depending on initial value * correctly initialize queued * Cleanup * Short circuit if entire query runs on single thread * Move parallel code into its own method * Rename method, add self_key to queued * Revert self-key changes * Move check *after* `deep_verify_memo` * Add a test for a cycle with changing cycle heads * Short circuit more often * Consider iteration in `validate_provisional` * Only yield if all heads result in a cycle. Retry if even just one inner cycle made progress (in which case there's a probably a new memo) * Fix hangs * Cargo fmt * clippy * Fix hang if cycle initial panics * Rename `cycle_head_kind` enable `cycle_a_t1_b_t2_fallback` shuttle test * Cleanup * Docs
110 lines
3.2 KiB
Rust
110 lines
3.2 KiB
Rust
//! Test a deeply nested-cycle scenario where cycles have changing query dependencies.
|
|
//!
|
|
//! The trick is that different threads call into the same cycle from different entry queries and
|
|
//! the cycle heads change over different iterations
|
|
//!
|
|
//! * Thread 1: `a` -> b -> c
|
|
//! * Thread 2: `b`
|
|
//! * Thread 3: `d` -> `c`
|
|
//! * Thread 4: `e` -> `c`
|
|
//!
|
|
//! `c` calls:
|
|
//! * `d` and `a` in the first few iterations
|
|
//! * `d`, `b` and `e` in the last iterations
|
|
use crate::sync::thread;
|
|
use crate::{Knobs, KnobsDatabase};
|
|
|
|
use salsa::CycleRecoveryAction;
|
|
|
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, salsa::Update)]
|
|
struct CycleValue(u32);
|
|
|
|
const MIN: CycleValue = CycleValue(0);
|
|
const MAX: CycleValue = CycleValue(3);
|
|
|
|
#[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=initial)]
|
|
fn query_a(db: &dyn KnobsDatabase) -> CycleValue {
|
|
query_b(db)
|
|
}
|
|
|
|
#[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=initial)]
|
|
fn query_b(db: &dyn KnobsDatabase) -> CycleValue {
|
|
let c_value = query_c(db);
|
|
CycleValue(c_value.0 + 1).min(MAX)
|
|
}
|
|
|
|
#[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=initial)]
|
|
fn query_c(db: &dyn KnobsDatabase) -> CycleValue {
|
|
let d_value = query_d(db);
|
|
|
|
if d_value > CycleValue(0) {
|
|
let e_value = query_e(db);
|
|
let b_value = query_b(db);
|
|
CycleValue(d_value.0.max(e_value.0).max(b_value.0))
|
|
} else {
|
|
let a_value = query_a(db);
|
|
CycleValue(d_value.0.max(a_value.0))
|
|
}
|
|
}
|
|
|
|
#[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=initial)]
|
|
fn query_d(db: &dyn KnobsDatabase) -> CycleValue {
|
|
query_c(db)
|
|
}
|
|
|
|
#[salsa::tracked(cycle_fn=cycle_fn, cycle_initial=initial)]
|
|
fn query_e(db: &dyn KnobsDatabase) -> CycleValue {
|
|
query_c(db)
|
|
}
|
|
|
|
fn cycle_fn(
|
|
_db: &dyn KnobsDatabase,
|
|
_value: &CycleValue,
|
|
_count: u32,
|
|
) -> CycleRecoveryAction<CycleValue> {
|
|
CycleRecoveryAction::Iterate
|
|
}
|
|
|
|
fn initial(_db: &dyn KnobsDatabase) -> CycleValue {
|
|
MIN
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn the_test() {
|
|
crate::sync::check(|| {
|
|
tracing::debug!("New run");
|
|
let db_t1 = Knobs::default();
|
|
let db_t2 = db_t1.clone();
|
|
let db_t3 = db_t1.clone();
|
|
let db_t4 = db_t1.clone();
|
|
|
|
let t1 = thread::spawn(move || {
|
|
let _span = tracing::debug_span!("t1", thread_id = ?thread::current().id()).entered();
|
|
let result = query_a(&db_t1);
|
|
db_t1.signal(1);
|
|
result
|
|
});
|
|
let t2 = thread::spawn(move || {
|
|
let _span = tracing::debug_span!("t4", thread_id = ?thread::current().id()).entered();
|
|
db_t4.wait_for(1);
|
|
query_b(&db_t4)
|
|
});
|
|
let t3 = thread::spawn(move || {
|
|
let _span = tracing::debug_span!("t2", thread_id = ?thread::current().id()).entered();
|
|
db_t2.wait_for(1);
|
|
query_d(&db_t2)
|
|
});
|
|
let t4 = thread::spawn(move || {
|
|
let _span = tracing::debug_span!("t3", thread_id = ?thread::current().id()).entered();
|
|
db_t3.wait_for(1);
|
|
query_e(&db_t3)
|
|
});
|
|
|
|
let r_t1 = t1.join().unwrap();
|
|
let r_t2 = t2.join().unwrap();
|
|
let r_t3 = t3.join().unwrap();
|
|
let r_t4 = t4.join().unwrap();
|
|
|
|
assert_eq!((r_t1, r_t2, r_t3, r_t4), (MAX, MAX, MAX, MAX));
|
|
});
|
|
}
|