#[cfg(all(feature = "set", feature = "atomic"))]
use bstack::{BStack, BStackGenOp};
#[cfg(all(feature = "set", feature = "atomic"))]
use std::collections::HashSet;
#[cfg(all(feature = "set", feature = "atomic"))]
use std::io;
#[cfg(all(feature = "set", feature = "atomic"))]
use std::sync::Arc;
#[cfg(all(feature = "set", feature = "atomic"))]
use std::thread;
#[cfg(all(feature = "set", feature = "atomic"))]
const SENTINEL: u64 = u64::MAX;
#[cfg(all(feature = "set", feature = "atomic"))]
const FREE_HEAD_OFFSET: u64 = 0;
#[cfg(all(feature = "set", feature = "atomic"))]
const BLOCK_SIZE: u64 = 8;
#[cfg(all(feature = "set", feature = "atomic"))]
const FIRST_BLOCK: u64 = FREE_HEAD_OFFSET + BLOCK_SIZE;
#[cfg(all(feature = "set", feature = "atomic"))]
const NUM_BLOCKS: u64 = 6;
#[cfg(all(feature = "set", feature = "atomic"))]
fn main() -> io::Result<()> {
let path = "atomic_linked_list_example.bstack";
let _ = std::fs::remove_file(path);
let stack = BStack::open(path)?;
let mut payload = Vec::new();
payload.extend_from_slice(&SENTINEL.to_le_bytes());
payload.extend_from_slice(&vec![0u8; (NUM_BLOCKS * BLOCK_SIZE) as usize]);
stack.push(&payload)?;
println!("Reserved {NUM_BLOCKS} blocks of {BLOCK_SIZE} bytes each; free list starts empty.\n");
push_demo(&stack)?;
pop_demo(&stack)?;
concurrent_pop_demo(stack)?;
Ok(())
}
#[cfg(all(feature = "set", feature = "atomic"))]
fn push_demo(stack: &BStack) -> io::Result<()> {
println!("=== Push: splice onto the head with cross_exchange ===");
for i in 0..NUM_BLOCKS {
let b_addr = block_offset(i);
stack.set(b_addr, b_addr.to_le_bytes())?;
stack.cross_exchange(b_addr, FREE_HEAD_OFFSET, BLOCK_SIZE)?;
println!(" freed block {i} -> {}", free_list_string(stack)?);
}
Ok(())
}
#[cfg(all(feature = "set", feature = "atomic"))]
fn pop_demo(stack: &BStack) -> io::Result<()> {
println!("\n=== Pop: read, read, write under one lock with process_gen ===");
loop {
match pop(stack)? {
Some(b_addr) => {
println!(
" popped block {} -> {}",
block_index(b_addr),
free_list_string(stack)?
);
}
None => {
println!(" list empty -> {}", free_list_string(stack)?);
break;
}
}
}
Ok(())
}
#[cfg(all(feature = "set", feature = "atomic"))]
fn concurrent_pop_demo(stack: BStack) -> io::Result<()> {
println!("\n=== Concurrent pop: {NUM_BLOCKS} threads race process_gen, no mutex ===");
for i in 0..NUM_BLOCKS {
let b_addr = block_offset(i);
stack.set(b_addr, b_addr.to_le_bytes())?;
stack.cross_exchange(b_addr, FREE_HEAD_OFFSET, BLOCK_SIZE)?;
}
println!(" rebuilt list -> {}", free_list_string(&stack)?);
let stack = Arc::new(stack);
let handles: Vec<_> = (0..NUM_BLOCKS)
.map(|_| {
let stack = Arc::clone(&stack);
thread::spawn(move || pop(&stack))
})
.collect();
let mut popped = Vec::new();
for handle in handles {
if let Some(b_addr) = handle.join().unwrap()? {
popped.push(b_addr);
}
}
let mut seen = HashSet::new();
for &b_addr in &popped {
assert!(
seen.insert(b_addr),
"block at offset {b_addr} was popped more than once — ABA corruption!"
);
}
println!(
" {} threads popped {} distinct blocks — no duplicates, nothing lost",
NUM_BLOCKS,
popped.len()
);
println!(" final state -> {}", free_list_string(&stack)?);
Ok(())
}
#[cfg(all(feature = "set", feature = "atomic"))]
fn pop(stack: &BStack) -> io::Result<Option<u64>> {
let mut head_buf = [0u8; 8];
let mut next_buf = [0u8; 8];
let mut step = 0usize;
let mut popped: Option<u64> = None;
stack.process_gen(|| {
let op = match step {
0 => Some(BStackGenOp::Read {
offset: FREE_HEAD_OFFSET,
buf: unsafe { core::mem::transmute::<&mut [u8], _>(&mut head_buf[..]) },
}),
1 => {
let head = u64::from_le_bytes(head_buf);
if head == SENTINEL {
None
} else {
popped = Some(head);
Some(BStackGenOp::Read {
offset: head,
buf: unsafe { core::mem::transmute::<&mut [u8], _>(&mut next_buf[..]) },
})
}
}
2 => Some(BStackGenOp::Write {
offset: FREE_HEAD_OFFSET,
data: unsafe { core::mem::transmute::<&[u8], _>(&next_buf[..]) },
}),
_ => None,
};
step += 1;
op
})?;
Ok(popped)
}
#[cfg(all(feature = "set", feature = "atomic"))]
fn block_offset(i: u64) -> u64 {
FIRST_BLOCK + i * BLOCK_SIZE
}
#[cfg(all(feature = "set", feature = "atomic"))]
fn block_index(b_addr: u64) -> u64 {
(b_addr - FIRST_BLOCK) / BLOCK_SIZE
}
#[cfg(all(feature = "set", feature = "atomic"))]
fn free_list_string(stack: &BStack) -> io::Result<String> {
let mut out = String::from("head");
let mut offset = read_u64(stack, FREE_HEAD_OFFSET)?;
while offset != SENTINEL {
out.push_str(&format!(" -> B{}", block_index(offset)));
offset = read_u64(stack, offset)?;
}
out.push_str(" -> sentinel");
Ok(out)
}
#[cfg(all(feature = "set", feature = "atomic"))]
fn read_u64(stack: &BStack, offset: u64) -> io::Result<u64> {
let mut buf = [0u8; 8];
stack.peek_into(offset, &mut buf)?;
Ok(u64::from_le_bytes(buf))
}
#[cfg(not(all(feature = "set", feature = "atomic")))]
fn main() {
eprintln!("This example requires the `set` and `atomic` features.");
eprintln!("Run: cargo run --example atomic_linked_list --features \"set,atomic\"");
}