use std::{
env,
process::Command,
sync::{Arc, Barrier},
thread,
time::{Duration, Instant},
};
use dashmap::{mapref::entry::Entry, DashMap};
const CHILD_ENV: &str = "PI_STORE_DASHMAP_COMPAT_CHILD";
const CHILD_TIMEOUT: Duration = Duration::from_secs(30);
const KEY_COUNT: usize = 1_024;
const WRITE_ROUNDS: usize = 32;
const READER_COUNT: usize = 2;
const WRITER_COUNT: usize = 2;
#[test]
fn test_dashmap_6_2_compatibility() {
if env::var_os(CHILD_ENV).is_some() {
run_bounded_concurrent_api_matrix();
return;
}
let executable = env::current_exe().expect("无法取得当前 DashMap 专项测试程序路径");
let mut child = Command::new(executable)
.arg("--exact")
.arg("test_dashmap_6_2_compatibility")
.arg("--nocapture")
.arg("--test-threads=1")
.env(CHILD_ENV, "1")
.spawn()
.expect("无法启动隔离的 DashMap 兼容测试子进程");
let deadline = Instant::now() + CHILD_TIMEOUT;
loop {
if let Some(status) = child.try_wait().expect("无法查询 DashMap 测试子进程状态")
{
assert!(status.success(), "DashMap 兼容测试子进程失败: {status}");
break;
}
if Instant::now() >= deadline {
let _ = child.kill();
let _ = child.wait();
panic!("DashMap 兼容测试超过 {CHILD_TIMEOUT:?},疑似发生持续阻塞或死锁");
}
thread::sleep(Duration::from_millis(10));
}
}
fn run_bounded_concurrent_api_matrix() {
let map = Arc::new(DashMap::<usize, usize>::new());
for key in 0..KEY_COUNT {
assert_eq!(map.insert(key, 0), None);
}
assert_eq!(map.len(), KEY_COUNT);
let start = Arc::new(Barrier::new(WRITER_COUNT + READER_COUNT + 1));
let mut joins = Vec::with_capacity(WRITER_COUNT + READER_COUNT);
for writer_id in 0..WRITER_COUNT {
let map = map.clone();
let start = start.clone();
joins.push(thread::spawn(move || {
start.wait();
for round in 0..WRITE_ROUNDS {
for key in (writer_id..KEY_COUNT).step_by(WRITER_COUNT) {
if round % 2 == 0 {
match map.entry(key) {
Entry::Occupied(mut occupied) => *occupied.get_mut() += 1,
Entry::Vacant(_) => panic!("初始化后的键不应在写入期间消失: {key}"),
}
} else {
*map.get_mut(&key).expect("初始化后的键必须存在") += 1;
}
}
}
}));
}
for reader_id in 0..READER_COUNT {
let map = map.clone();
let start = start.clone();
joins.push(thread::spawn(move || {
const WINDOW: usize = 64;
start.wait();
for round in 0..WRITE_ROUNDS {
let begin = (round * 31 + reader_id * 17) % (KEY_COUNT - WINDOW);
let guards: Vec<_> = (begin..begin + WINDOW)
.map(|key| map.get(&key).expect("并发只更新值,不得删除键"))
.collect();
assert!(guards.iter().all(|guard| **guard <= WRITE_ROUNDS));
assert_eq!(map.len(), KEY_COUNT);
}
}));
}
start.wait();
for join in joins {
join.join().expect("DashMap 受控并发工作线程发生 panic");
}
for key in 0..KEY_COUNT {
assert_eq!(*map.get(&key).expect("写入结束后键必须存在"), WRITE_ROUNDS);
}
for key in 0..32 {
match map.entry(key) {
Entry::Occupied(mut occupied) => *occupied.get_mut() += 1,
Entry::Vacant(_) => panic!("既有键必须进入 Occupied 分支"),
}
match map.entry(KEY_COUNT + key) {
Entry::Vacant(vacant) => {
vacant.insert(key);
}
Entry::Occupied(_) => panic!("新键必须进入 Vacant 分支"),
}
}
assert_eq!(map.len(), KEY_COUNT + 32);
assert!((0..KEY_COUNT + 32).all(|key| map.contains_key(&key)));
let iterated = map.iter().count();
assert_eq!(iterated, KEY_COUNT + 32);
for key in 0..32 {
assert_eq!(map.remove(&(KEY_COUNT + key)), Some((KEY_COUNT + key, key)));
}
assert_eq!(map.len(), KEY_COUNT);
}