#![cfg(feature = "shuttle")]
#[path = "support/lin.rs"]
mod lin;
#[path = "support/maps.rs"]
mod maps;
use lin::{Call, Event, linearizable, perform};
use maps::{Clustered, Constant, Grouped, Identity, Map};
use shuttle::sync::Mutex;
use shuttle::sync::atomic::{AtomicU64, Ordering};
use shuttle::thread;
use std::sync::Arc;
type Fold = foldhash::fast::FixedState;
fn iterations() -> usize {
std::env::var("KOVAN_SHUTTLE_ITERS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(300)
}
const ALL: u64 = u64::MAX;
#[derive(Default)]
struct Record {
clock: AtomicU64,
log: Mutex<Vec<(u64, Event)>>,
}
impl Record {
fn call<M: Map<u64, u64>>(&self, m: &M, k: u64, call: Call) -> Option<u64> {
let invoked = self.clock.fetch_add(1, Ordering::SeqCst);
let answer = perform(m, k, call);
let answered = self.clock.fetch_add(1, Ordering::SeqCst);
let key = if call == Call::Clear { ALL } else { k };
self.log.lock().expect("log").push((
key,
Event {
call,
answer,
invoked,
answered,
},
));
answer
}
fn check(&self, initial: &[(u64, Option<u64>)], name: &str) {
let log = self.log.lock().expect("log");
for &(k, start) in initial {
let history: Vec<Event> = log
.iter()
.filter(|(key, _)| *key == k || *key == ALL)
.map(|(_, e)| *e)
.collect();
assert!(
linearizable(&history, start),
"{name} key {k}: no linearization of {history:#?}"
);
}
}
}
fn run_all(bodies: Vec<Box<dyn FnOnce() + Send>>) {
let handles: Vec<_> = bodies.into_iter().map(thread::spawn).collect();
for h in handles {
h.join().expect("a thread of the scenario panicked");
}
}
fn fill<M: Map<u64, u64>>(m: &M, from: u64, n: u64) {
for k in from..from + n {
m.insert(k, 0);
}
}
fn claims_race_a_grow<M: Map<u64, u64>>() {
let m = Arc::new(M::with_capacity(64));
fill(&*m, 1_000, 46);
m.insert(2, 20);
let rec = Arc::new(Record::default());
let (m1, r1) = (Arc::clone(&m), Arc::clone(&rec));
let (m2, r2) = (Arc::clone(&m), Arc::clone(&rec));
let m3 = Arc::clone(&m);
run_all(vec![
Box::new(move || {
r1.call(&*m1, 1, Call::InsertIfAbsent(1));
r1.call(&*m1, 2, Call::Insert(21));
}),
Box::new(move || {
r2.call(&*m2, 1, Call::GetOrInsert(2));
r2.call(&*m2, 2, Call::Remove);
}),
Box::new(move || fill(&*m3, 2_000, 3)),
]);
rec.check(&[(1, None), (2, Some(20))], M::NAME);
assert_eq!(
m.len(),
46 + 3 + 1 + usize::from(m.contains_key(&2)),
"{}: count",
M::NAME
);
}
fn writes_race_a_shrink<M: Map<u64, u64>>() {
let m = Arc::new(M::with_capacity(64));
fill(&*m, 1_000, 49);
for k in 1_000..1_018 {
m.remove(&k);
}
m.insert(5, 5);
let rec = Arc::new(Record::default());
let (m1, r1) = (Arc::clone(&m), Arc::clone(&rec));
let (m2, r2) = (Arc::clone(&m), Arc::clone(&rec));
let m3 = Arc::clone(&m);
run_all(vec![
Box::new(move || {
r1.call(&*m1, 1, Call::Insert(1));
r1.call(&*m1, 1, Call::InsertIfAbsent(2));
}),
Box::new(move || {
r2.call(&*m2, 1, Call::Insert(3));
assert_eq!(
m2.force_remove(&5),
Some(5),
"force_remove of a present key"
);
assert!(
!m2.contains_key(&5),
"a force-removed key found by its remover"
);
r2.call(&*m2, 1, Call::Remove);
r2.call(&*m2, 1, Call::Get);
}),
Box::new(move || {
m3.remove(&1_020);
m3.remove(&1_021);
}),
]);
rec.check(&[(1, None)], M::NAME);
}
fn adjacent_removes<M: Map<u64, u64>>() {
let m = Arc::new(M::with_capacity(64));
for k in [3, 67, 131] {
m.insert(k, k);
}
let rec = Arc::new(Record::default());
let (m1, r1) = (Arc::clone(&m), Arc::clone(&rec));
let (m2, r2) = (Arc::clone(&m), Arc::clone(&rec));
let (m3, r3) = (Arc::clone(&m), Arc::clone(&rec));
run_all(vec![
Box::new(move || {
r1.call(&*m1, 3, Call::Remove);
}),
Box::new(move || {
r2.call(&*m2, 67, Call::Remove);
assert!(!m2.contains_key(&67), "a removed key found by its remover");
}),
Box::new(move || {
r3.call(&*m3, 131, Call::Get);
r3.call(&*m3, 67, Call::Get);
}),
]);
rec.check(&[(3, Some(3)), (67, Some(67)), (131, Some(131))], M::NAME);
assert_eq!(m.len(), 1, "{}: count", M::NAME);
}
fn walk_races_writes_and_a_grow<M: Map<u64, u64>>() {
let m = Arc::new(M::with_capacity(64));
for k in 1..=4 {
m.insert(k, k);
}
fill(&*m, 1_000, 42);
let (m1, m2, m3) = (Arc::clone(&m), Arc::clone(&m), Arc::clone(&m));
let seen = Arc::new(Mutex::new(Vec::new()));
let s1 = Arc::clone(&seen);
run_all(vec![
Box::new(move || {
let keys: Vec<u64> = m1.entries().into_iter().map(|(k, _)| k).collect();
*s1.lock().expect("seen") = keys;
}),
Box::new(move || {
m2.insert(2, 9);
fill(&*m2, 3_000, 3);
}),
Box::new(move || {
m3.remove(&1_000);
m3.insert(5, 5);
}),
]);
let seen = seen.lock().expect("seen");
for k in 1..=4 {
assert_eq!(
seen.iter().filter(|&&s| s == k).count(),
1,
"{}: steady key {k}",
M::NAME
);
}
}
fn clear_races_writes<M: Map<u64, u64>>() {
let m = Arc::new(M::with_capacity(64));
m.insert(2, 2);
let rec = Arc::new(Record::default());
let (m1, r1) = (Arc::clone(&m), Arc::clone(&rec));
let (m2, r2) = (Arc::clone(&m), Arc::clone(&rec));
let (m3, r3) = (Arc::clone(&m), Arc::clone(&rec));
run_all(vec![
Box::new(move || {
r1.call(&*m1, 0, Call::Clear);
}),
Box::new(move || {
r2.call(&*m2, 1, Call::Insert(1));
r2.call(&*m2, 1, Call::Get);
}),
Box::new(move || {
r3.call(&*m3, 2, Call::InsertIfAbsent(3));
r3.call(&*m3, 2, Call::Remove);
}),
]);
rec.check(&[(1, None), (2, Some(2))], M::NAME);
assert_eq!(m.len(), m.entries().len(), "{}: count", M::NAME);
}
fn writes_race_a_move<M: Map<u64, u64>>() {
let m = Arc::new(M::with_capacity(64));
for k in 0..=32 {
m.insert(k, k);
}
let rec = Arc::new(Record::default());
let (m1, r1) = (Arc::clone(&m), Arc::clone(&rec));
let (m2, r2) = (Arc::clone(&m), Arc::clone(&rec));
let (m3, r3) = (Arc::clone(&m), Arc::clone(&rec));
run_all(vec![
Box::new(move || {
r1.call(&*m1, 64, Call::Insert(64));
r1.call(&*m1, 64, Call::Remove);
}),
Box::new(move || {
r2.call(&*m2, 2, Call::Get);
r2.call(&*m2, 3, Call::Remove);
}),
Box::new(move || {
r3.call(&*m3, 2, Call::InsertIfAbsent(7));
r3.call(&*m3, 3, Call::GetOrInsert(8));
}),
]);
rec.check(&[(2, Some(2)), (3, Some(3)), (64, None)], M::NAME);
}
fn conditional_writes_race_a_grow<M: Map<u64, u64>>() {
let m = Arc::new(M::with_capacity(64));
fill(&*m, 1_000, 46);
m.insert(1, 2);
m.insert(2, 4);
let rec = Arc::new(Record::default());
let (m1, r1) = (Arc::clone(&m), Arc::clone(&rec));
let (m2, r2) = (Arc::clone(&m), Arc::clone(&rec));
let m3 = Arc::clone(&m);
run_all(vec![
Box::new(move || {
r1.call(&*m1, 1, Call::CompareAndSwap(2, 3));
r1.call(&*m1, 2, Call::ComputeAdd(10));
}),
Box::new(move || {
r2.call(&*m2, 1, Call::RemoveIfEven);
r2.call(&*m2, 2, Call::ReplaceIfEven(5));
}),
Box::new(move || fill(&*m3, 2_000, 3)),
]);
rec.check(&[(1, Some(2)), (2, Some(4))], M::NAME);
assert_eq!(m.len(), m.entries().len(), "{}: count", M::NAME);
}
fn compute_of_an_absent_key_races_claims_and_a_clear<M: Map<u64, u64>>() {
let m = Arc::new(M::with_capacity(64));
m.insert(3, 3);
let rec = Arc::new(Record::default());
let (m1, r1) = (Arc::clone(&m), Arc::clone(&rec));
let (m2, r2) = (Arc::clone(&m), Arc::clone(&rec));
let (m3, r3) = (Arc::clone(&m), Arc::clone(&rec));
run_all(vec![
Box::new(move || {
r1.call(&*m1, 67, Call::ComputeToggle(1));
r1.call(&*m1, 67, Call::ComputeAdd(4));
}),
Box::new(move || {
r2.call(&*m2, 67, Call::InsertIfAbsent(2));
r2.call(&*m2, 131, Call::Insert(5));
}),
Box::new(move || {
r3.call(&*m3, 3, Call::CompareAndRemove(3));
r3.call(&*m3, 0, Call::Clear);
}),
]);
rec.check(&[(3, Some(3)), (67, None), (131, None)], M::NAME);
assert_eq!(m.len(), m.entries().len(), "{}: count", M::NAME);
}
fn conditional_writes_race_adjacent_removes<M: Map<u64, u64>>() {
let m = Arc::new(M::with_capacity(64));
for k in [3, 67, 131] {
m.insert(k, k + 1);
}
let rec = Arc::new(Record::default());
let (m1, r1) = (Arc::clone(&m), Arc::clone(&rec));
let (m2, r2) = (Arc::clone(&m), Arc::clone(&rec));
let (m3, r3) = (Arc::clone(&m), Arc::clone(&rec));
run_all(vec![
Box::new(move || {
r1.call(&*m1, 3, Call::RemoveIfEven);
r1.call(&*m1, 131, Call::ComputeToggle(9));
}),
Box::new(move || {
r2.call(&*m2, 67, Call::Remove);
r2.call(&*m2, 3, Call::ReplaceIfEven(6));
}),
Box::new(move || {
r3.call(&*m3, 131, Call::Get);
r3.call(&*m3, 67, Call::CompareAndSwap(68, 1));
}),
]);
rec.check(&[(3, Some(4)), (67, Some(68)), (131, Some(132))], M::NAME);
assert_eq!(m.len(), m.entries().len(), "{}: count", M::NAME);
}
fn conditional_writes_race_a_move<M: Map<u64, u64>>() {
let m = Arc::new(M::with_capacity(64));
for k in 0..=32 {
m.insert(k, k);
}
let rec = Arc::new(Record::default());
let (m1, r1) = (Arc::clone(&m), Arc::clone(&rec));
let (m2, r2) = (Arc::clone(&m), Arc::clone(&rec));
let (m3, r3) = (Arc::clone(&m), Arc::clone(&rec));
run_all(vec![
Box::new(move || {
r1.call(&*m1, 64, Call::Insert(64));
r1.call(&*m1, 64, Call::RemoveIfEven);
}),
Box::new(move || {
r2.call(&*m2, 2, Call::CompareAndSwap(2, 7));
r2.call(&*m2, 128, Call::ComputeToggle(5));
}),
Box::new(move || {
r3.call(&*m3, 3, Call::ComputeAdd(1));
r3.call(&*m3, 2, Call::RemoveIfEven);
}),
]);
rec.check(
&[(2, Some(2)), (3, Some(3)), (64, None), (128, None)],
M::NAME,
);
assert_eq!(m.len(), m.entries().len(), "{}: count", M::NAME);
}
fn search(f: fn()) {
let n = iterations();
shuttle::check_random(f, n);
shuttle::check_pct(f, n, 3);
}
macro_rules! scenario {
($name:ident, $body:ident: $($test:ident => $map:ty),+ $(,)?) => {
mod $name {
use super::*;
$(
#[test]
fn $test() {
search($body::<$map>);
}
)+
}
};
}
scenario!(claims_race_a_grow, claims_race_a_grow:
hashmap => kovan_map::HashMap<u64, u64, Fold>,
hashmap_grouped => kovan_map::HashMap<u64, u64, Grouped>,
hopscotch => kovan_map::HopscotchMap<u64, u64, Fold>,
hopscotch_clustered => kovan_map::HopscotchMap<u64, u64, Clustered>,
);
scenario!(writes_race_a_shrink, writes_race_a_shrink:
hashmap => kovan_map::HashMap<u64, u64, Fold>,
hopscotch => kovan_map::HopscotchMap<u64, u64, Fold>,
);
scenario!(adjacent_removes, adjacent_removes:
hashmap_one_chain => kovan_map::HashMap<u64, u64, Constant>,
hopscotch_one_neighborhood => kovan_map::HopscotchMap<u64, u64, Identity>,
);
scenario!(walk_races_writes_and_a_grow, walk_races_writes_and_a_grow:
hashmap => kovan_map::HashMap<u64, u64, Fold>,
hashmap_one_chain => kovan_map::HashMap<u64, u64, Constant>,
hopscotch => kovan_map::HopscotchMap<u64, u64, Fold>,
);
scenario!(clear_races_writes, clear_races_writes:
hashmap => kovan_map::HashMap<u64, u64, Fold>,
hopscotch => kovan_map::HopscotchMap<u64, u64, Fold>,
);
scenario!(writes_race_a_move, writes_race_a_move:
hashmap => kovan_map::HashMap<u64, u64, Identity>,
hopscotch => kovan_map::HopscotchMap<u64, u64, Identity>,
);
scenario!(conditional_writes_race_a_grow, conditional_writes_race_a_grow:
hashmap => kovan_map::HashMap<u64, u64, Fold>,
hashmap_grouped => kovan_map::HashMap<u64, u64, Grouped>,
hopscotch => kovan_map::HopscotchMap<u64, u64, Fold>,
hopscotch_clustered => kovan_map::HopscotchMap<u64, u64, Clustered>,
);
scenario!(compute_of_an_absent_key_races_claims_and_a_clear,
compute_of_an_absent_key_races_claims_and_a_clear:
hashmap_one_chain => kovan_map::HashMap<u64, u64, Constant>,
hopscotch_one_neighborhood => kovan_map::HopscotchMap<u64, u64, Identity>,
);
scenario!(conditional_writes_race_adjacent_removes, conditional_writes_race_adjacent_removes:
hashmap_one_chain => kovan_map::HashMap<u64, u64, Constant>,
hopscotch_one_neighborhood => kovan_map::HopscotchMap<u64, u64, Identity>,
);
scenario!(conditional_writes_race_a_move, conditional_writes_race_a_move:
hashmap => kovan_map::HashMap<u64, u64, Identity>,
hopscotch => kovan_map::HopscotchMap<u64, u64, Identity>,
);