#![allow(unused_unsafe)]
use kovan::{Atomic, RetiredNode, pin, retire};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;
#[repr(C)]
struct TestNode {
retired: RetiredNode,
value: usize,
freed: Arc<AtomicBool>,
}
impl TestNode {
fn new(value: usize, freed: Arc<AtomicBool>) -> *mut Self {
Box::into_raw(Box::new(Self {
retired: RetiredNode::new(),
value,
freed,
}))
}
}
impl Drop for TestNode {
fn drop(&mut self) {
self.freed.store(true, Ordering::Release);
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_no_premature_free() {
let freed = Arc::new(AtomicBool::new(false));
let atomic = Arc::new(Atomic::new(TestNode::new(42, freed.clone())));
let started = Arc::new(AtomicBool::new(false));
let can_retire = Arc::new(AtomicBool::new(false));
let atomic1 = atomic.clone();
let freed1 = freed.clone();
let started1 = started.clone();
let can_retire1 = can_retire.clone();
let handle1 = thread::spawn(move || {
let guard = pin();
let ptr = atomic1.load(Ordering::Acquire, &guard);
if let Some(node) = unsafe { ptr.as_ref() } {
assert_eq!(node.value, 42);
started1.store(true, Ordering::Release);
while !can_retire1.load(Ordering::Acquire) {
thread::sleep(Duration::from_millis(10));
}
assert!(!freed1.load(Ordering::Acquire), "Node freed prematurely!");
assert_eq!(node.value, 42);
}
});
let atomic2 = atomic.clone();
let started2 = started.clone();
let can_retire2 = can_retire.clone();
let handle2 = thread::spawn(move || {
while !started2.load(Ordering::Acquire) {
thread::sleep(Duration::from_millis(10));
}
let guard = pin();
let old = atomic2.swap(
unsafe { kovan::Shared::from_raw(std::ptr::null_mut()) },
Ordering::Release,
&guard,
);
if !old.is_null() {
unsafe {
retire(old.as_raw());
}
}
for i in 0..70 {
let dummy = TestNode::new(i, Arc::new(AtomicBool::new(false)));
unsafe {
retire(dummy);
}
}
can_retire2.store(true, Ordering::Release);
});
handle2.join().unwrap();
handle1.join().unwrap();
println!("No premature free test: PASS - node remained accessible while guard held");
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_eventual_reclamation() {
const NUM_NODES: usize = 10000;
let atomic = Arc::new(Atomic::new(std::ptr::null_mut::<TestNode>()));
for i in 0..NUM_NODES {
let freed = Arc::new(AtomicBool::new(false));
let node = Box::into_raw(Box::new(TestNode {
retired: RetiredNode::new(),
value: i,
freed,
}));
let guard = pin();
let old = atomic.swap(
unsafe { kovan::Shared::from_raw(node) },
Ordering::Release,
&guard,
);
if !old.is_null() {
unsafe {
retire(old.as_raw());
}
}
if i % 100 == 0 {
for _ in 0..10 {
let _guard = pin();
}
}
}
{
let guard = pin();
let old = atomic.swap(
unsafe { kovan::Shared::from_raw(std::ptr::null_mut()) },
Ordering::Release,
&guard,
);
if !old.is_null() {
unsafe {
retire(old.as_raw());
}
}
}
for _ in 0..1000 {
let _guard = pin();
}
println!(
"Eventual reclamation test: retired {} nodes successfully",
NUM_NODES
);
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_concurrent_access() {
const NUM_THREADS: usize = 8;
const ITERATIONS: usize = 10000;
let atomic = Arc::new(Atomic::new(TestNode::new(
0,
Arc::new(AtomicBool::new(false)),
)));
let mut handles = vec![];
for _ in 0..NUM_THREADS / 2 {
let atomic = atomic.clone();
handles.push(thread::spawn(move || {
for _ in 0..ITERATIONS {
let guard = pin();
let ptr = atomic.load(Ordering::Acquire, &guard);
if let Some(node) = unsafe { ptr.as_ref() } {
let _ = node.value;
}
}
}));
}
for tid in 0..NUM_THREADS / 2 {
let atomic = atomic.clone();
handles.push(thread::spawn(move || {
for i in 0..ITERATIONS {
let new_node =
TestNode::new(tid * ITERATIONS + i, Arc::new(AtomicBool::new(false)));
let guard = pin();
let old = atomic.swap(
unsafe { kovan::Shared::from_raw(new_node) },
Ordering::Release,
&guard,
);
if !old.is_null() {
unsafe {
retire(old.as_raw());
}
}
}
}));
}
for handle in handles {
handle.join().unwrap();
}
let guard = pin();
let old = atomic.swap(
unsafe { kovan::Shared::from_raw(std::ptr::null_mut()) },
Ordering::Release,
&guard,
);
if !old.is_null() {
unsafe {
retire(old.as_raw());
}
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_guard_drop_triggers_reclamation() {
const NUM_RETIRES: usize = 1000;
let atomic = Atomic::new(std::ptr::null_mut::<TestNode>());
let handle = thread::spawn(move || {
for i in 0..NUM_RETIRES {
let node = TestNode::new(i, Arc::new(AtomicBool::new(false)));
let guard = pin();
let old = atomic.swap(
unsafe { kovan::Shared::from_raw(node) },
Ordering::Release,
&guard,
);
if !old.is_null() {
unsafe {
retire(old.as_raw());
}
}
}
let guard = pin();
let old = atomic.swap(
unsafe { kovan::Shared::from_raw(std::ptr::null_mut()) },
Ordering::Release,
&guard,
);
if !old.is_null() {
unsafe {
retire(old.as_raw());
}
}
});
handle.join().unwrap();
for _ in 0..500 {
let _guard = pin();
}
println!(
"Guard drop triggers reclamation test: PASS - {} nodes retired successfully",
NUM_RETIRES
);
}