use std::ptr;
use std::sync::atomic::{AtomicPtr, AtomicU64, Ordering};
use super::error::SwizzleError;
const SWIZZLE_FLAG: u64 = 1 << 63;
const MEMORY_STATE: u64 = SWIZZLE_FLAG;
const INSTALLING_MEMORY_STATE: u64 = SWIZZLE_FLAG | 1;
const EVICTING_MEMORY_STATE: u64 = SWIZZLE_FLAG | 2;
const BLOCK_ID_SHIFT: u32 = 40;
const OFFSET_SHIFT: u32 = 18;
const BLOCK_ID_BITS: u32 = 23;
const OFFSET_BITS: u32 = 22;
const FLAGS_BITS: u32 = 18;
const BLOCK_ID_MASK: u64 = (1 << BLOCK_ID_BITS) - 1; const OFFSET_MASK: u64 = (1 << OFFSET_BITS) - 1; const FLAGS_MASK: u64 = (1 << FLAGS_BITS) - 1;
pub const MAX_BLOCK_ID: u32 = (1 << BLOCK_ID_BITS) - 1;
pub const MAX_OFFSET: u32 = (1 << OFFSET_BITS) - 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum NodeType {
Node4 = 4,
Node16 = 16,
Node48 = 48,
Node256 = 0, Bucket = 1,
CharNode4 = 104,
CharNode16 = 116,
CharNode48 = 148,
CharBucket = 101,
}
impl NodeType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
4 => Some(NodeType::Node4),
16 => Some(NodeType::Node16),
48 => Some(NodeType::Node48),
0 => Some(NodeType::Node256),
1 => Some(NodeType::Bucket),
104 => Some(NodeType::CharNode4),
116 => Some(NodeType::CharNode16),
148 => Some(NodeType::CharNode48),
101 => Some(NodeType::CharBucket),
_ => None,
}
}
#[inline]
pub fn is_byte_level(&self) -> bool {
matches!(
self,
NodeType::Node4
| NodeType::Node16
| NodeType::Node48
| NodeType::Node256
| NodeType::Bucket
)
}
#[inline]
pub fn is_char_level(&self) -> bool {
matches!(
self,
NodeType::CharNode4
| NodeType::CharNode16
| NodeType::CharNode48
| NodeType::CharBucket
)
}
}
impl TryFrom<u8> for NodeType {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
NodeType::from_u8(value).ok_or(())
}
}
#[derive(Debug)]
pub struct SwizzledPtr {
state: AtomicU64,
memory_ptr: AtomicPtr<()>,
}
impl SwizzledPtr {
pub const fn null() -> Self {
Self {
state: AtomicU64::new(0),
memory_ptr: AtomicPtr::new(ptr::null_mut()),
}
}
pub fn on_disk(block_id: u32, offset: u32, node_type: NodeType) -> Self {
debug_assert!(
block_id <= MAX_BLOCK_ID,
"block_id {} exceeds maximum {}",
block_id,
MAX_BLOCK_ID
);
debug_assert!(
offset <= MAX_OFFSET,
"offset {} exceeds maximum {}",
offset,
MAX_OFFSET
);
let encoded = ((block_id as u64 & BLOCK_ID_MASK) << BLOCK_ID_SHIFT)
| ((offset as u64 & OFFSET_MASK) << OFFSET_SHIFT)
| (node_type as u64 & FLAGS_MASK);
debug_assert!(
encoded & SWIZZLE_FLAG == 0,
"disk reference must not have swizzle flag set"
);
Self {
state: AtomicU64::new(encoded),
memory_ptr: AtomicPtr::new(ptr::null_mut()),
}
}
pub fn in_memory<T>(ptr: *const T) -> Self {
assert!(!ptr.is_null(), "memory pointer must not be null");
Self {
state: AtomicU64::new(MEMORY_STATE),
memory_ptr: AtomicPtr::new(ptr.cast_mut().cast::<()>()),
}
}
#[inline]
pub fn is_null(&self) -> bool {
self.state.load(Ordering::Acquire) == 0
}
#[inline]
pub fn is_swizzled(&self) -> bool {
self.state.load(Ordering::Acquire) == MEMORY_STATE
&& !self.memory_ptr.load(Ordering::Acquire).is_null()
}
#[inline]
pub fn is_on_disk(&self) -> bool {
let val = self.state.load(Ordering::Acquire);
val != 0 && val & SWIZZLE_FLAG == 0
}
#[inline]
pub unsafe fn as_ptr_unchecked<T>(&self) -> *const T {
debug_assert!(
self.state.load(Ordering::Acquire) == MEMORY_STATE,
"pointer is not swizzled"
);
let ptr = self.memory_ptr.load(Ordering::Acquire);
debug_assert!(!ptr.is_null(), "memory pointer is missing");
ptr.cast::<T>()
}
#[inline]
pub fn as_ptr<T>(&self) -> Option<*const T> {
if self.state.load(Ordering::Acquire) != MEMORY_STATE {
return None;
}
let ptr = self.memory_ptr.load(Ordering::Acquire);
(!ptr.is_null()).then_some(ptr.cast::<T>())
}
pub fn disk_location(&self) -> Option<DiskLocation> {
let val = self.state.load(Ordering::Acquire);
if val == 0 || val & SWIZZLE_FLAG != 0 {
return None;
}
let block_id = ((val >> BLOCK_ID_SHIFT) & BLOCK_ID_MASK) as u32;
let offset = ((val >> OFFSET_SHIFT) & OFFSET_MASK) as u32;
let type_byte = (val & FLAGS_MASK) as u8;
let node_type = NodeType::from_u8(type_byte)?;
Some(DiskLocation {
block_id,
offset,
node_type,
})
}
pub fn swizzle<T>(&self, ptr: *const T) -> Result<(), SwizzleError> {
assert!(!ptr.is_null(), "memory pointer must not be null");
let old = self.state.load(Ordering::Acquire);
if old == MEMORY_STATE {
return Err(SwizzleError::AlreadySwizzled);
}
if old & SWIZZLE_FLAG != 0 {
return Err(SwizzleError::RaceCondition);
}
if old == 0 {
return Err(SwizzleError::AlreadyUnswizzled);
}
self.state
.compare_exchange(
old,
INSTALLING_MEMORY_STATE,
Ordering::AcqRel,
Ordering::Acquire,
)
.map_err(|_| SwizzleError::RaceCondition)?;
self.memory_ptr
.store(ptr.cast_mut().cast::<()>(), Ordering::Release);
self.state.store(MEMORY_STATE, Ordering::Release);
Ok(())
}
pub fn unswizzle<T>(
&self,
block_id: u32,
offset: u32,
node_type: NodeType,
) -> Result<*const T, SwizzleError> {
if block_id > MAX_BLOCK_ID {
return Err(SwizzleError::BlockIdOverflow { block_id });
}
if offset > MAX_OFFSET {
return Err(SwizzleError::OffsetOverflow { offset });
}
let old = self.state.load(Ordering::Acquire);
if old & SWIZZLE_FLAG == 0 {
return Err(SwizzleError::AlreadyUnswizzled);
}
if old != MEMORY_STATE {
return Err(SwizzleError::RaceCondition);
}
let new = ((block_id as u64 & BLOCK_ID_MASK) << BLOCK_ID_SHIFT)
| ((offset as u64 & OFFSET_MASK) << OFFSET_SHIFT)
| (node_type as u64 & FLAGS_MASK);
self.state
.compare_exchange(
MEMORY_STATE,
EVICTING_MEMORY_STATE,
Ordering::AcqRel,
Ordering::Acquire,
)
.map_err(|_| SwizzleError::RaceCondition)?;
let old_ptr = self.memory_ptr.load(Ordering::Acquire);
if old_ptr.is_null() {
self.state.store(MEMORY_STATE, Ordering::Release);
return Err(SwizzleError::RaceCondition);
}
self.memory_ptr.store(ptr::null_mut(), Ordering::Release);
self.state.store(new, Ordering::Release);
Ok(old_ptr.cast::<T>())
}
pub fn to_raw(&self) -> u64 {
self.state.load(Ordering::Acquire)
}
pub fn from_raw(raw: u64) -> Self {
Self {
state: AtomicU64::new(raw),
memory_ptr: AtomicPtr::new(ptr::null_mut()),
}
}
pub fn as_arena_slot(&self) -> Option<super::arena_slot::ArenaSlot> {
let loc = self.disk_location()?;
let arena_id = loc.block_id.checked_sub(1)?;
Some(super::arena_slot::ArenaSlot::new(arena_id, loc.offset))
}
pub fn from_arena_slot(slot: super::arena_slot::ArenaSlot, node_type: NodeType) -> Self {
let block_id = slot.arena_id.saturating_add(1);
Self::on_disk(block_id, slot.slot_id, node_type)
}
#[inline]
pub fn load_raw(&self) -> u64 {
self.state.load(Ordering::Acquire)
}
#[inline]
pub fn store_raw(&self, value: u64) {
self.memory_ptr.store(ptr::null_mut(), Ordering::Release);
self.state.store(value, Ordering::Release)
}
#[inline]
pub fn compare_exchange_raw(&self, expected: u64, new: u64) -> Result<u64, u64> {
if expected & SWIZZLE_FLAG != 0 || new & SWIZZLE_FLAG != 0 {
return Err(self.load_raw());
}
self.state
.compare_exchange(expected, new, Ordering::AcqRel, Ordering::Acquire)
}
#[inline]
pub fn compare_exchange_weak_raw(&self, expected: u64, new: u64) -> Result<u64, u64> {
if expected & SWIZZLE_FLAG != 0 || new & SWIZZLE_FLAG != 0 {
return Err(self.load_raw());
}
self.state
.compare_exchange_weak(expected, new, Ordering::AcqRel, Ordering::Acquire)
}
#[inline]
pub fn try_insert_child(&self, new_child: &SwizzledPtr) -> Result<(), u64> {
let new_raw = new_child.load_raw();
if new_raw == MEMORY_STATE {
let new_ptr = new_child.memory_ptr.load(Ordering::Acquire);
if new_ptr.is_null() {
return Err(self.load_raw());
}
return match self.state.compare_exchange(
0,
INSTALLING_MEMORY_STATE,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
self.memory_ptr.store(new_ptr, Ordering::Release);
self.state.store(MEMORY_STATE, Ordering::Release);
Ok(())
}
Err(actual) => Err(actual),
};
}
if new_raw & SWIZZLE_FLAG != 0 {
return Err(self.load_raw());
}
match self.compare_exchange_raw(0, new_raw) {
Ok(_) => Ok(()),
Err(actual) => Err(actual),
}
}
#[inline]
pub fn try_update_child(
&self,
expected_child: &SwizzledPtr,
new_child: &SwizzledPtr,
) -> Result<(), u64> {
let expected_raw = expected_child.load_raw();
let new_raw = new_child.load_raw();
let expected_ptr = expected_child.memory_ptr.load(Ordering::Acquire);
let new_ptr = new_child.memory_ptr.load(Ordering::Acquire);
if (expected_raw & SWIZZLE_FLAG != 0 && expected_raw != MEMORY_STATE)
|| (new_raw & SWIZZLE_FLAG != 0 && new_raw != MEMORY_STATE)
{
return Err(self.load_raw());
}
match (expected_raw == MEMORY_STATE, new_raw == MEMORY_STATE) {
(false, false) => match self.compare_exchange_raw(expected_raw, new_raw) {
Ok(_) => {
self.memory_ptr.store(ptr::null_mut(), Ordering::Release);
Ok(())
}
Err(actual) => Err(actual),
},
(false, true) => {
if new_ptr.is_null() {
return Err(self.load_raw());
}
match self.state.compare_exchange(
expected_raw,
INSTALLING_MEMORY_STATE,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
self.memory_ptr.store(new_ptr, Ordering::Release);
self.state.store(MEMORY_STATE, Ordering::Release);
Ok(())
}
Err(actual) => Err(actual),
}
}
(true, false) => {
if expected_ptr.is_null() {
return Err(self.load_raw());
}
if self.state.load(Ordering::Acquire) != MEMORY_STATE
|| self.memory_ptr.load(Ordering::Acquire) != expected_ptr
{
return Err(self.load_raw());
}
match self.state.compare_exchange(
MEMORY_STATE,
EVICTING_MEMORY_STATE,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
let actual_ptr = self.memory_ptr.load(Ordering::Acquire);
if actual_ptr != expected_ptr {
self.state.store(MEMORY_STATE, Ordering::Release);
return Err(MEMORY_STATE);
}
self.memory_ptr.store(ptr::null_mut(), Ordering::Release);
self.state.store(new_raw, Ordering::Release);
Ok(())
}
Err(actual) => Err(actual),
}
}
(true, true) => {
if expected_ptr.is_null() || new_ptr.is_null() {
return Err(self.load_raw());
}
if self.state.load(Ordering::Acquire) != MEMORY_STATE
|| self.memory_ptr.load(Ordering::Acquire) != expected_ptr
{
return Err(self.load_raw());
}
match self.state.compare_exchange(
MEMORY_STATE,
INSTALLING_MEMORY_STATE,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
let actual_ptr = self.memory_ptr.load(Ordering::Acquire);
if actual_ptr != expected_ptr {
self.state.store(MEMORY_STATE, Ordering::Release);
return Err(MEMORY_STATE);
}
self.memory_ptr.store(new_ptr, Ordering::Release);
self.state.store(MEMORY_STATE, Ordering::Release);
Ok(())
}
Err(actual) => Err(actual),
}
}
}
}
#[inline]
pub fn is_null_relaxed(&self) -> bool {
self.state.load(Ordering::Relaxed) == 0
}
}
impl Clone for SwizzledPtr {
fn clone(&self) -> Self {
Self {
state: AtomicU64::new(self.state.load(Ordering::Acquire)),
memory_ptr: AtomicPtr::new(self.memory_ptr.load(Ordering::Acquire)),
}
}
}
impl Default for SwizzledPtr {
fn default() -> Self {
Self::null()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DiskLocation {
pub block_id: u32,
pub offset: u32,
pub node_type: NodeType,
}
impl DiskLocation {
pub fn file_offset(&self, block_size: usize) -> u64 {
(self.block_id as u64 * block_size as u64) + self.offset as u64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_null_pointer() {
let ptr = SwizzledPtr::null();
assert!(ptr.is_null());
assert!(!ptr.is_swizzled());
assert!(!ptr.is_on_disk());
}
#[test]
fn test_disk_reference() {
let ptr = SwizzledPtr::on_disk(1234, 5678, NodeType::Node16);
assert!(!ptr.is_null());
assert!(!ptr.is_swizzled());
assert!(ptr.is_on_disk());
let loc = ptr.disk_location().expect("should have disk location");
assert_eq!(loc.block_id, 1234);
assert_eq!(loc.offset, 5678);
assert_eq!(loc.node_type, NodeType::Node16);
}
#[test]
fn test_memory_pointer() {
let data: u64 = 42;
let ptr = SwizzledPtr::in_memory(&data);
assert!(!ptr.is_null());
assert!(ptr.is_swizzled());
assert!(!ptr.is_on_disk());
let retrieved: *const u64 = ptr.as_ptr().expect("should have memory pointer");
assert_eq!(unsafe { *retrieved }, 42);
}
#[test]
fn test_memory_raw_value_does_not_reconstruct_provenance() {
let data: u64 = 42;
let ptr = SwizzledPtr::in_memory(&data);
let raw = ptr.to_raw();
assert_eq!(raw, MEMORY_STATE);
let restored = SwizzledPtr::from_raw(raw);
assert!(!restored.is_null());
assert!(!restored.is_on_disk());
assert!(!restored.is_swizzled());
assert_eq!(restored.as_ptr::<u64>(), None);
}
#[test]
fn test_swizzle() {
let ptr = SwizzledPtr::on_disk(100, 200, NodeType::Node4);
assert!(ptr.is_on_disk());
let data: u64 = 12345;
ptr.swizzle(&data).expect("swizzle should succeed");
assert!(ptr.is_swizzled());
let retrieved: *const u64 = ptr.as_ptr().expect("should have memory pointer");
assert_eq!(unsafe { *retrieved }, 12345);
}
#[test]
fn test_double_swizzle_fails() {
let ptr = SwizzledPtr::on_disk(100, 200, NodeType::Node4);
let data: u64 = 42;
ptr.swizzle(&data).expect("first swizzle should succeed");
let result = ptr.swizzle(&data);
assert_eq!(result, Err(SwizzleError::AlreadySwizzled));
}
#[test]
fn test_unswizzle() {
let data: u64 = 42;
let ptr = SwizzledPtr::in_memory(&data);
assert!(ptr.is_swizzled());
let old_ptr: *const u64 = ptr
.unswizzle(500, 600, NodeType::Bucket)
.expect("unswizzle should succeed");
assert!(ptr.is_on_disk());
assert_eq!(unsafe { *old_ptr }, 42);
let loc = ptr.disk_location().expect("should have disk location");
assert_eq!(loc.block_id, 500);
assert_eq!(loc.offset, 600);
assert_eq!(loc.node_type, NodeType::Bucket);
}
#[test]
fn test_max_values() {
let ptr = SwizzledPtr::on_disk(MAX_BLOCK_ID, MAX_OFFSET, NodeType::Node256);
let loc = ptr.disk_location().expect("should have disk location");
assert_eq!(loc.block_id, MAX_BLOCK_ID);
assert_eq!(loc.offset, MAX_OFFSET);
}
#[test]
fn test_file_offset_calculation() {
let loc = DiskLocation {
block_id: 10,
offset: 1024,
node_type: NodeType::Node16,
};
let block_size = 256 * 1024;
let expected = 10 * block_size as u64 + 1024;
assert_eq!(loc.file_offset(block_size), expected);
}
#[test]
fn test_node_type_roundtrip() {
for node_type in [
NodeType::Node4,
NodeType::Node16,
NodeType::Node48,
NodeType::Node256,
NodeType::Bucket,
] {
let ptr = SwizzledPtr::on_disk(123, 456, node_type);
let loc = ptr.disk_location().expect("should decode");
assert_eq!(loc.node_type, node_type);
}
}
mod arena_slot_tests {
use super::*;
use crate::persistent_artrie_core::arena_slot::ArenaSlot;
#[test]
fn test_from_arena_slot() {
let slot = ArenaSlot::new(0, 100);
let ptr = SwizzledPtr::from_arena_slot(slot, NodeType::Node4);
assert!(ptr.is_on_disk());
let loc = ptr.disk_location().expect("should have disk location");
assert_eq!(loc.block_id, 1); assert_eq!(loc.offset, 100);
assert_eq!(loc.node_type, NodeType::Node4);
}
#[test]
fn test_as_arena_slot() {
let ptr = SwizzledPtr::on_disk(5, 200, NodeType::Node16);
let slot = ptr.as_arena_slot().expect("should convert to arena slot");
assert_eq!(slot.arena_id, 4); assert_eq!(slot.slot_id, 200);
}
#[test]
fn test_arena_slot_roundtrip() {
let original = ArenaSlot::new(42, 12345);
let ptr = SwizzledPtr::from_arena_slot(original, NodeType::Node48);
let recovered = ptr.as_arena_slot().expect("should convert back");
assert_eq!(recovered.arena_id, original.arena_id);
assert_eq!(recovered.slot_id, original.slot_id);
}
#[test]
fn test_as_arena_slot_returns_none_for_memory() {
let data: u64 = 42;
let ptr = SwizzledPtr::in_memory(&data);
assert!(ptr.as_arena_slot().is_none());
}
#[test]
fn test_as_arena_slot_returns_none_for_null() {
let ptr = SwizzledPtr::null();
assert!(ptr.as_arena_slot().is_none());
}
#[test]
fn test_as_arena_slot_returns_none_for_block_zero() {
let ptr = SwizzledPtr::on_disk(0, 100, NodeType::Node4);
assert!(ptr.as_arena_slot().is_none());
}
}
mod cas_tests {
use super::*;
use std::sync::atomic::AtomicUsize;
use std::sync::{Arc, Barrier};
use std::thread;
#[test]
fn test_load_store_raw() {
let ptr = SwizzledPtr::null();
assert_eq!(ptr.load_raw(), 0);
let child = SwizzledPtr::on_disk(1, 100, NodeType::Node4);
ptr.store_raw(child.load_raw());
assert_eq!(ptr.load_raw(), child.load_raw());
assert!(ptr.is_on_disk());
}
#[test]
fn test_compare_exchange_raw_success() {
let ptr = SwizzledPtr::null();
let new_child = SwizzledPtr::on_disk(1, 100, NodeType::Node4);
let result = ptr.compare_exchange_raw(0, new_child.load_raw());
assert!(result.is_ok());
assert_eq!(result.unwrap(), 0);
assert_eq!(ptr.load_raw(), new_child.load_raw());
}
#[test]
fn test_compare_exchange_raw_failure() {
let ptr = SwizzledPtr::on_disk(1, 100, NodeType::Node4);
let new_child = SwizzledPtr::on_disk(2, 200, NodeType::Node16);
let result = ptr.compare_exchange_raw(0, new_child.load_raw());
assert!(result.is_err());
let actual = result.unwrap_err();
assert_eq!(actual, ptr.load_raw());
}
#[test]
fn test_try_insert_child_success() {
let slot = SwizzledPtr::null();
let child = SwizzledPtr::on_disk(1, 100, NodeType::Node4);
let result = slot.try_insert_child(&child);
assert!(result.is_ok());
assert_eq!(slot.load_raw(), child.load_raw());
}
#[test]
fn test_try_insert_memory_child_preserves_pointer() {
let slot = SwizzledPtr::null();
let child_data = 123_u64;
let child = SwizzledPtr::in_memory(&child_data);
let result = slot.try_insert_child(&child);
assert!(result.is_ok());
assert!(slot.is_swizzled());
assert_eq!(slot.as_ptr::<u64>(), Some(&child_data as *const u64));
}
#[test]
fn test_try_insert_child_failure() {
let existing = SwizzledPtr::on_disk(1, 100, NodeType::Node4);
let slot = SwizzledPtr::from_raw(existing.load_raw());
let new_child = SwizzledPtr::on_disk(2, 200, NodeType::Node16);
let result = slot.try_insert_child(&new_child);
assert!(result.is_err());
assert_eq!(slot.load_raw(), existing.load_raw());
}
#[test]
fn test_try_update_child_success() {
let old_child = SwizzledPtr::on_disk(1, 100, NodeType::Node4);
let slot = SwizzledPtr::from_raw(old_child.load_raw());
let new_child = SwizzledPtr::on_disk(2, 200, NodeType::Node16);
let result = slot.try_update_child(&old_child, &new_child);
assert!(result.is_ok());
assert_eq!(slot.load_raw(), new_child.load_raw());
}
#[test]
fn test_try_update_memory_child_checks_pointer_identity() {
let old_data = 10_u64;
let wrong_old_data = 11_u64;
let new_data = 12_u64;
let old_child = SwizzledPtr::in_memory(&old_data);
let wrong_old_child = SwizzledPtr::in_memory(&wrong_old_data);
let slot = old_child.clone();
let new_child = SwizzledPtr::in_memory(&new_data);
assert!(slot.try_update_child(&wrong_old_child, &new_child).is_err());
assert_eq!(slot.as_ptr::<u64>(), Some(&old_data as *const u64));
assert!(slot.try_update_child(&old_child, &new_child).is_ok());
assert_eq!(slot.as_ptr::<u64>(), Some(&new_data as *const u64));
}
#[test]
fn test_concurrent_memory_child_update_has_single_expected_pointer_winner() {
let values = Arc::new([10_u64, 20, 30, 40, 50, 60, 70, 80]);
let slot = Arc::new(SwizzledPtr::in_memory(&values[0]));
let expected = Arc::new(SwizzledPtr::in_memory(&values[0]));
let successes = Arc::new(AtomicUsize::new(0));
let num_threads = values.len() - 1;
let barrier = Arc::new(Barrier::new(num_threads));
let handles: Vec<_> = (1..values.len())
.map(|i| {
let values = Arc::clone(&values);
let slot = Arc::clone(&slot);
let expected = Arc::clone(&expected);
let successes = Arc::clone(&successes);
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
let new_child = SwizzledPtr::in_memory(&values[i]);
barrier.wait();
if slot.try_update_child(&expected, &new_child).is_ok() {
successes.fetch_add(1, Ordering::SeqCst);
}
})
})
.collect();
for handle in handles {
handle.join().expect("thread should complete");
}
assert_eq!(
successes.load(Ordering::SeqCst),
1,
"only one stale expected memory pointer update may publish"
);
let final_ptr = slot.as_ptr::<u64>().expect("slot should remain in memory");
assert!((1..values.len()).any(|i| final_ptr == &values[i] as *const u64));
}
#[test]
fn test_try_update_memory_child_to_disk_clears_pointer() {
let old_data = 10_u64;
let old_child = SwizzledPtr::in_memory(&old_data);
let slot = old_child.clone();
let disk_child = SwizzledPtr::on_disk(4, 512, NodeType::Node16);
assert!(slot.try_update_child(&old_child, &disk_child).is_ok());
assert_eq!(slot.as_ptr::<u64>(), None);
assert!(slot.is_on_disk());
assert_eq!(slot.disk_location(), disk_child.disk_location());
}
#[test]
fn test_try_update_child_failure() {
let old_child = SwizzledPtr::on_disk(1, 100, NodeType::Node4);
let actual_child = SwizzledPtr::on_disk(3, 300, NodeType::Node48);
let slot = SwizzledPtr::from_raw(actual_child.load_raw());
let new_child = SwizzledPtr::on_disk(2, 200, NodeType::Node16);
let result = slot.try_update_child(&old_child, &new_child);
assert!(result.is_err());
assert_eq!(slot.load_raw(), actual_child.load_raw());
}
#[test]
fn test_is_null_relaxed() {
let null_ptr = SwizzledPtr::null();
assert!(null_ptr.is_null_relaxed());
let non_null = SwizzledPtr::on_disk(1, 100, NodeType::Node4);
assert!(!non_null.is_null_relaxed());
}
#[test]
fn test_concurrent_try_insert_child() {
let slot = Arc::new(SwizzledPtr::null());
let num_threads = 10;
let handles: Vec<_> = (0..num_threads)
.map(|i| {
let s = Arc::clone(&slot);
thread::spawn(move || {
let child = SwizzledPtr::on_disk(1, i as u32, NodeType::Node4);
s.try_insert_child(&child).is_ok()
})
})
.collect();
let results: Vec<bool> = handles
.into_iter()
.map(|h| h.join().expect("thread should complete"))
.collect();
let winners = results.iter().filter(|&&x| x).count();
assert_eq!(winners, 1, "exactly one thread should win try_insert_child");
assert!(!slot.is_null());
}
#[test]
fn test_concurrent_compare_exchange() {
let ptr = Arc::new(SwizzledPtr::null());
let num_threads = 20;
let handles: Vec<_> = (0..num_threads)
.map(|i| {
let p = Arc::clone(&ptr);
thread::spawn(move || {
let new_val = SwizzledPtr::on_disk(1, i as u32, NodeType::Node4);
p.compare_exchange_raw(0, new_val.load_raw()).is_ok()
})
})
.collect();
let results: Vec<bool> = handles
.into_iter()
.map(|h| h.join().expect("thread should complete"))
.collect();
let winners = results.iter().filter(|&&x| x).count();
assert_eq!(winners, 1, "exactly one thread should win CAS");
let final_val = ptr.load_raw();
assert_ne!(final_val, 0, "final value should not be null");
let disk_ptr = SwizzledPtr::from_raw(final_val);
assert!(disk_ptr.is_on_disk());
}
}
}