// Adapted from: https://github.com/jonhoo/flurry/blob/main/tests/cuckoo/stress.rs use papaya::{HashMap, ResizeMode}; use rand::distributions::{Distribution, Uniform}; use std::sync::atomic::Ordering; use std::sync::Mutex; use std::sync::{atomic::AtomicBool, Arc}; use std::thread; #[cfg(not(miri))] mod cfg { /// Number of keys and values to work with. pub const NUM_KEYS: usize = 1 << 14; /// Number of threads that should be started. pub const NUM_THREADS: usize = 4; /// How long the stress test will run (in milliseconds). pub const TEST_LEN: u64 = 10_000; } #[cfg(miri)] mod cfg { /// Number of keys and values to work with. pub const NUM_KEYS: usize = 1 << 10; /// Number of threads that should be started. pub const NUM_THREADS: usize = 4; /// How long the stress test will run (in milliseconds). pub const TEST_LEN: u64 = 5000; } type Key = usize; type Value = usize; struct Environment { table1: HashMap, table2: HashMap, keys: Vec, vals1: Mutex>, vals2: Mutex>, ind_dist: Uniform, val_dist1: Uniform, val_dist2: Uniform, in_table: Mutex>, in_use: Mutex>, finished: AtomicBool, } impl Environment { pub fn new() -> Self { let mut keys = Vec::with_capacity(cfg::NUM_KEYS); let mut in_use = Vec::with_capacity(cfg::NUM_KEYS); for i in 0..cfg::NUM_KEYS { keys.push(i); in_use.push(AtomicBool::new(false)); } Self { table1: HashMap::new(), table2: HashMap::new(), keys, vals1: Mutex::new(vec![0usize; cfg::NUM_KEYS]), vals2: Mutex::new(vec![0usize; cfg::NUM_KEYS]), ind_dist: Uniform::from(0..cfg::NUM_KEYS - 1), val_dist1: Uniform::from(Value::min_value()..Value::max_value()), val_dist2: Uniform::from(Value::min_value()..Value::max_value()), in_table: Mutex::new(vec![false; cfg::NUM_KEYS]), in_use: Mutex::new(in_use), finished: AtomicBool::new(false), } } } fn stress_insert_thread(env: Arc) { let mut rng = rand::thread_rng(); let guard1 = env.table1.guard(); let guard2 = env.table2.guard(); while !env.finished.load(Ordering::SeqCst) { let idx = env.ind_dist.sample(&mut rng); let in_use = env.in_use.lock().unwrap(); if (*in_use)[idx] .compare_exchange(false, true, Ordering::SeqCst, Ordering::Relaxed) .is_ok() { let key = env.keys[idx]; let val1 = env.val_dist1.sample(&mut rng); let val2 = env.val_dist2.sample(&mut rng); let res1 = if !env.table1.contains_key(&key, &guard1) { env.table1 .insert(key, val1, &guard1) .map_or(true, |_| false) } else { false }; let res2 = if !env.table2.contains_key(&key, &guard2) { env.table2 .insert(key, val2, &guard2) .map_or(true, |_| false) } else { false }; let mut in_table = env.in_table.lock().unwrap(); assert_ne!(res1, (*in_table)[idx]); assert_ne!(res2, (*in_table)[idx]); if res1 { assert_eq!(Some(&val1), env.table1.get(&key, &guard1)); assert_eq!(Some(&val2), env.table2.get(&key, &guard2)); let mut vals1 = env.vals1.lock().unwrap(); let mut vals2 = env.vals2.lock().unwrap(); (*vals1)[idx] = val1; (*vals2)[idx] = val2; (*in_table)[idx] = true; } (*in_use)[idx].swap(false, Ordering::SeqCst); } } } fn stress_delete_thread(env: Arc) { let mut rng = rand::thread_rng(); let guard1 = env.table1.guard(); let guard2 = env.table2.guard(); while !env.finished.load(Ordering::SeqCst) { let idx = env.ind_dist.sample(&mut rng); let in_use = env.in_use.lock().unwrap(); if (*in_use)[idx] .compare_exchange(false, true, Ordering::SeqCst, Ordering::Relaxed) .is_ok() { let key = env.keys[idx]; let res1 = env.table1.remove(&key, &guard1).map_or(false, |_| true); let res2 = env.table2.remove(&key, &guard2).map_or(false, |_| true); let mut in_table = env.in_table.lock().unwrap(); assert_eq!(res1, (*in_table)[idx]); assert_eq!(res2, (*in_table)[idx]); if res1 { assert!(env.table1.get(&key, &guard1).is_none()); assert!(env.table2.get(&key, &guard2).is_none()); (*in_table)[idx] = false; } (*in_use)[idx].swap(false, Ordering::SeqCst); } } } fn stress_find_thread(env: Arc) { let mut rng = rand::thread_rng(); let guard1 = env.table1.guard(); let guard2 = env.table2.guard(); while !env.finished.load(Ordering::SeqCst) { let idx = env.ind_dist.sample(&mut rng); let in_use = env.in_use.lock().unwrap(); if (*in_use)[idx] .compare_exchange(false, true, Ordering::SeqCst, Ordering::Relaxed) .is_ok() { let key = env.keys[idx]; let in_table = env.in_table.lock().unwrap(); let val1 = (*env.vals1.lock().unwrap())[idx]; let val2 = (*env.vals2.lock().unwrap())[idx]; let value = env.table1.get(&key, &guard1); if value.is_some() { assert_eq!(&val1, value.unwrap()); assert!((*in_table)[idx]); } let value = env.table2.get(&key, &guard2); if value.is_some() { assert_eq!(&val2, value.unwrap()); assert!((*in_table)[idx]); } (*in_use)[idx].swap(false, Ordering::SeqCst); } } } #[test] #[ignore] #[cfg(not(papaya_stress))] fn stress_test_blocking() { let mut root = Environment::new(); root.table1 = HashMap::builder().resize_mode(ResizeMode::Blocking).build(); root.table2 = HashMap::builder().resize_mode(ResizeMode::Blocking).build(); run(Arc::new(root)); } #[test] #[ignore] fn stress_test_incremental() { let mut root = Environment::new(); root.table1 = HashMap::builder() .resize_mode(ResizeMode::Incremental(1024)) .build(); root.table2 = HashMap::builder() .resize_mode(ResizeMode::Incremental(1024)) .build(); run(Arc::new(root)); } #[test] #[ignore] fn stress_test_incremental_slow() { let mut root = Environment::new(); root.table1 = HashMap::builder() .resize_mode(ResizeMode::Incremental(1)) .build(); root.table2 = HashMap::builder() .resize_mode(ResizeMode::Incremental(1)) .build(); run(Arc::new(root)); } fn run(root: Arc) { let mut threads = Vec::new(); for _ in 0..cfg::NUM_THREADS { let env = Arc::clone(&root); threads.push(thread::spawn(move || stress_insert_thread(env))); let env = Arc::clone(&root); threads.push(thread::spawn(move || stress_delete_thread(env))); let env = Arc::clone(&root); threads.push(thread::spawn(move || stress_find_thread(env))); } thread::sleep(std::time::Duration::from_millis(cfg::TEST_LEN)); root.finished.swap(true, Ordering::SeqCst); for t in threads { t.join().expect("failed to join thread"); } if !cfg!(papaya_stress) { let in_table = &*root.in_table.lock().unwrap(); let num_filled = in_table.iter().filter(|b| **b).count(); assert_eq!(num_filled, root.table1.pin().len()); assert_eq!(num_filled, root.table2.pin().len()); } }