use std::ptr::NonNull;
use std::sync::{Arc, Mutex};
use std::time::Instant;
pub struct ArenaAllocator {
base_ptr: NonNull<u8>,
total_size: usize,
current_offset: usize,
high_water_mark: usize,
alignment: usize,
config: ArenaConfig,
allocations: Vec<AllocationRecord>,
stats: ArenaStats,
checkpoints: Vec<ArenaCheckpoint>,
}
#[derive(Debug, Clone)]
pub struct AllocationRecord {
pub ptr: NonNull<u8>,
pub size: usize,
pub offset: usize,
pub allocated_at: Instant,
pub id: u64,
pub tag: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ArenaCheckpoint {
pub offset: usize,
pub allocation_count: usize,
pub created_at: Instant,
pub name: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ArenaConfig {
pub alignment: usize,
pub enable_tracking: bool,
pub enable_debug: bool,
pub enable_checkpoints: bool,
pub enable_stats: bool,
pub growth_strategy: GrowthStrategy,
pub initial_tracking_capacity: usize,
}
impl Default for ArenaConfig {
fn default() -> Self {
Self {
alignment: 8,
enable_tracking: false,
enable_debug: false,
enable_checkpoints: true,
enable_stats: true,
growth_strategy: GrowthStrategy::Fixed,
initial_tracking_capacity: 1024,
}
}
}
#[derive(Debug, Clone)]
pub enum GrowthStrategy {
Fixed,
Double,
Linear(usize),
Custom(fn(usize) -> usize),
}
#[derive(Debug, Clone, Default)]
pub struct ArenaStats {
pub total_allocations: u64,
pub total_bytes_allocated: u64,
pub current_bytes_allocated: usize,
pub peak_bytes_allocated: usize,
pub reset_count: u64,
pub checkpoint_count: u64,
pub rollback_count: u64,
pub average_allocation_size: f64,
pub allocation_rate: f64,
pub utilization_ratio: f64,
pub first_allocation_time: Option<Instant>,
pub last_allocation_time: Option<Instant>,
pub bytes_wasted_to_alignment: u64,
}
impl ArenaStats {
pub fn record_allocation(&mut self, size: usize) {
let now = Instant::now();
self.total_allocations += 1;
self.total_bytes_allocated += size as u64;
self.current_bytes_allocated += size;
if self.current_bytes_allocated > self.peak_bytes_allocated {
self.peak_bytes_allocated = self.current_bytes_allocated;
}
self.average_allocation_size =
self.total_bytes_allocated as f64 / self.total_allocations as f64;
if let Some(first_time) = self.first_allocation_time {
let elapsed = now.duration_since(first_time).as_secs_f64();
if elapsed > 0.0 {
self.allocation_rate = self.total_allocations as f64 / elapsed;
}
} else {
self.first_allocation_time = Some(now);
}
self.last_allocation_time = Some(now);
}
pub fn record_reset(&mut self) {
self.reset_count += 1;
self.current_bytes_allocated = 0;
}
pub fn record_checkpoint(&mut self) {
self.checkpoint_count += 1;
}
pub fn record_rollback(&mut self, bytes_freed: usize) {
self.rollback_count += 1;
self.current_bytes_allocated = self.current_bytes_allocated.saturating_sub(bytes_freed);
}
pub fn update_utilization(&mut self, total_size: usize) {
if total_size > 0 {
self.utilization_ratio = self.current_bytes_allocated as f64 / total_size as f64;
}
}
pub fn record_padding(&mut self, padding: usize) {
self.bytes_wasted_to_alignment += padding as u64;
}
}
impl ArenaAllocator {
pub fn new(
base_ptr: NonNull<u8>,
size: usize,
config: ArenaConfig,
) -> Result<Self, ArenaError> {
if size == 0 {
return Err(ArenaError::InvalidSize(
"Arena size cannot be zero".to_string(),
));
}
if !config.alignment.is_power_of_two() {
return Err(ArenaError::InvalidAlignment(format!(
"Alignment {} is not a power of two",
config.alignment
)));
}
let allocations = if config.enable_tracking {
Vec::with_capacity(config.initial_tracking_capacity)
} else {
Vec::new()
};
Ok(Self {
base_ptr,
total_size: size,
current_offset: 0,
high_water_mark: 0,
alignment: config.alignment,
allocations,
stats: ArenaStats::default(),
checkpoints: Vec::new(),
config,
})
}
pub fn allocate(&mut self, size: usize) -> Result<NonNull<u8>, ArenaError> {
if size == 0 {
return Err(ArenaError::InvalidSize(
"Cannot allocate zero bytes".to_string(),
));
}
let aligned_size = (size + self.alignment - 1) & !(self.alignment - 1);
if self.current_offset + aligned_size > self.total_size {
return Err(ArenaError::OutOfMemory(format!(
"Not enough space: need {}, have {}",
aligned_size,
self.total_size - self.current_offset
)));
}
let ptr =
unsafe { NonNull::new_unchecked(self.base_ptr.as_ptr().add(self.current_offset)) };
self.current_offset += aligned_size;
if self.current_offset > self.high_water_mark {
self.high_water_mark = self.current_offset;
}
if self.config.enable_tracking {
let record = AllocationRecord {
ptr,
size: aligned_size,
offset: self.current_offset - aligned_size,
allocated_at: Instant::now(),
id: self.stats.total_allocations,
tag: None,
};
self.allocations.push(record);
}
if self.config.enable_stats {
self.stats.record_allocation(aligned_size);
self.stats.update_utilization(self.total_size);
}
Ok(ptr)
}
pub fn allocate_tagged(&mut self, size: usize, tag: String) -> Result<NonNull<u8>, ArenaError> {
let ptr = self.allocate(size)?;
if self.config.enable_tracking && !self.allocations.is_empty() {
let last_idx = self.allocations.len() - 1;
self.allocations[last_idx].tag = Some(tag);
}
Ok(ptr)
}
pub fn allocate_aligned(
&mut self,
size: usize,
alignment: usize,
) -> Result<NonNull<u8>, ArenaError> {
if !alignment.is_power_of_two() {
return Err(ArenaError::InvalidAlignment(format!(
"Alignment {} is not a power of two",
alignment
)));
}
let aligned_offset = (self.current_offset + alignment - 1) & !(alignment - 1);
let padding = aligned_offset - self.current_offset;
if aligned_offset + size > self.total_size {
return Err(ArenaError::OutOfMemory(format!(
"Not enough space for aligned allocation: need {}, have {}",
aligned_offset + size - self.current_offset,
self.total_size - self.current_offset
)));
}
self.current_offset = aligned_offset;
if self.config.enable_stats && padding > 0 {
self.stats.record_padding(padding);
}
self.allocate(size)
}
pub fn reset(&mut self) {
self.current_offset = 0;
if self.config.enable_tracking {
self.allocations.clear();
}
if self.config.enable_stats {
self.stats.record_reset();
self.stats.update_utilization(self.total_size);
}
self.checkpoints.clear();
}
pub fn checkpoint(&mut self) -> Result<CheckpointHandle, ArenaError> {
if !self.config.enable_checkpoints {
return Err(ArenaError::CheckpointsDisabled);
}
let checkpoint = ArenaCheckpoint {
offset: self.current_offset,
allocation_count: self.allocations.len(),
created_at: Instant::now(),
name: None,
};
self.checkpoints.push(checkpoint);
if self.config.enable_stats {
self.stats.record_checkpoint();
}
Ok(CheckpointHandle {
index: self.checkpoints.len() - 1,
offset: self.current_offset,
})
}
pub fn checkpoint_named(&mut self, name: String) -> Result<CheckpointHandle, ArenaError> {
if !self.config.enable_checkpoints {
return Err(ArenaError::CheckpointsDisabled);
}
let checkpoint = ArenaCheckpoint {
offset: self.current_offset,
allocation_count: self.allocations.len(),
created_at: Instant::now(),
name: Some(name),
};
self.checkpoints.push(checkpoint);
if self.config.enable_stats {
self.stats.record_checkpoint();
}
Ok(CheckpointHandle {
index: self.checkpoints.len() - 1,
offset: self.current_offset,
})
}
pub fn rollback(&mut self, handle: CheckpointHandle) -> Result<(), ArenaError> {
if !self.config.enable_checkpoints {
return Err(ArenaError::CheckpointsDisabled);
}
if handle.index >= self.checkpoints.len() {
return Err(ArenaError::InvalidCheckpoint(
"Checkpoint index out of range".to_string(),
));
}
let checkpoint = &self.checkpoints[handle.index];
if checkpoint.offset != handle.offset {
return Err(ArenaError::InvalidCheckpoint(
"stale checkpoint handle: the checkpoint at this index was replaced since the handle was created".to_string(),
));
}
let bytes_freed = self.current_offset - checkpoint.offset;
self.current_offset = checkpoint.offset;
if self.config.enable_tracking {
self.allocations.truncate(checkpoint.allocation_count);
}
self.checkpoints.truncate(handle.index);
if self.config.enable_stats {
self.stats.record_rollback(bytes_freed);
self.stats.update_utilization(self.total_size);
}
Ok(())
}
pub fn get_usage(&self) -> ArenaUsage {
ArenaUsage {
total_size: self.total_size,
used_size: self.current_offset,
free_size: self.total_size - self.current_offset,
high_water_mark: self.high_water_mark,
allocation_count: self.allocations.len(),
checkpoint_count: self.checkpoints.len(),
utilization_ratio: self.current_offset as f64 / self.total_size as f64,
}
}
pub fn get_stats(&self) -> &ArenaStats {
&self.stats
}
pub fn get_allocations(&self) -> &[AllocationRecord] {
&self.allocations
}
pub fn get_checkpoints(&self) -> &[ArenaCheckpoint] {
&self.checkpoints
}
pub fn contains_pointer(&self, ptr: NonNull<u8>) -> bool {
let ptr_addr = ptr.as_ptr() as usize;
let base_addr = self.base_ptr.as_ptr() as usize;
ptr_addr >= base_addr && ptr_addr < base_addr + self.current_offset
}
pub fn get_allocation_info(&self, ptr: NonNull<u8>) -> Option<&AllocationRecord> {
if !self.config.enable_tracking {
return None;
}
self.allocations.iter().find(|record| record.ptr == ptr)
}
pub fn validate(&self) -> Result<(), ArenaError> {
if self.current_offset > self.total_size {
return Err(ArenaError::CorruptedArena(format!(
"Current offset {} exceeds total size {}",
self.current_offset, self.total_size
)));
}
if self.high_water_mark > self.total_size {
return Err(ArenaError::CorruptedArena(format!(
"High water mark {} exceeds total size {}",
self.high_water_mark, self.total_size
)));
}
if self.high_water_mark < self.current_offset {
return Err(ArenaError::CorruptedArena(format!(
"High water mark {} is less than current offset {}",
self.high_water_mark, self.current_offset
)));
}
if self.config.enable_tracking {
let mut total_tracked_size = 0;
for (i, record) in self.allocations.iter().enumerate() {
if !self.contains_pointer(record.ptr) {
return Err(ArenaError::CorruptedArena(format!(
"Allocation {} has pointer outside arena bounds",
i
)));
}
total_tracked_size += record.size;
}
if total_tracked_size > self.current_offset {
return Err(ArenaError::CorruptedArena(format!(
"Tracked size {} exceeds current offset {}",
total_tracked_size, self.current_offset
)));
}
}
Ok(())
}
pub fn get_memory_layout(&self) -> MemoryLayout {
let mut layout = MemoryLayout {
base_address: self.base_ptr.as_ptr() as usize,
total_size: self.total_size,
used_size: self.current_offset,
regions: Vec::new(),
};
if self.config.enable_tracking {
for record in &self.allocations {
layout.regions.push(MemoryRegion {
offset: record.offset,
size: record.size,
allocated_at: record.allocated_at,
tag: record.tag.clone(),
});
}
}
layout
}
}
unsafe impl Send for ArenaAllocator {}
unsafe impl Sync for ArenaAllocator {}
#[derive(Debug, Clone)]
pub struct CheckpointHandle {
index: usize,
offset: usize,
}
#[derive(Debug, Clone)]
pub struct ArenaUsage {
pub total_size: usize,
pub used_size: usize,
pub free_size: usize,
pub high_water_mark: usize,
pub allocation_count: usize,
pub checkpoint_count: usize,
pub utilization_ratio: f64,
}
#[derive(Debug, Clone)]
pub struct MemoryLayout {
pub base_address: usize,
pub total_size: usize,
pub used_size: usize,
pub regions: Vec<MemoryRegion>,
}
#[derive(Debug, Clone)]
pub struct MemoryRegion {
pub offset: usize,
pub size: usize,
pub allocated_at: Instant,
pub tag: Option<String>,
}
pub struct RingArena {
arena: ArenaAllocator,
read_offset: usize,
live_allocations: usize,
ring_config: RingConfig,
}
#[derive(Debug, Clone)]
pub struct RingConfig {
pub overwrite_protection: bool,
pub overwrite_callback: Option<fn(*mut u8, usize)>,
pub enable_stats: bool,
}
impl Default for RingConfig {
fn default() -> Self {
Self {
overwrite_protection: true,
overwrite_callback: None,
enable_stats: true,
}
}
}
impl RingArena {
pub fn new(
base_ptr: NonNull<u8>,
size: usize,
ring_config: RingConfig,
) -> Result<Self, ArenaError> {
let arena_config = ArenaConfig {
enable_tracking: ring_config.enable_stats,
enable_checkpoints: false,
..ArenaConfig::default()
};
let arena = ArenaAllocator::new(base_ptr, size, arena_config)?;
Ok(Self {
arena,
read_offset: 0,
live_allocations: 0,
ring_config,
})
}
pub fn allocate(&mut self, size: usize) -> Result<NonNull<u8>, ArenaError> {
if self.ring_config.overwrite_protection {
let aligned_size = (size + self.arena.alignment - 1) & !(self.arena.alignment - 1);
if self.arena.current_offset + aligned_size > self.arena.total_size {
if self.read_offset > 0 && aligned_size > self.read_offset {
return Err(ArenaError::RingBufferFull(
"Ring buffer full, would overwrite live data".to_string(),
));
}
self.arena.current_offset = 0;
} else if self.read_offset > self.arena.current_offset {
if self.arena.current_offset + aligned_size > self.read_offset {
return Err(ArenaError::RingBufferFull(
"Ring buffer full, would overwrite live data".to_string(),
));
}
}
}
let ptr = self.arena.allocate(size)?;
self.live_allocations += 1;
Ok(ptr)
}
pub fn consume(&mut self, size: usize) -> Result<(), ArenaError> {
let aligned_size = (size + self.arena.alignment - 1) & !(self.arena.alignment - 1);
if self.read_offset + aligned_size > self.arena.total_size {
self.read_offset = aligned_size - (self.arena.total_size - self.read_offset);
} else {
self.read_offset += aligned_size;
}
self.live_allocations = self.live_allocations.saturating_sub(1);
Ok(())
}
pub fn reset(&mut self) {
self.arena.reset();
self.read_offset = 0;
self.live_allocations = 0;
}
pub fn get_ring_usage(&self) -> RingUsage {
let total_size = self.arena.total_size;
let write_offset = self.arena.current_offset;
let used_size = if write_offset >= self.read_offset {
write_offset - self.read_offset
} else {
total_size - self.read_offset + write_offset
};
RingUsage {
total_size,
used_size,
free_size: total_size - used_size,
read_offset: self.read_offset,
write_offset,
live_allocations: self.live_allocations,
}
}
}
#[derive(Debug, Clone)]
pub struct RingUsage {
pub total_size: usize,
pub used_size: usize,
pub free_size: usize,
pub read_offset: usize,
pub write_offset: usize,
pub live_allocations: usize,
}
pub struct GrowingArena {
current_arena: ArenaAllocator,
previous_arenas: Vec<ArenaAllocator>,
growth_strategy: GrowthStrategy,
external_allocator: Option<Box<dyn ExternalAllocator>>,
}
pub trait ExternalAllocator {
fn allocate(&mut self, size: usize) -> Result<NonNull<u8>, ArenaError>;
fn deallocate(&mut self, ptr: NonNull<u8>, size: usize);
}
impl GrowingArena {
pub fn new(
base_ptr: NonNull<u8>,
initial_size: usize,
growth_strategy: GrowthStrategy,
) -> Result<Self, ArenaError> {
let config = ArenaConfig::default();
let arena = ArenaAllocator::new(base_ptr, initial_size, config)?;
Ok(Self {
current_arena: arena,
previous_arenas: Vec::new(),
growth_strategy,
external_allocator: None,
})
}
pub fn with_external_allocator(mut self, allocator: Box<dyn ExternalAllocator>) -> Self {
self.external_allocator = Some(allocator);
self
}
pub fn allocate(&mut self, size: usize) -> Result<NonNull<u8>, ArenaError> {
match self.current_arena.allocate(size) {
Ok(ptr) => Ok(ptr),
Err(ArenaError::OutOfMemory(_)) => {
self.grow(size)?;
self.current_arena.allocate(size)
}
Err(e) => Err(e),
}
}
fn grow(&mut self, min_additional_size: usize) -> Result<(), ArenaError> {
if self.external_allocator.is_none() {
return Err(ArenaError::CannotGrow(
"No external allocator configured".to_string(),
));
}
let current_size = self.current_arena.total_size;
let new_size = match &self.growth_strategy {
GrowthStrategy::Fixed => {
return Err(ArenaError::CannotGrow("Fixed size arena".to_string()))
}
GrowthStrategy::Double => current_size * 2,
GrowthStrategy::Linear(increment) => current_size + increment,
GrowthStrategy::Custom(func) => func(current_size),
};
let actual_new_size = new_size.max(min_additional_size);
let new_ptr = self
.external_allocator
.as_mut()
.ok_or_else(|| ArenaError::CannotGrow("No external allocator configured".to_string()))?
.allocate(actual_new_size)?;
let old_arena = std::mem::replace(
&mut self.current_arena,
ArenaAllocator::new(new_ptr, actual_new_size, ArenaConfig::default())?,
);
self.previous_arenas.push(old_arena);
Ok(())
}
pub fn contains_pointer(&self, ptr: NonNull<u8>) -> bool {
if self.current_arena.contains_pointer(ptr) {
return true;
}
self.previous_arenas
.iter()
.any(|arena| arena.contains_pointer(ptr))
}
pub fn get_total_usage(&self) -> GrowingArenaUsage {
let mut total_size = self.current_arena.total_size;
let mut used_size = self.current_arena.current_offset;
let mut allocation_count = self.current_arena.allocations.len();
for arena in &self.previous_arenas {
total_size += arena.total_size;
used_size += arena.current_offset;
allocation_count += arena.allocations.len();
}
GrowingArenaUsage {
total_size,
used_size,
free_size: total_size - used_size,
arena_count: 1 + self.previous_arenas.len(),
allocation_count,
current_arena_size: self.current_arena.total_size,
utilization_ratio: used_size as f64 / total_size as f64,
}
}
}
#[derive(Debug, Clone)]
pub struct GrowingArenaUsage {
pub total_size: usize,
pub used_size: usize,
pub free_size: usize,
pub arena_count: usize,
pub allocation_count: usize,
pub current_arena_size: usize,
pub utilization_ratio: f64,
}
#[derive(Debug, Clone)]
pub enum ArenaError {
InvalidSize(String),
InvalidAlignment(String),
OutOfMemory(String),
CheckpointsDisabled,
InvalidCheckpoint(String),
CorruptedArena(String),
RingBufferFull(String),
CannotGrow(String),
}
impl std::fmt::Display for ArenaError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ArenaError::InvalidSize(msg) => write!(f, "Invalid size: {}", msg),
ArenaError::InvalidAlignment(msg) => write!(f, "Invalid alignment: {}", msg),
ArenaError::OutOfMemory(msg) => write!(f, "Out of memory: {}", msg),
ArenaError::CheckpointsDisabled => write!(f, "Checkpoints are disabled"),
ArenaError::InvalidCheckpoint(msg) => write!(f, "Invalid checkpoint: {}", msg),
ArenaError::CorruptedArena(msg) => write!(f, "Corrupted arena: {}", msg),
ArenaError::RingBufferFull(msg) => write!(f, "Ring buffer full: {}", msg),
ArenaError::CannotGrow(msg) => write!(f, "Cannot grow: {}", msg),
}
}
}
impl std::error::Error for ArenaError {}
pub struct ThreadSafeArena {
arena: Arc<Mutex<ArenaAllocator>>,
}
impl ThreadSafeArena {
pub fn new(
base_ptr: NonNull<u8>,
size: usize,
config: ArenaConfig,
) -> Result<Self, ArenaError> {
let arena = ArenaAllocator::new(base_ptr, size, config)?;
Ok(Self {
arena: Arc::new(Mutex::new(arena)),
})
}
pub fn allocate(&self, size: usize) -> Result<NonNull<u8>, ArenaError> {
let mut arena = self.arena.lock().unwrap_or_else(|e| e.into_inner());
arena.allocate(size)
}
pub fn reset(&self) {
let mut arena = self.arena.lock().unwrap_or_else(|e| e.into_inner());
arena.reset();
}
pub fn checkpoint(&self) -> Result<CheckpointHandle, ArenaError> {
let mut arena = self.arena.lock().unwrap_or_else(|e| e.into_inner());
arena.checkpoint()
}
pub fn rollback(&self, handle: CheckpointHandle) -> Result<(), ArenaError> {
let mut arena = self.arena.lock().unwrap_or_else(|e| e.into_inner());
arena.rollback(handle)
}
pub fn get_usage(&self) -> ArenaUsage {
let arena = self.arena.lock().unwrap_or_else(|e| e.into_inner());
arena.get_usage()
}
pub fn get_stats(&self) -> ArenaStats {
let arena = self.arena.lock().unwrap_or_else(|e| e.into_inner());
arena.get_stats().clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arena_creation() {
let size = 4096;
let memory = vec![0u8; size];
let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
let config = ArenaConfig::default();
let arena = ArenaAllocator::new(ptr, size, config);
assert!(arena.is_ok());
}
#[test]
fn test_basic_allocation() {
let size = 4096;
let memory = vec![0u8; size];
let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
let config = ArenaConfig::default();
let mut arena = ArenaAllocator::new(ptr, size, config).expect("unwrap failed");
let alloc1 = arena.allocate(100);
assert!(alloc1.is_ok());
let alloc2 = arena.allocate(200);
assert!(alloc2.is_ok());
let usage = arena.get_usage();
assert!(usage.used_size > 0);
assert!(usage.allocation_count == 2 || !arena.config.enable_tracking);
}
#[test]
fn test_alignment() {
let size = 4096;
let memory = vec![0u8; size];
let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
let config = ArenaConfig {
alignment: 16,
..ArenaConfig::default()
};
let mut arena = ArenaAllocator::new(ptr, size, config).expect("unwrap failed");
let alloc_ptr = arena.allocate(10).expect("unwrap failed");
assert_eq!(alloc_ptr.as_ptr() as usize % 16, 0);
}
#[test]
fn test_allocate_aligned_records_padding_in_stats() {
let size = 4096;
let memory = vec![0u8; size];
let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
let config = ArenaConfig {
alignment: 1,
..ArenaConfig::default()
};
let mut arena = ArenaAllocator::new(ptr, size, config).expect("unwrap failed");
assert_eq!(arena.get_stats().bytes_wasted_to_alignment, 0);
arena.allocate(3).expect("unwrap failed");
arena
.allocate_aligned(10, 64)
.expect("aligned allocation should succeed");
assert_eq!(arena.get_stats().bytes_wasted_to_alignment, 61);
let before = arena.get_stats().bytes_wasted_to_alignment;
arena
.allocate_aligned(4, 1)
.expect("aligned allocation should succeed");
assert_eq!(arena.get_stats().bytes_wasted_to_alignment, before);
}
#[test]
fn test_checkpoints() {
let size = 4096;
let memory = vec![0u8; size];
let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
let config = ArenaConfig {
enable_checkpoints: true,
enable_tracking: true,
..ArenaConfig::default()
};
let mut arena = ArenaAllocator::new(ptr, size, config).expect("unwrap failed");
arena.allocate(100).expect("unwrap failed");
let checkpoint = arena.checkpoint().expect("unwrap failed");
arena.allocate(200).expect("unwrap failed");
let usage_before = arena.get_usage();
arena.rollback(checkpoint).expect("unwrap failed");
let usage_after = arena.get_usage();
assert!(usage_after.used_size < usage_before.used_size);
}
#[test]
fn test_rollback_rejects_stale_handle_after_index_reuse() {
let size = 4096;
let memory = vec![0u8; size];
let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
let config = ArenaConfig {
enable_checkpoints: true,
enable_tracking: true,
..ArenaConfig::default()
};
let mut arena = ArenaAllocator::new(ptr, size, config).expect("unwrap failed");
arena.allocate(100).expect("unwrap failed");
let handle_a = arena.checkpoint().expect("unwrap failed");
arena.rollback(handle_a.clone()).expect("unwrap failed");
arena.allocate(50).expect("unwrap failed");
arena.checkpoint().expect("unwrap failed");
let result = arena.rollback(handle_a);
assert!(matches!(result, Err(ArenaError::InvalidCheckpoint(_))));
}
#[test]
fn test_reset() {
let size = 4096;
let memory = vec![0u8; size];
let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
let config = ArenaConfig::default();
let mut arena = ArenaAllocator::new(ptr, size, config).expect("unwrap failed");
arena.allocate(100).expect("unwrap failed");
arena.allocate(200).expect("unwrap failed");
let usage_before = arena.get_usage();
assert!(usage_before.used_size > 0);
arena.reset();
let usage_after = arena.get_usage();
assert_eq!(usage_after.used_size, 0);
}
#[test]
fn test_ring_arena() {
let size = 1024;
let memory = vec![0u8; size];
let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
let config = RingConfig::default();
let mut ring = RingArena::new(ptr, size, config).expect("unwrap failed");
let alloc1 = ring.allocate(100);
assert!(alloc1.is_ok());
ring.consume(100).expect("unwrap failed");
let alloc2 = ring.allocate(100);
assert!(alloc2.is_ok());
}
#[test]
fn test_thread_safe_arena() {
let size = 4096;
let memory = vec![0u8; size];
let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
let config = ArenaConfig::default();
let arena = ThreadSafeArena::new(ptr, size, config).expect("unwrap failed");
let alloc_result = arena.allocate(100);
assert!(alloc_result.is_ok());
let usage = arena.get_usage();
assert!(usage.used_size > 0);
}
#[test]
fn test_arena_validation() {
let size = 4096;
let memory = vec![0u8; size];
let ptr = NonNull::new(memory.as_ptr() as *mut u8).expect("unwrap failed");
let config = ArenaConfig {
enable_tracking: true,
..ArenaConfig::default()
};
let mut arena = ArenaAllocator::new(ptr, size, config).expect("unwrap failed");
arena.allocate(100).expect("unwrap failed");
arena.allocate(200).expect("unwrap failed");
let validation_result = arena.validate();
assert!(validation_result.is_ok());
}
}