use std::alloc::{alloc, dealloc, Layout};
use std::cell::UnsafeCell;
use std::ptr::NonNull;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
pub struct PoolConfig {
pub block_size: usize,
pub initial_blocks: usize,
pub max_blocks: Option<usize>,
pub auto_resize: bool,
pub growth_factor: f64,
}
impl Default for PoolConfig {
fn default() -> Self {
Self {
block_size: 4096, initial_blocks: 16, max_blocks: Some(1024), auto_resize: true, growth_factor: 2.0, }
}
}
#[derive(Debug)]
struct MemoryBlock {
ptr: NonNull<u8>,
layout: Layout,
}
unsafe impl Send for MemoryBlock {}
unsafe impl Sync for MemoryBlock {}
impl MemoryBlock {
fn new(layout: Layout) -> Option<Self> {
unsafe {
let ptr = NonNull::new(alloc(layout))?;
Some(Self { ptr, layout })
}
}
}
impl Drop for MemoryBlock {
fn drop(&mut self) {
unsafe {
dealloc(self.ptr.as_ptr(), self.layout);
}
}
}
#[derive(Debug)]
struct PoolState {
config: PoolConfig,
memory: Vec<MemoryBlock>,
free_list: Vec<usize>,
total_blocks: usize,
used_blocks: usize,
}
#[derive(Debug)]
pub struct PoolAllocator {
state: Arc<Mutex<UnsafeCell<PoolState>>>,
}
impl PoolAllocator {
pub fn new(config: PoolConfig) -> Self {
let mut state = PoolState {
config: config.clone(),
memory: Vec::with_capacity(config.initial_blocks),
free_list: Vec::with_capacity(config.initial_blocks),
total_blocks: 0,
used_blocks: 0,
};
let layout = Layout::from_size_align(config.block_size, 8).unwrap_or_else(|_| {
Layout::from_size_align(1024, 8)
.expect("Layout::from_size_align(1024, 8) should succeed")
});
for _ in 0..config.initial_blocks {
if let Some(block) = MemoryBlock::new(layout) {
state.free_list.push(state.memory.len());
state.memory.push(block);
state.total_blocks += 1;
}
}
Self {
state: Arc::new(Mutex::new(UnsafeCell::new(state))),
}
}
pub fn allocate(&self) -> Option<NonNull<u8>> {
let state_mutex = self
.state
.lock()
.expect("state mutex should not be poisoned");
let state = unsafe { &mut *state_mutex.get() };
if let Some(index) = state.free_list.pop() {
state.used_blocks += 1;
return Some(state.memory[index].ptr);
}
if state.config.auto_resize {
let can_grow = match state.config.max_blocks {
Some(max) => state.total_blocks < max,
None => true,
};
if can_grow {
let growth = (state.total_blocks as f64 * (state.config.growth_factor - 1.0)).ceil()
as usize;
let new_blocks = growth.max(1); let max_new = match state.config.max_blocks {
Some(max) => (max - state.total_blocks).min(new_blocks),
None => new_blocks,
};
let layout =
Layout::from_size_align(state.config.block_size, 8).unwrap_or_else(|_| {
Layout::from_size_align(1024, 8)
.expect("Layout::from_size_align(1024, 8) should succeed")
});
for _ in 0..max_new {
if let Some(block) = MemoryBlock::new(layout) {
state.free_list.push(state.memory.len());
state.memory.push(block);
state.total_blocks += 1;
}
}
if let Some(index) = state.free_list.pop() {
state.used_blocks += 1;
return Some(state.memory[index].ptr);
}
}
}
None
}
pub unsafe fn deallocate(&self, ptr: NonNull<u8>) {
let state_mutex = self
.state
.lock()
.expect("state mutex should not be poisoned");
let state = &mut *state_mutex.get();
for (i, block) in state.memory.iter().enumerate() {
if block.ptr.as_ptr() == ptr.as_ptr() {
state.free_list.push(i);
state.used_blocks -= 1;
return;
}
}
panic!("Attempted to free a pointer that wasn't allocated from this pool");
}
pub fn available_blocks(&self) -> usize {
let state_mutex = self
.state
.lock()
.expect("state mutex should not be poisoned");
let state = unsafe { &*state_mutex.get() };
state.free_list.len()
}
pub fn total_blocks(&self) -> usize {
let state_mutex = self
.state
.lock()
.expect("state mutex should not be poisoned");
let state = unsafe { &*state_mutex.get() };
state.total_blocks
}
pub fn used_blocks(&self) -> usize {
let state_mutex = self
.state
.lock()
.expect("state mutex should not be poisoned");
let state = unsafe { &*state_mutex.get() };
state.used_blocks
}
pub fn block_size(&self) -> usize {
let state_mutex = self
.state
.lock()
.expect("state mutex should not be poisoned");
let state = unsafe { &*state_mutex.get() };
state.config.block_size
}
pub fn reset(&self) {
let state_mutex = self
.state
.lock()
.expect("state mutex should not be poisoned");
let state = unsafe { &mut *state_mutex.get() };
state.free_list.clear();
for i in 0..state.memory.len() {
state.free_list.push(i);
}
state.used_blocks = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pool_allocator_basic() {
let config = PoolConfig {
block_size: 1024,
initial_blocks: 4,
max_blocks: Some(8),
auto_resize: true,
growth_factor: 2.0,
};
let pool = PoolAllocator::new(config);
assert_eq!(pool.available_blocks(), 4);
assert_eq!(pool.total_blocks(), 4);
assert_eq!(pool.used_blocks(), 0);
let mut ptrs = Vec::new();
for _ in 0..4 {
let ptr = pool.allocate().expect("Allocation should succeed");
ptrs.push(ptr);
}
assert_eq!(pool.available_blocks(), 0);
assert_eq!(pool.used_blocks(), 4);
let ptr5 = pool.allocate().expect("Allocation should trigger resize");
ptrs.push(ptr5);
assert_eq!(pool.total_blocks(), 8);
assert_eq!(pool.available_blocks(), 3);
assert_eq!(pool.used_blocks(), 5);
unsafe {
pool.deallocate(ptrs[0]);
}
assert_eq!(pool.available_blocks(), 4);
assert_eq!(pool.used_blocks(), 4);
pool.reset();
assert_eq!(pool.available_blocks(), 8);
assert_eq!(pool.used_blocks(), 0);
}
#[test]
fn test_pool_allocator_max_size() {
let config = PoolConfig {
block_size: 64,
initial_blocks: 2,
max_blocks: Some(2),
auto_resize: true, growth_factor: 2.0,
};
let pool = PoolAllocator::new(config);
let ptr1 = pool.allocate().expect("First allocation should succeed");
let ptr2 = pool.allocate().expect("Second allocation should succeed");
assert!(pool.allocate().is_none());
unsafe {
pool.deallocate(ptr1);
pool.deallocate(ptr2);
}
assert_eq!(pool.available_blocks(), 2);
}
}