use dualcache_ff::{Config, DualCacheFF};
use std::sync::Arc;
#[cfg(feature = "loom")]
use loom::thread;
#[cfg(not(feature = "loom"))]
use std::thread;
#[cfg(feature = "loom")]
use loom::sync::atomic::{AtomicUsize, Ordering};
#[cfg(not(feature = "loom"))]
use std::sync::atomic::{AtomicUsize, Ordering};
#[cfg(feature = "loom")]
use loom::sync::Mutex;
#[cfg(not(feature = "loom"))]
use std::sync::Mutex;
use std::collections::HashMap;
use crate::common::run_with_timeout;
#[cfg(feature = "loom")]
macro_rules! test_runner {
($name:ident, $body:expr) => {
#[test]
fn $name() {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(2);
builder.check(|| {
$body
});
}
};
}
#[cfg(not(feature = "loom"))]
macro_rules! test_runner {
($name:ident, $body:expr) => {
#[test]
fn $name() {
run_with_timeout(std::time::Duration::from_secs(10), || {
$body
});
}
};
}
test_runner!(test_concurrent_ops, {
let config = Config::new_expert(128, 64, 64, 200, 128);
let cache = DualCacheFF::new(config);
let ops = Arc::new(AtomicUsize::new(0));
let shadow = Arc::new(Mutex::new(HashMap::new()));
let mut handles = vec![];
for i in 0..2 {
let c = cache.clone();
let ops_clone = ops.clone();
let shadow_clone = shadow.clone();
let handle = thread::spawn(move || {
let offset = i * 100;
c.insert(offset, offset);
shadow_clone.lock().unwrap().insert(offset, offset);
c.insert(offset + 1, offset + 1);
shadow_clone.lock().unwrap().insert(offset + 1, offset + 1);
let _ = c.get(&offset);
c.remove(&(offset + 1));
shadow_clone.lock().unwrap().remove(&(offset + 1));
ops_clone.fetch_add(4, Ordering::Relaxed);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(ops.load(Ordering::Relaxed), 8);
#[cfg(not(feature = "loom"))]
{
cache.sync();
let shadow_map = shadow.lock().unwrap();
let mut found = 0;
for k in shadow_map.keys() {
if let Some(actual_v) = cache.get(k) {
let expected_v = shadow_map.get(k).unwrap();
assert_eq!(*expected_v, actual_v);
found += 1;
}
}
assert!(found > 0, "Vacuous assertion: zero keys found");
}
});
#[cfg(not(feature = "loom"))]
#[test]
fn test_ttl_mechanic() {
run_with_timeout(std::time::Duration::from_secs(10), || {
use std::time::Duration;
let config = Config::new_expert(128, 64, 64, 1, 128);
let cache = DualCacheFF::new(config);
for _ in 0..64 {
cache.insert(1, 100);
}
cache.sync();
assert_eq!(cache.get(&1), Some(100));
let mut expired = false;
for _ in 0..25 {
if cache.get(&1).is_none() {
expired = true;
break;
}
thread::sleep(Duration::from_millis(100));
}
assert!(expired, "TTL Mechanic failed: item did not expire within 2.5 seconds");
});
}
test_runner!(test_concurrent_insert_t1, {
let config = Config::new_expert(128, 64, 64, 200, 128);
let cache = DualCacheFF::new(config);
let ops = Arc::new(AtomicUsize::new(0));
let mut handles = vec![];
for i in 0..2 {
let c = cache.clone();
let ops_clone = ops.clone();
let handle = thread::spawn(move || {
let offset = i * 100;
let session = c.begin_cold_start_session();
session.warmup(offset, offset);
session.warmup(offset + 1, offset + 1);
let _ = c.get(&offset);
ops_clone.fetch_add(2, Ordering::Relaxed);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(ops.load(Ordering::Relaxed), 4);
#[cfg(not(feature = "loom"))]
{
cache.sync();
assert_eq!(cache.get(&0), Some(0));
assert_eq!(cache.get(&1), Some(1));
assert_eq!(cache.get(&100), Some(100));
assert_eq!(cache.get(&101), Some(101));
}
});