use std::ops::{Deref, DerefMut, Range};
#[cfg(unix)]
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex, MutexGuard};
use fsqlite_error::{FrankenError, Result};
#[cfg(unix)]
struct MmapBacking {
ptr: *mut u8,
len: usize,
mutex: Mutex<()>,
}
#[cfg(unix)]
impl Drop for MmapBacking {
fn drop(&mut self) {
if !self.ptr.is_null() && self.len > 0 {
unsafe {
libc::munmap(self.ptr.cast::<libc::c_void>(), self.len);
}
}
}
}
#[cfg(unix)]
unsafe impl Send for MmapBacking {}
#[cfg(unix)]
unsafe impl Sync for MmapBacking {}
enum ShmRegionBacking {
Heap(Arc<Mutex<Vec<u8>>>),
#[cfg(unix)]
Mmap(Arc<MmapBacking>),
}
impl std::fmt::Debug for ShmRegionBacking {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Heap(v) => f
.debug_tuple("Heap")
.field(&format_args!(
"Vec<u8>[{}]",
v.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.len()
))
.finish(),
#[cfg(unix)]
Self::Mmap(m) => f
.debug_tuple("Mmap")
.field(&format_args!("ptr={:?}, len={}", m.ptr, m.len))
.finish(),
}
}
}
impl Clone for ShmRegionBacking {
fn clone(&self) -> Self {
match self {
Self::Heap(v) => {
let guard = v.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
Self::Heap(Arc::new(Mutex::new(guard.clone())))
}
#[cfg(unix)]
Self::Mmap(m) => Self::Mmap(Arc::clone(m)),
}
}
}
pub const SQLITE_SHM_UNLOCK: u32 = 0x01;
pub const SQLITE_SHM_LOCK: u32 = 0x02;
pub const SQLITE_SHM_SHARED: u32 = 0x04;
pub const SQLITE_SHM_EXCLUSIVE: u32 = 0x08;
pub const WAL_WRITE_LOCK: u32 = 0;
pub const WAL_CKPT_LOCK: u32 = 1;
pub const WAL_RECOVER_LOCK: u32 = 2;
pub const WAL_READ_LOCK_BASE: u32 = 3;
pub const WAL_NREADER: u32 = 5;
pub const WAL_NREADER_USIZE: usize = 5;
pub const WAL_TOTAL_LOCKS: u32 = WAL_READ_LOCK_BASE + WAL_NREADER;
pub const SHM_SEGMENT_SIZE: u32 = 32 * 1024;
pub const SHM_READ_MARK_OFFSET: usize = 100;
const SQLITE_SHM_LOCK_BASE: u64 = 120;
#[must_use]
pub const fn wal_read_lock_slot(index: u32) -> Option<u32> {
if index < WAL_NREADER {
Some(WAL_READ_LOCK_BASE + index)
} else {
None
}
}
#[must_use]
pub const fn wal_lock_byte(slot: u32) -> Option<u64> {
if slot < WAL_TOTAL_LOCKS {
Some(SQLITE_SHM_LOCK_BASE + slot as u64)
} else {
None
}
}
#[derive(Debug, Clone)]
pub struct ShmRegion {
len: usize,
backing: ShmRegionBacking,
}
impl ShmRegion {
#[must_use]
pub fn new(size: usize) -> Self {
Self {
len: size,
backing: ShmRegionBacking::Heap(Arc::new(Mutex::new(vec![0; size]))),
}
}
#[must_use]
pub fn from_vec(data: Vec<u8>) -> Self {
let len = data.len();
Self {
len,
backing: ShmRegionBacking::Heap(Arc::new(Mutex::new(data))),
}
}
#[cfg(unix)]
pub unsafe fn from_mmap(ptr: *mut u8, len: usize) -> Self {
Self {
len,
backing: ShmRegionBacking::Mmap(Arc::new(MmapBacking {
ptr,
len,
mutex: Mutex::new(()),
})),
}
}
#[must_use]
pub fn share(&self) -> Self {
Self {
len: self.len,
backing: match &self.backing {
ShmRegionBacking::Heap(v) => ShmRegionBacking::Heap(Arc::clone(v)),
#[cfg(unix)]
ShmRegionBacking::Mmap(m) => ShmRegionBacking::Mmap(Arc::clone(m)),
},
}
}
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn lock(&self) -> ShmRegionGuard<'_> {
match &self.backing {
ShmRegionBacking::Heap(data) => ShmRegionGuard {
inner: ShmRegionGuardInner::Heap {
guard: data
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
visible_len: self.len,
},
},
#[cfg(unix)]
ShmRegionBacking::Mmap(m) => ShmRegionGuard {
inner: ShmRegionGuardInner::Mmap {
ptr: m.ptr,
visible_len: self.len.min(m.len),
_guard: m
.mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
_backing: m,
},
},
}
}
pub fn read_u32_le(&self, offset: usize) -> Result<u32> {
let bytes: [u8; 4] = {
let guard = self.lock();
let range = self.checked_range(offset, 4, guard.len(), "SHM u32 LE read")?;
guard[range].try_into().expect("slice is exactly 4 bytes")
};
Ok(u32::from_le_bytes(bytes))
}
pub fn write_u32_le(&self, offset: usize, val: u32) -> Result<()> {
let mut guard = self.lock();
let range = self.checked_range(offset, 4, guard.len(), "SHM u32 LE write")?;
guard[range].copy_from_slice(&val.to_le_bytes());
Ok(())
}
pub fn read_u32_ne(&self, offset: usize) -> Result<u32> {
let bytes: [u8; 4] = {
let guard = self.lock();
let range = self.checked_range(offset, 4, guard.len(), "SHM u32 native-endian read")?;
guard[range].try_into().expect("slice is exactly 4 bytes")
};
Ok(u32::from_ne_bytes(bytes))
}
pub fn write_u32_ne(&self, offset: usize, val: u32) -> Result<()> {
let mut guard = self.lock();
let range = self.checked_range(offset, 4, guard.len(), "SHM u32 native-endian write")?;
guard[range].copy_from_slice(&val.to_ne_bytes());
Ok(())
}
pub fn read_u64_le(&self, offset: usize) -> Result<u64> {
let bytes: [u8; 8] = {
let guard = self.lock();
let range = self.checked_range(offset, 8, guard.len(), "SHM u64 LE read")?;
guard[range].try_into().expect("slice is exactly 8 bytes")
};
Ok(u64::from_le_bytes(bytes))
}
pub fn write_u64_le(&self, offset: usize, val: u64) -> Result<()> {
let mut guard = self.lock();
let range = self.checked_range(offset, 8, guard.len(), "SHM u64 LE write")?;
guard[range].copy_from_slice(&val.to_le_bytes());
Ok(())
}
pub fn atomic_load_u64_le(&self, offset: usize, ordering: Ordering) -> Result<u64> {
#[cfg(not(unix))]
let _ = ordering;
match &self.backing {
ShmRegionBacking::Heap(data) => {
let guard = data
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let range = self.checked_aligned_u64_offset(offset, guard.len())?;
let bytes: [u8; 8] = guard[range].try_into().expect("slice is exactly 8 bytes");
Ok(u64::from_le_bytes(bytes))
}
#[cfg(unix)]
ShmRegionBacking::Mmap(m) => {
self.checked_aligned_u64_offset(offset, m.len)?;
let _guard = m
.mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let raw = unsafe { atomic_u64_at(m.ptr, offset) }.load(ordering);
Ok(u64::from_le(raw))
}
}
}
pub fn atomic_store_u64_le(&self, offset: usize, val: u64, ordering: Ordering) -> Result<()> {
#[cfg(not(unix))]
let _ = ordering;
match &self.backing {
ShmRegionBacking::Heap(data) => {
let mut guard = data
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let range = self.checked_aligned_u64_offset(offset, guard.len())?;
guard[range].copy_from_slice(&val.to_le_bytes());
Ok(())
}
#[cfg(unix)]
ShmRegionBacking::Mmap(m) => {
self.checked_aligned_u64_offset(offset, m.len)?;
let _guard = m
.mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
unsafe { atomic_u64_at(m.ptr, offset) }.store(val.to_le(), ordering);
Ok(())
}
}
}
pub fn atomic_fetch_add_u64_le(
&self,
offset: usize,
delta: u64,
ordering: Ordering,
) -> Result<u64> {
#[cfg(not(unix))]
let _ = ordering;
match &self.backing {
ShmRegionBacking::Heap(data) => {
let mut guard = data
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let range = self.checked_aligned_u64_offset(offset, guard.len())?;
let current = u64::from_le_bytes(
guard[range.clone()]
.try_into()
.expect("slice is exactly 8 bytes"),
);
let next = current.wrapping_add(delta);
guard[range].copy_from_slice(&next.to_le_bytes());
Ok(current)
}
#[cfg(unix)]
ShmRegionBacking::Mmap(m) => {
self.checked_aligned_u64_offset(offset, m.len)?;
let _guard = m
.mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
loop {
let atom = unsafe { atomic_u64_at(m.ptr, offset) };
let current = u64::from_le(atom.load(Ordering::Acquire));
let next = current.wrapping_add(delta);
if atom
.compare_exchange(
current.to_le(),
next.to_le(),
ordering,
Ordering::Acquire,
)
.is_ok()
{
return Ok(current);
}
}
}
}
}
pub fn atomic_compare_exchange_u64_le(
&self,
offset: usize,
current: u64,
new: u64,
success: Ordering,
failure: Ordering,
) -> Result<std::result::Result<u64, u64>> {
#[cfg(not(unix))]
let _ = (success, failure);
match &self.backing {
ShmRegionBacking::Heap(data) => {
let mut guard = data
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let range = self.checked_aligned_u64_offset(offset, guard.len())?;
let actual = u64::from_le_bytes(
guard[range.clone()]
.try_into()
.expect("slice is exactly 8 bytes"),
);
if actual == current {
guard[range].copy_from_slice(&new.to_le_bytes());
Ok(Ok(actual))
} else {
Ok(Err(actual))
}
}
#[cfg(unix)]
ShmRegionBacking::Mmap(m) => {
self.checked_aligned_u64_offset(offset, m.len)?;
let _guard = m
.mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
Ok(unsafe { atomic_u64_at(m.ptr, offset) }
.compare_exchange(current.to_le(), new.to_le(), success, failure)
.map(u64::from_le)
.map_err(u64::from_le))
}
}
}
pub fn try_resize_heap(&mut self, new_size: usize) -> Result<()> {
match &self.backing {
ShmRegionBacking::Heap(data) => {
let mut guard = data
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if new_size > guard.len() {
let current_len = guard.len();
guard
.try_reserve_exact(new_size.saturating_sub(current_len))
.map_err(|_| FrankenError::OutOfMemory)?;
guard.resize(new_size, 0);
} else if new_size < guard.len() {
guard.truncate(new_size);
}
self.len = new_size;
Ok(())
}
#[cfg(unix)]
ShmRegionBacking::Mmap(_) => Err(FrankenError::Unsupported),
}
}
#[must_use]
pub fn heap_len_and_capacity(&self) -> Option<(usize, usize)> {
match &self.backing {
ShmRegionBacking::Heap(data) => {
let guard = data
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
Some((guard.len(), guard.capacity()))
}
#[cfg(unix)]
ShmRegionBacking::Mmap(_) => None,
}
}
#[must_use]
pub fn is_mmap_backed(&self) -> bool {
match &self.backing {
ShmRegionBacking::Heap(_) => false,
#[cfg(unix)]
ShmRegionBacking::Mmap(_) => true,
}
}
fn checked_range(
&self,
offset: usize,
width: usize,
actual_len: usize,
what: &'static str,
) -> Result<Range<usize>> {
Self::checked_range_for_len(offset, width, self.len.min(actual_len), what)
}
fn checked_aligned_u64_offset(&self, offset: usize, actual_len: usize) -> Result<Range<usize>> {
if offset % std::mem::align_of::<u64>() != 0 {
return Err(FrankenError::OutOfRange {
what: "SHM atomic u64 access".to_owned(),
value: format!("unaligned offset={offset}"),
});
}
self.checked_range(offset, 8, actual_len, "SHM atomic u64 access")
}
fn checked_range_for_len(
offset: usize,
width: usize,
len: usize,
what: &'static str,
) -> Result<Range<usize>> {
let end = offset
.checked_add(width)
.ok_or_else(|| FrankenError::OutOfRange {
what: what.to_owned(),
value: format!("offset={offset} width={width} len={len}"),
})?;
if end > len {
return Err(FrankenError::OutOfRange {
what: what.to_owned(),
value: format!("offset={offset} width={width} len={len}"),
});
}
Ok(offset..end)
}
}
#[cfg(unix)]
#[allow(clippy::cast_ptr_alignment)]
unsafe fn atomic_u64_at<'a>(ptr: *mut u8, offset: usize) -> &'a AtomicU64 {
unsafe { &*ptr.add(offset).cast::<AtomicU64>() }
}
pub struct ShmRegionGuard<'a> {
inner: ShmRegionGuardInner<'a>,
}
enum ShmRegionGuardInner<'a> {
Heap {
guard: MutexGuard<'a, Vec<u8>>,
visible_len: usize,
},
#[cfg(unix)]
Mmap {
ptr: *mut u8,
visible_len: usize,
_guard: MutexGuard<'a, ()>,
_backing: &'a Arc<MmapBacking>,
},
}
impl Deref for ShmRegionGuard<'_> {
type Target = [u8];
fn deref(&self) -> &[u8] {
match &self.inner {
ShmRegionGuardInner::Heap { guard, visible_len } => {
&guard[..(*visible_len).min(guard.len())]
}
#[cfg(unix)]
ShmRegionGuardInner::Mmap {
ptr, visible_len, ..
} => {
unsafe { std::slice::from_raw_parts(*ptr, *visible_len) }
}
}
}
}
impl DerefMut for ShmRegionGuard<'_> {
fn deref_mut(&mut self) -> &mut [u8] {
match &mut self.inner {
ShmRegionGuardInner::Heap { guard, visible_len } => {
let end = (*visible_len).min(guard.len());
&mut guard[..end]
}
#[cfg(unix)]
ShmRegionGuardInner::Mmap {
ptr, visible_len, ..
} => {
unsafe { std::slice::from_raw_parts_mut(*ptr, *visible_len) }
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shm_region_new_zeroed() {
let region = ShmRegion::new(4096);
assert_eq!(region.len(), 4096);
assert!(region.lock().iter().all(|&b| b == 0));
}
#[test]
fn test_shm_region_read_write_u32() {
let region = ShmRegion::new(64);
region.write_u32_le(0, 0xDEAD_BEEF).unwrap();
region.write_u32_le(4, 42).unwrap();
assert_eq!(region.read_u32_le(0).unwrap(), 0xDEAD_BEEF);
assert_eq!(region.read_u32_le(4).unwrap(), 42);
}
#[test]
fn test_shm_region_read_write_u64() {
let region = ShmRegion::new(64);
region.write_u64_le(0, 0x0102_0304_0506_0708).unwrap();
assert_eq!(region.read_u64_le(0).unwrap(), 0x0102_0304_0506_0708);
}
#[test]
fn test_shm_region_atomic_u64_round_trip() {
let region = ShmRegion::new(64);
region.atomic_store_u64_le(8, 41, Ordering::SeqCst).unwrap();
assert_eq!(region.atomic_load_u64_le(8, Ordering::SeqCst).unwrap(), 41);
let before = region
.atomic_fetch_add_u64_le(8, 1, Ordering::SeqCst)
.unwrap();
assert_eq!(before, 41);
assert_eq!(region.atomic_load_u64_le(8, Ordering::SeqCst).unwrap(), 42);
assert_eq!(
region
.atomic_compare_exchange_u64_le(8, 42, 99, Ordering::SeqCst, Ordering::SeqCst)
.unwrap(),
Ok(42)
);
assert_eq!(region.atomic_load_u64_le(8, Ordering::SeqCst).unwrap(), 99);
}
#[test]
fn test_shm_region_atomic_compare_exchange_reports_actual_value() {
let region = ShmRegion::new(64);
region.atomic_store_u64_le(16, 7, Ordering::SeqCst).unwrap();
assert_eq!(
region
.atomic_compare_exchange_u64_le(16, 8, 9, Ordering::SeqCst, Ordering::SeqCst)
.unwrap(),
Err(7)
);
assert_eq!(region.atomic_load_u64_le(16, Ordering::SeqCst).unwrap(), 7);
}
#[test]
fn test_shm_region_deref() {
let region = ShmRegion::new(8);
{
let mut g = region.lock();
g[0] = 0xFF;
}
assert_eq!(region.lock()[0], 0xFF);
}
#[test]
fn test_shm_region_from_vec() {
let data = vec![1, 2, 3, 4];
let region = ShmRegion::from_vec(data);
assert_eq!(region.len(), 4);
assert_eq!(&*region.lock(), &[1, 2, 3, 4]);
}
#[test]
fn test_wal_lock_slots_and_bytes() {
assert_eq!(WAL_WRITE_LOCK, 0);
assert_eq!(WAL_CKPT_LOCK, 1);
assert_eq!(WAL_RECOVER_LOCK, 2);
assert_eq!(wal_read_lock_slot(0), Some(3));
assert_eq!(wal_read_lock_slot(4), Some(7));
assert_eq!(wal_read_lock_slot(5), None);
assert_eq!(wal_lock_byte(WAL_WRITE_LOCK), Some(120));
assert_eq!(wal_lock_byte(7), Some(127));
assert_eq!(wal_lock_byte(8), None);
}
#[test]
fn test_shm_region_is_empty() {
let empty = ShmRegion::new(0);
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
let non_empty = ShmRegion::new(1);
assert!(!non_empty.is_empty());
}
#[test]
fn test_shm_region_from_vec_empty() {
let region = ShmRegion::from_vec(vec![]);
assert!(region.is_empty());
assert_eq!(region.len(), 0);
assert!(region.lock().is_empty());
}
#[test]
fn test_shm_region_clone_copies_existing_data() {
let r1 = ShmRegion::new(16);
r1.write_u32_le(0, 0x1234_5678).unwrap();
let r2 = r1.clone();
assert_eq!(r2.read_u32_le(0).unwrap(), 0x1234_5678);
assert_eq!(r1.read_u32_le(0).unwrap(), 0x1234_5678);
}
#[test]
fn test_shm_region_guard_deref_mut() {
let region = ShmRegion::new(8);
{
let mut guard = region.lock();
guard[0] = 0xAA;
guard[7] = 0xBB;
}
let guard = region.lock();
assert_eq!(guard[0], 0xAA);
assert_eq!(guard[7], 0xBB);
drop(guard);
}
#[test]
fn test_shm_region_u32_at_nonzero_offset() {
let region = ShmRegion::new(32);
region.write_u32_le(12, 999).unwrap();
region.write_u32_le(28, u32::MAX).unwrap();
assert_eq!(region.read_u32_le(12).unwrap(), 999);
assert_eq!(region.read_u32_le(28).unwrap(), u32::MAX);
assert_eq!(region.read_u32_le(16).unwrap(), 0);
}
#[test]
fn test_shm_region_u64_at_nonzero_offset() {
let region = ShmRegion::new(32);
region.write_u64_le(8, u64::MAX).unwrap();
assert_eq!(region.read_u64_le(8).unwrap(), u64::MAX);
assert_eq!(region.read_u64_le(0).unwrap(), 0);
}
#[test]
fn test_shm_region_u32_min_max() {
let region = ShmRegion::new(8);
region.write_u32_le(0, 0).unwrap();
assert_eq!(region.read_u32_le(0).unwrap(), 0);
region.write_u32_le(0, u32::MAX).unwrap();
assert_eq!(region.read_u32_le(0).unwrap(), u32::MAX);
}
#[test]
fn test_shm_region_u64_min_max() {
let region = ShmRegion::new(16);
region.write_u64_le(0, 0).unwrap();
assert_eq!(region.read_u64_le(0).unwrap(), 0);
region.write_u64_le(0, u64::MAX).unwrap();
assert_eq!(region.read_u64_le(0).unwrap(), u64::MAX);
}
#[test]
fn test_shm_flag_constants() {
assert_eq!(SQLITE_SHM_UNLOCK, 0x01);
assert_eq!(SQLITE_SHM_LOCK, 0x02);
assert_eq!(SQLITE_SHM_SHARED, 0x04);
assert_eq!(SQLITE_SHM_EXCLUSIVE, 0x08);
assert_ne!(
SQLITE_SHM_LOCK | SQLITE_SHM_SHARED,
SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE
);
}
#[test]
fn test_wal_read_lock_slot_all_valid() {
for i in 0..WAL_NREADER {
assert_eq!(wal_read_lock_slot(i), Some(WAL_READ_LOCK_BASE + i));
}
}
#[test]
fn test_wal_lock_byte_all_valid() {
for slot in 0..WAL_TOTAL_LOCKS {
let byte = wal_lock_byte(slot);
assert!(byte.is_some());
assert_eq!(byte.unwrap(), 120 + u64::from(slot));
}
}
#[test]
fn test_wal_total_locks_consistent() {
assert_eq!(WAL_TOTAL_LOCKS, WAL_READ_LOCK_BASE + WAL_NREADER);
assert_eq!(WAL_NREADER_USIZE, WAL_NREADER as usize);
}
#[test]
fn test_shm_region_read_u32_out_of_bounds() {
let region = ShmRegion::new(4);
let err = region.read_u32_le(2).unwrap_err(); assert!(matches!(err, FrankenError::OutOfRange { .. }));
}
#[test]
fn test_shm_region_read_u64_out_of_bounds() {
let region = ShmRegion::new(8);
let err = region.read_u64_le(4).unwrap_err(); assert!(matches!(err, FrankenError::OutOfRange { .. }));
}
#[test]
fn test_shm_region_write_out_of_bounds_returns_error() {
let region = ShmRegion::new(4);
let err = region.write_u32_le(2, 99).unwrap_err();
assert!(matches!(err, FrankenError::OutOfRange { .. }));
let region = ShmRegion::new(8);
let err = region.write_u64_le(4, 99).unwrap_err();
assert!(matches!(err, FrankenError::OutOfRange { .. }));
}
#[test]
fn test_shm_region_offset_overflow_returns_error() {
let region = ShmRegion::new(64);
let err = region.read_u32_le(usize::MAX - 1).unwrap_err();
assert!(matches!(err, FrankenError::OutOfRange { .. }));
let err = region.write_u64_le(usize::MAX - 7, 99).unwrap_err();
assert!(matches!(err, FrankenError::OutOfRange { .. }));
}
#[test]
fn test_shm_region_atomic_bad_offsets_return_error() {
let region = ShmRegion::new(16);
let err = region.atomic_load_u64_le(4, Ordering::SeqCst).unwrap_err();
assert!(matches!(err, FrankenError::OutOfRange { .. }));
let err = region
.atomic_store_u64_le(12, 99, Ordering::SeqCst)
.unwrap_err();
assert!(matches!(err, FrankenError::OutOfRange { .. }));
let err = region
.atomic_compare_exchange_u64_le(usize::MAX, 0, 1, Ordering::SeqCst, Ordering::SeqCst)
.unwrap_err();
assert!(matches!(err, FrankenError::OutOfRange { .. }));
}
#[test]
fn test_shm_region_clone_unaffected_by_original_shrink() {
let mut region = ShmRegion::new(16);
let clone = region.clone();
region.try_resize_heap(4).unwrap();
assert_eq!(clone.len(), 16);
assert_eq!(clone.lock().len(), 16);
clone.read_u64_le(8).unwrap();
let err = region.read_u64_le(8).unwrap_err();
assert!(matches!(err, FrankenError::OutOfRange { .. }));
}
#[test]
fn test_shm_region_stale_clone_after_grow_lock_respects_cached_len() {
let mut region = ShmRegion::new(4);
let clone = region.clone();
region.try_resize_heap(8).unwrap();
assert_eq!(region.len(), 8);
assert_eq!(region.lock().len(), 8);
assert_eq!(clone.len(), 4);
assert_eq!(
clone.lock().len(),
4,
"stale clones must not see newly mapped bytes until remapped"
);
}
#[test]
fn test_shm_region_debug() {
let region = ShmRegion::new(4);
let debug_str = format!("{region:?}");
assert!(debug_str.contains("ShmRegion"));
}
#[test]
fn test_shm_region_interleaved_u32_u64() {
let region = ShmRegion::new(16);
region.write_u32_le(0, 42).unwrap();
region.write_u64_le(8, 0xCAFE_BABE_DEAD_BEEF).unwrap();
assert_eq!(region.read_u32_le(0).unwrap(), 42);
assert_eq!(region.read_u64_le(8).unwrap(), 0xCAFE_BABE_DEAD_BEEF);
}
#[test]
fn test_shm_region_read_write_u32_ne() {
let region = ShmRegion::new(64);
region.write_u32_ne(0, 0xDEAD_BEEF).unwrap();
region.write_u32_ne(4, 42).unwrap();
assert_eq!(region.read_u32_ne(0).unwrap(), 0xDEAD_BEEF);
assert_eq!(region.read_u32_ne(4).unwrap(), 42);
}
#[test]
fn test_shm_region_native_endian_consistency() {
let region = ShmRegion::new(16);
let value = 0x1234_5678_u32;
region.write_u32_ne(0, value).unwrap();
assert_eq!(region.read_u32_ne(0).unwrap(), value);
if cfg!(target_endian = "little") {
assert_eq!(region.read_u32_le(0).unwrap(), value);
}
}
#[test]
fn test_shm_read_mark_offset_constant() {
assert_eq!(SHM_READ_MARK_OFFSET, 100);
assert_eq!(SHM_SEGMENT_SIZE, 32 * 1024);
}
#[test]
fn test_shm_region_try_resize_heap_preserves_data() {
let mut region = ShmRegion::new(8);
region.write_u32_le(0, 0xDEAD).unwrap();
region.write_u32_le(4, 0xBEEF).unwrap();
region.try_resize_heap(16).unwrap();
assert_eq!(region.len(), 16);
assert_eq!(region.read_u32_le(0).unwrap(), 0xDEAD);
assert_eq!(region.read_u32_le(4).unwrap(), 0xBEEF);
assert_eq!(
region.read_u32_le(8).unwrap(),
0,
"grown region zero-filled"
);
region.try_resize_heap(4).unwrap();
assert_eq!(region.len(), 4);
assert_eq!(region.read_u32_le(0).unwrap(), 0xDEAD);
}
#[test]
fn test_shm_region_heap_len_and_capacity() {
let region = ShmRegion::new(32);
let (len, cap) = region.heap_len_and_capacity().expect("heap-backed");
assert_eq!(len, 32);
assert!(cap >= 32);
}
#[test]
fn test_shm_region_is_mmap_backed_false_for_heap() {
let region = ShmRegion::new(16);
assert!(!region.is_mmap_backed());
}
#[test]
fn test_shm_region_atomic_fetch_add_u64_le() {
let region = ShmRegion::new(16);
region.write_u64_le(0, 100).unwrap();
let prev = region
.atomic_fetch_add_u64_le(0, 42, Ordering::SeqCst)
.unwrap();
assert_eq!(prev, 100);
assert_eq!(region.read_u64_le(0).unwrap(), 142);
let prev2 = region
.atomic_fetch_add_u64_le(0, u64::MAX, Ordering::SeqCst)
.unwrap();
assert_eq!(prev2, 142);
assert_eq!(region.read_u64_le(0).unwrap(), 141, "wrapping add");
}
#[test]
fn test_shm_region_clone_is_independent() {
let region = ShmRegion::new(16);
region.write_u32_le(0, 0xDEAD).unwrap();
let cloned = region.clone();
assert_eq!(cloned.read_u32_le(0).unwrap(), 0xDEAD);
assert_eq!(cloned.len(), 16);
region.write_u32_le(0, 0xBEEF).unwrap();
assert_eq!(
cloned.read_u32_le(0).unwrap(),
0xDEAD,
"clone must be independent"
);
}
#[test]
fn test_shm_region_share_aliases_buffer() {
let region = ShmRegion::new(16);
region.write_u32_le(0, 0xDEAD).unwrap();
let shared = region.share();
assert_eq!(
shared.read_u32_le(0).unwrap(),
0xDEAD,
"share() handle sees post-write state",
);
region.write_u32_le(0, 0xBEEF).unwrap();
assert_eq!(
shared.read_u32_le(0).unwrap(),
0xBEEF,
"share() handle aliases the original buffer — subsequent writes through one are visible to the other",
);
shared.write_u32_le(4, 0xCAFE).unwrap();
assert_eq!(
region.read_u32_le(4).unwrap(),
0xCAFE,
"writes through the shared handle are also visible to the original",
);
}
#[test]
fn test_wal_read_lock_slot_out_of_range() {
assert!(wal_read_lock_slot(WAL_NREADER).is_none());
assert!(wal_read_lock_slot(WAL_NREADER + 1).is_none());
assert!(wal_read_lock_slot(u32::MAX).is_none());
}
#[test]
fn test_wal_lock_byte_out_of_range() {
assert!(wal_lock_byte(WAL_TOTAL_LOCKS).is_none());
assert!(wal_lock_byte(WAL_TOTAL_LOCKS + 1).is_none());
assert!(wal_lock_byte(u32::MAX).is_none());
}
#[test]
fn test_shm_segment_size_is_32kib() {
assert_eq!(SHM_SEGMENT_SIZE, 32 * 1024);
assert_eq!(WAL_TOTAL_LOCKS, WAL_READ_LOCK_BASE + WAL_NREADER);
}
}