#![expect(clippy::expect_used, reason = "fail fast with meaningful reason")]
use super::*;
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::thread;
use std::time::Duration;
#[test]
fn test_concurrent_lock_unlock() {
let version = Arc::new(NodeVersion::new(true));
let iterations = 1000;
let num_threads = 4;
let handles: Vec<_> = (0..num_threads)
.map(|_| {
let v = Arc::clone(&version);
thread::spawn(move || {
for _ in 0..iterations {
let mut guard = v.lock();
guard.mark_insert();
}
})
})
.collect();
for h in handles {
h.join().expect("thread panicked");
}
assert!(!version.is_locked());
assert!(!version.is_dirty());
}
#[test]
fn test_stable_spins_on_dirty() {
use std::sync::atomic::AtomicBool;
let version = Arc::new(NodeVersion::new(true));
let writer_done = Arc::new(AtomicBool::new(false));
let v_writer = Arc::clone(&version);
let done = Arc::clone(&writer_done);
let writer = thread::spawn(move || {
let mut guard = v_writer.lock();
guard.mark_insert();
thread::sleep(Duration::from_millis(50));
drop(guard);
done.store(true, Ordering::Release);
});
thread::sleep(Duration::from_millis(10));
let v_reader = Arc::clone(&version);
let reader = thread::spawn(move || {
let stable_v = v_reader.stable();
assert_eq!(stable_v & DIRTY_MASK, 0);
});
writer.join().expect("writer panicked");
reader.join().expect("reader panicked");
}
#[test]
fn test_try_lock_for_timeout() {
let version = Arc::new(NodeVersion::new(true));
let v = Arc::clone(&version);
let holder = thread::spawn(move || {
let guard = v.lock();
thread::sleep(Duration::from_millis(200));
drop(guard);
});
thread::sleep(Duration::from_millis(10));
let result = version.try_lock_for(Duration::from_millis(50));
assert!(result.is_none(), "should have timed out");
holder.join().expect("holder panicked");
assert!(version.try_lock().is_some());
}
#[test]
#[expect(clippy::panic, reason = "Intentional panic")]
fn test_guard_unlocks_on_panic() {
let version = Arc::new(NodeVersion::new(true));
let v = Arc::clone(&version);
let handle = thread::spawn(move || {
let mut guard = v.lock();
guard.mark_insert();
panic!("intentional panic");
});
let result = handle.join();
assert!(result.is_err(), "should have panicked");
assert!(!version.is_locked());
let _guard = version.lock();
assert!(version.is_locked());
}
#[test]
fn test_version_increment_under_contention() {
let version = Arc::new(NodeVersion::new(true));
let iterations = 100;
let num_threads = 4;
let initial = version.stable();
let handles: Vec<_> = (0..num_threads)
.map(|_| {
let v = Arc::clone(&version);
thread::spawn(move || {
for _ in 0..iterations {
let mut guard = v.lock();
guard.mark_insert();
}
})
})
.collect();
for h in handles {
h.join().expect("thread panicked");
}
assert!(version.has_changed(initial));
}
#[test]
fn test_try_lock_under_contention() {
let version = Arc::new(NodeVersion::new(true));
let success_count = Arc::new(AtomicUsize::new(0));
let num_threads = 8;
let handles: Vec<_> = (0..num_threads)
.map(|_| {
let v = Arc::clone(&version);
let count = Arc::clone(&success_count);
thread::spawn(move || {
for _ in 0..100 {
if let Some(mut guard) = v.try_lock() {
count.fetch_add(1, Ordering::Relaxed);
guard.mark_insert();
}
std::hint::spin_loop();
}
})
})
.collect();
for h in handles {
h.join().expect("thread panicked");
}
let successes: usize = success_count.load(Ordering::Relaxed);
assert!(successes > 0, "no try_lock succeeded");
assert!(version.has_changed(ISLEAF_BIT));
}