use std::{
process,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
mpsc,
},
thread,
time::{Duration, Instant},
};
use aok::{OK, Result, Void};
use compio::runtime::Runtime;
use log::info;
use tempfile::tempdir;
use wdev::SegmentedDevice;
use wepoch::LightEpoch;
use whlog::{Error, HybridLog, HybridLogConfig, SECTOR_ALIGNMENT};
async fn setup_truncate_fixture(
dir: &tempfile::TempDir,
tag: &str,
n_records: usize,
) -> Result<(
Arc<HybridLog<SegmentedDevice>>,
Arc<LightEpoch>,
Vec<(u64, Vec<u8>)>,
)> {
let device = Arc::new(SegmentedDevice::single_file(dir.path().join(tag))?);
let epoch = Arc::new(LightEpoch::new(32));
let config = HybridLogConfig::new(SECTOR_ALIGNMENT, 8, 0.5)?;
let hlog = Arc::new(HybridLog::new(config, device, epoch.clone())?);
let mut addrs = Vec::with_capacity(n_records);
for i in 0..n_records {
let key = format!("k{i:03}").into_bytes();
let addr = hlog.append(&key, &[b'v'; 24], 0, false)?;
addrs.push((addr, key));
}
hlog.flush_all().await?;
hlog.sync().await?;
Ok((hlog, epoch, addrs))
}
fn spawn_shift_watchdog(
label: &'static str,
done: Arc<AtomicBool>,
hlog: Arc<HybridLog<SegmentedDevice>>,
deadline: Duration,
) -> thread::JoinHandle<()> {
thread::spawn(move || {
let start = Instant::now();
let mut reported = false;
while !done.load(Ordering::Acquire) {
thread::sleep(Duration::from_millis(100));
let elapsed = start.elapsed();
if elapsed > deadline && !reported {
reported = true;
eprintln!(
"[看门狗:{label}] 超过 {deadline:?} 未完成,转储诊断: snapshot={:?} epoch={:?}",
hlog.addresses.snapshot(),
hlog.epoch
);
}
if elapsed > deadline + Duration::from_secs(2) {
eprintln!("[看门狗:{label}] grace 期满仍未完成,判定挂死,abort 转储全部线程栈");
process::abort();
}
}
})
}
#[test]
fn test_shift_begin_truncation_drain_barrier() -> Void {
use std::sync::{Condvar, Mutex};
#[derive(Debug, PartialEq)]
enum Event {
Dropped,
Done,
}
let rt = Runtime::new()?;
rt.block_on(async {
let dir = tempdir()?;
let (hlog, epoch, addrs) = setup_truncate_fixture(&dir, "barrier.db", 128).await?;
let (ready_tx, ready_rx) = mpsc::channel::<()>();
let (go_tx, go_rx) = mpsc::channel::<()>();
let (sampled_tx, sampled_rx) = mpsc::channel::<()>();
let log: Arc<(Mutex<Vec<Event>>, Condvar)> = Arc::new((Mutex::new(Vec::new()), Condvar::new()));
let hlog_r = Arc::clone(&hlog);
let epoch_r = Arc::clone(&epoch);
let log_r = Arc::clone(&log);
let addr_r = addrs[16].0;
let key_r = addrs[16].1.clone();
let reader = thread::spawn(move || -> aok::Result<()> {
let rt = Runtime::new()?;
rt.block_on(async move {
{
let _guard = epoch_r.protected_scope();
let _ = ready_tx.send(());
if go_rx.recv().is_err() {
return Err(aok::Error::msg("shifter 已退出,握手断裂"));
}
if !hlog_r.is_on_disk(addr_r) {
return Err(aok::Error::msg(format!(
"采样点必须在截断线推进前: addr_r={addr_r:#x} snapshot={:?}",
hlog_r.addresses.snapshot()
)));
}
let _ = sampled_tx.send(());
let read_res = async {
for i in 0..64 {
match hlog_r.read_disk_record(addr_r).await {
Ok(out) => {
let key = out
.key()
.map_err(|e| aok::Error::msg(format!("第{i}次冷读键损坏: {e}")))?;
if key != key_r.as_slice() {
return Err(aok::Error::msg(format!("第{i}次冷读撕裂: 键不匹配")));
}
}
Err(Error::PageNotReady(_)) => break,
Err(e) => return Err(aok::Error::msg(format!("第{i}次冷读设备级撕裂: {e}"))),
}
}
Ok(())
}
.await;
log_r.0.lock().unwrap().push(Event::Dropped);
log_r.1.notify_all();
read_res
}
})
});
let hlog_s = Arc::clone(&hlog);
let log_s = Arc::clone(&log);
let cut = addrs[32].0;
let head_at = addrs[96].0;
let shifter = thread::spawn(move || -> aok::Result<()> {
let rt = Runtime::new()?;
rt.block_on(async move {
if ready_rx.recv().is_err() {
return Err(aok::Error::msg("reader 已退出,握手断裂"));
}
hlog_s.shift_head_address(head_at);
let _ = go_tx.send(());
if sampled_rx.recv().is_err() {
return Err(aok::Error::msg("reader 采样失败,握手断裂"));
}
hlog_s.shift_begin_address(cut).await?;
log_s.0.lock().unwrap().push(Event::Done);
log_s.1.notify_all();
Ok(())
})
});
let reader_res = reader.join().unwrap();
shifter.join().unwrap()?;
{
let (m, cv) = &*log;
let events = cv.wait_while(m.lock().unwrap(), |v| v.len() < 2).unwrap();
assert_eq!(
*events,
vec![Event::Dropped, Event::Done],
"截断屏障失效:截断先于读者守卫退出完成"
);
}
reader_res?;
assert_eq!(hlog.begin_address(), cut);
assert!(hlog.safe_read_only_address() >= cut);
match hlog.read_disk_record(addr_r).await {
Err(Error::PageNotReady(_)) => {}
other => panic!("截断线以下冷读应干净返回 PageNotReady,实际: {other:?}"),
}
assert!(hlog.addresses.validate_invariants());
info!("截断前纪元排空屏障测试通过");
aok::Result::<()>::Ok(())
})?;
OK
}
#[test]
fn test_shift_begin_under_caller_epoch_guard() -> Void {
let rt = Runtime::new()?;
rt.block_on(async {
let dir = tempdir()?;
let (hlog, epoch, addrs) = setup_truncate_fixture(&dir, "guard.db", 32).await?;
{
let _guard = epoch.protected_scope();
hlog.shift_begin_address(addrs[8].0).await?;
}
assert_eq!(hlog.begin_address(), addrs[8].0);
assert!(hlog.with_memory_record(addrs[16].0, |_| Ok(()))?.is_some());
let participant = epoch.register()?;
{
let _guard = participant.enter();
hlog.shift_begin_address(addrs[16].0).await?;
}
assert_eq!(hlog.begin_address(), addrs[16].0);
assert!(hlog.safe_read_only_address() >= addrs[16].0);
assert!(hlog.addresses.validate_invariants());
info!("调用方持守卫截断自动解钉测试通过");
aok::Result::<()>::Ok(())
})?;
OK
}
#[test]
fn test_shift_begin_preflushed_barrier_liveness() -> Void {
let rt = Runtime::new()?;
rt.block_on(async {
let dir = tempdir()?;
let (hlog, _epoch, addrs) = setup_truncate_fixture(&dir, "preflush.db", 16).await?;
hlog.shift_begin_address(addrs[8].0).await?;
assert_eq!(hlog.begin_address(), addrs[8].0);
assert!(hlog.safe_read_only_address() >= addrs[8].0);
assert!(hlog.head_address() >= addrs[8].0);
match hlog.read_record(addrs[4].0).await {
Err(Error::AddressOutOfRange { .. }) => {}
other => panic!("截断线以下读取应干净返回 AddressOutOfRange,实际: {other:?}"),
}
assert!(hlog.addresses.validate_invariants());
info!("预刷盘直入截断屏障活性测试通过");
aok::Result::<()>::Ok(())
})?;
OK
}
#[test]
fn test_concurrent_disk_read_vs_shift_begin() -> Void {
let rt = Runtime::new()?;
rt.block_on(async {
let dir = tempdir()?;
let (hlog, epoch, addrs) = setup_truncate_fixture(&dir, "race.db", 256).await?;
let addrs = Arc::new(addrs);
let n = addrs.len() as u64;
hlog.shift_head_address(addrs[192].0);
let done = Arc::new(AtomicBool::new(false));
let watchdog = spawn_shift_watchdog(
"read-vs-shift",
Arc::clone(&done),
Arc::clone(&hlog),
Duration::from_secs(20),
);
let hlog_r = Arc::clone(&hlog);
let epoch_r = Arc::clone(&epoch);
let done_r = Arc::clone(&done);
let addrs_r = Arc::clone(&addrs);
let reader = thread::spawn(move || -> aok::Result<()> {
let rt = Runtime::new()?;
rt.block_on(async move {
let mut i = 0u64;
let mut reads = 0u64;
while !done_r.load(Ordering::Acquire) {
i = (i + 7) % n;
let (addr, key) = (&addrs_r[i as usize].0, &addrs_r[i as usize].1);
let _guard = epoch_r.protected_scope();
match hlog_r.read_record(*addr).await {
Ok(out) => {
reads += 1;
assert_eq!(
out.key()?,
key.as_slice(),
"在途读者读到撕裂记录: addr={addr:#x}"
);
}
Err(Error::AddressOutOfRange { .. } | Error::PageNotReady(_)) => {}
Err(e) => {
return Err(aok::Error::msg(format!(
"在途读者观察到底层撕裂: addr={addr:#x} err={e}"
)));
}
}
}
info!("读者完成 {reads} 次有效冷读");
Ok(())
})
});
let final_begin = addrs[128].0;
let hlog_s = Arc::clone(&hlog);
let addrs_s = Arc::clone(&addrs);
let shifter = thread::spawn(move || -> aok::Result<()> {
let rt = Runtime::new()?;
rt.block_on(async move {
for round in 1..=8 {
let cut = addrs_s[round * 16].0;
hlog_s.shift_begin_address(cut).await?;
}
Ok(())
})
});
let shift_res = shifter.join().unwrap();
done.store(true, Ordering::Release);
let reader_res = reader.join().unwrap();
shift_res?;
reader_res?;
watchdog.join().unwrap();
assert_eq!(hlog.begin_address(), final_begin);
assert!(hlog.addresses.validate_invariants());
info!("磁盘区在途读者与并发截断压力测试通过");
aok::Result::<()>::Ok(())
})?;
OK
}