1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
use std::{
panic::AssertUnwindSafe,
sync::atomic::{AtomicU32, Ordering},
time::Duration,
};
use inc_complete::{
Db, DbHandle, Storage, define_intermediate, intermediate, storage::HashMapStorage,
};
#[derive(Default, Storage)]
struct MyStorage {
check: HashMapStorage<Foo>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
struct Foo(bool);
// We expect 2 cycle errors to be issued: one for each thread.
static ASSERTS_PASSED: AtomicU32 = AtomicU32::new(0);
// Cycle between Foo(false) <-> Foo(true) but both are on separate threads
#[intermediate(id = 0)]
fn foo(ctx: &Foo, db: &DbHandle<MyStorage>) -> u32 {
println!("Foo({}) on thread {:?}", ctx.0, std::thread::current().id());
std::thread::sleep(Duration::from_millis(250));
// Scenario 1: both threads operate roughly in sync and the true thread grabs the false
// resource while the false thread grabs the true resource. Both threads should immediately
// error with a cycle error.
//
// Scenario 2: both threads start quickly but one thread panics before the other tries to grab
// the resource. The resource is now free for the other thread, and it is able to recur. The
// other thread recurs and later errors after discovering the cycle on a single thread.
match std::panic::catch_unwind(AssertUnwindSafe(|| Foo(!ctx.0).get(db))) {
Ok(_) => panic!("Ran cycle on Foo({}) without error!", ctx.0),
Err(message) => {
if let Some(message) = message.downcast_ref::<String>() {
// The false thread will panic with `1. Foo(false)\n 2. Foo(true)\n 3. Foo(false)`, while the
// true thread will panic with `1. Foo(true)\n 2. Foo(false)\n 3. Foo(true)`. This needs
// to return true for both.
assert!(message.contains("Foo(false)"));
assert!(message.contains("Foo(true)"));
println!(
"Caught cycle panic on thread {:?}",
std::thread::current().id()
);
ASSERTS_PASSED.fetch_add(1, Ordering::Relaxed);
} else {
println!("No cycle panic on thread {:?}", std::thread::current().id());
}
panic!()
}
}
}
#[test]
fn cycle_between_two_threads() {
// Cycle: Foo(false) <-> Foo(true)
let db = Db::<MyStorage>::new();
std::thread::scope(|scope| {
let f = scope.spawn(|| db.get(Foo(false)));
let t = scope.spawn(|| db.get(Foo(true)));
t.join().ok();
f.join().ok();
});
assert_eq!(ASSERTS_PASSED.load(Ordering::SeqCst), 2);
}