use std::{
mem::{align_of, size_of},
sync::{
Arc,
atomic::{AtomicBool, AtomicUsize, Ordering},
},
thread,
};
use aok::{OK, Void};
use log::info;
use wepoch::{EpochEntry, Error, LightEpoch};
#[ctor::ctor(unsafe)]
fn _log_init() {
log_init::init();
}
#[test]
fn cacheline_alignment() -> Void {
info!("验证 EpochEntry 严格 64 字节 Cacheline 对齐与内存排布");
const CACHELINE_SIZE: usize = 64;
const ENTRY_COUNT: usize = 16;
assert_eq!(size_of::<EpochEntry>(), CACHELINE_SIZE);
assert_eq!(align_of::<EpochEntry>(), CACHELINE_SIZE);
assert_eq!(align_of::<LightEpoch>(), CACHELINE_SIZE);
let epoch = LightEpoch::new(ENTRY_COUNT);
assert_eq!(
&epoch as *const LightEpoch as usize % CACHELINE_SIZE,
0,
"LightEpoch 实例基址必须 64 字节对齐"
);
assert_eq!(epoch.entries.len(), ENTRY_COUNT);
for (i, entry) in epoch.entries.iter().enumerate() {
let ptr = entry as *const EpochEntry as usize;
assert_eq!(
ptr % CACHELINE_SIZE,
0,
"条目 {i} 的内存地址 {ptr:#x} 未满足 64 字节对齐"
);
}
for pair in epoch.entries.windows(2) {
let ptr1 = &pair[0] as *const EpochEntry as usize;
let ptr2 = &pair[1] as *const EpochEntry as usize;
assert_eq!(ptr2 - ptr1, CACHELINE_SIZE);
}
let line_of = |ptr: *const u8| ptr as usize / CACHELINE_SIZE;
let cur_line = line_of(&epoch.current_epoch as *const _ as *const u8);
let safe_line = line_of(&epoch.safe_to_reclaim_epoch as *const _ as *const u8);
let count_line = line_of(&epoch.drain_count as *const _ as *const u8);
let mask_line = line_of(&epoch.user_word_mask as *const _ as *const u8);
assert_ne!(
cur_line, safe_line,
"current_epoch 与 safe_to_reclaim_epoch 不得共享缓存行"
);
assert_ne!(
cur_line, count_line,
"current_epoch 与 drain_count 不得共享缓存行"
);
assert_ne!(
safe_line, count_line,
"safe_to_reclaim_epoch 与 drain_count 不得共享缓存行"
);
assert_eq!(
count_line, mask_line,
"drain_count 与 user_word_mask 应同属一行"
);
OK
}
#[test]
fn participant_capacity_and_slot_recycling() -> Void {
info!("验证参与者容量上限与 Drop 自动释放槽位复用");
let max_threads = 3;
let epoch = Arc::new(LightEpoch::new(max_threads));
let p0 = epoch.register()?;
let p1 = epoch.register()?;
let p2 = epoch.register()?;
assert_eq!(p0.entry_idx(), 0);
assert_eq!(p1.entry_idx(), 1);
assert_eq!(p2.entry_idx(), 2);
assert!(matches!(
epoch.register(),
Err(Error::ExceededMaxThreads(cap)) if cap == max_threads
));
drop(p1);
let p_new = epoch.register()?;
assert_eq!(p_new.entry_idx(), 1);
OK
}
#[test]
fn participant_refresh() -> Void {
info!("验证 Participant 会话句柄的 refresh 刷新机制");
let epoch = Arc::new(LightEpoch::new(8));
let p = epoch.register()?;
let drained = Arc::new(AtomicBool::new(false));
let drained_clone = Arc::clone(&drained);
let guard = p.enter();
assert_eq!(guard.protected_epoch(), 1);
epoch.bump_current_epoch_action(move || {
drained_clone.store(true, Ordering::SeqCst);
});
assert_eq!(p.protected_epoch(), 2);
assert!(drained.load(Ordering::Acquire));
drop(guard);
OK
}
#[test]
fn participant_long_guard_drain_list_exhaustion() -> Void {
info!("验证 Participant 长期守卫下 drain_list 满载注册不活锁");
let epoch = Arc::new(LightEpoch::new(8));
let p = epoch.register()?;
let fired = Arc::new(AtomicUsize::new(0));
const ACTIONS: usize = 2 * wepoch::DRAIN_LIST_SIZE;
let _guard = p.enter();
for _ in 0..ACTIONS {
let f = Arc::clone(&fired);
epoch.bump_current_epoch_action(move || {
f.fetch_add(1, Ordering::SeqCst);
});
}
assert_eq!(fired.load(Ordering::Acquire), ACTIONS);
OK
}
#[test]
fn mixed_tls_participant_drain_no_livelock() -> Void {
info!("验证同线程 TLS+Participant 双机制并存时注册延迟动作不活锁");
let epoch = Arc::new(LightEpoch::new(8));
let p = epoch.register()?;
let _pg = p.enter();
const ACTIONS: usize = 2 * wepoch::DRAIN_LIST_SIZE;
let fired = Arc::new(AtomicUsize::new(0));
{
let _tls = epoch.protected_scope();
for _ in 0..ACTIONS {
let f = Arc::clone(&fired);
epoch.bump_current_epoch_action(move || {
f.fetch_add(1, Ordering::SeqCst);
});
}
}
assert_eq!(fired.load(Ordering::Acquire), ACTIONS);
OK
}
#[test]
fn thread_exit_tls_cleanup() -> Void {
info!("验证线程退出未释放槽位时的 TLS 自动兜底回收");
let epoch = Arc::new(LightEpoch::new(8));
let slot_holder = Arc::new(AtomicUsize::new(0));
let ep = Arc::clone(&epoch);
let sh = Arc::clone(&slot_holder);
let handle = thread::spawn(move || {
ep.resume();
let slot = ep.test_hook_this_thread_entry();
assert_ne!(slot, 0);
sh.store(slot, Ordering::SeqCst);
});
handle.join().unwrap();
let slot = slot_holder.load(Ordering::SeqCst);
assert_ne!(slot, 0);
assert_eq!(
epoch.test_hook_announced_epoch_at(slot),
0,
"线程析构后,遗留槽位的 announced_epoch 必须已被 TLS drop 清零"
);
assert_eq!(
epoch.test_hook_thread_id_at(slot),
0,
"线程析构后,遗留槽位的 thread_id 必须已被 TLS drop 清零"
);
epoch.bump_epoch();
assert_eq!(epoch.current_epoch(), 2);
assert!(
epoch.is_safe_to_reclaim(1),
"遗留死线程已被清理,纪元 1 必须判定为可安全回收"
);
OK
}
#[test]
fn transient_epoch_instance_lifecycle_and_tls_weak_cleanup() -> Void {
info!("验证长寿线程在大量瞬态 LightEpoch 下 TLS Weak 清理与 Arc 零泄漏");
const ITERATIONS: usize = 32;
const CAPACITY: usize = 4;
for _ in 0..ITERATIONS {
let epoch = LightEpoch::new(CAPACITY);
let weak = Arc::downgrade(&epoch.entries);
assert_eq!(Arc::strong_count(&epoch.entries), 1);
epoch.resume();
assert!(epoch.this_instance_protected());
epoch.suspend();
assert!(!epoch.this_instance_protected());
drop(epoch);
assert_eq!(
weak.strong_count(),
0,
"LightEpoch 销毁后 entries 强引用必须为 0"
);
}
OK
}
#[test]
fn unified_current_thread_id_consistency() -> Void {
info!("验证 wram 与 wepoch 线程标识统一原语");
let wepoch_tid = wepoch::current_thread_id();
let wram_tid = wram::current_thread_id();
assert_eq!(
wepoch_tid, wram_tid,
"同一线程在 wepoch 与 wram 中必须获得完全相同的 thread_id"
);
assert!(wepoch_tid > 0, "thread_id 必须大于 0");
let mut handles = Vec::new();
for _ in 0..8 {
handles.push(thread::spawn(|| {
let t1 = wepoch::current_thread_id();
let t2 = wram::current_thread_id();
assert_eq!(t1, t2);
assert!(t1 > 0);
t1
}));
}
let mut ids = vec![wepoch_tid];
for h in handles {
ids.push(h.join().unwrap());
}
let total = ids.len();
ids.sort_unstable();
ids.dedup();
assert_eq!(
ids.len(),
total,
"所有并发线程获取的 thread_id 必须全局唯一"
);
OK
}