#![allow(unsafe_code)]
use std::cell::UnsafeCell;
use std::sync::atomic::{AtomicUsize, Ordering};
pub trait MemPool: Send + Sync {
fn alloc_str<'a>(&self, s: &'a str) -> &'a str;
fn alloc_bytes<'a>(&self, b: &'a [u8]) -> &'a [u8];
fn reset(&self);
fn used_bytes(&self) -> usize;
}
pub struct StackPool<const CAP: usize> {
buffer: UnsafeCell<[u8; CAP]>,
pos: AtomicUsize,
}
impl<const CAP: usize> StackPool<CAP> {
pub const fn new() -> Self {
Self {
buffer: UnsafeCell::new([0u8; CAP]),
pos: AtomicUsize::new(0),
}
}
pub const fn capacity() -> usize {
CAP
}
pub fn remaining(&self) -> usize {
CAP - self.pos.load(Ordering::Acquire)
}
}
unsafe impl<const CAP: usize> Send for StackPool<CAP> {}
unsafe impl<const CAP: usize> Sync for StackPool<CAP> {}
impl<const CAP: usize> MemPool for StackPool<CAP> {
fn alloc_str<'a>(&self, s: &'a str) -> &'a str {
let bytes = s.as_bytes();
let len = bytes.len();
if len == 0 {
return "";
}
let start = self.pos.fetch_add(len, Ordering::AcqRel);
if start + len > CAP {
self.pos.fetch_sub(len, Ordering::AcqRel);
return s;
}
let buf = unsafe { &mut *self.buffer.get() };
buf[start..start + len].copy_from_slice(bytes);
unsafe { std::str::from_utf8_unchecked(&buf[start..start + len]) }
}
fn alloc_bytes<'a>(&self, b: &'a [u8]) -> &'a [u8] {
let len = b.len();
if len == 0 {
return &[];
}
let start = self.pos.fetch_add(len, Ordering::AcqRel);
if start + len > CAP {
self.pos.fetch_sub(len, Ordering::AcqRel);
return b;
}
let buf = unsafe { &mut *self.buffer.get() };
buf[start..start + len].copy_from_slice(b);
&buf[start..start + len]
}
fn reset(&self) {
self.pos.store(0, Ordering::Release);
}
fn used_bytes(&self) -> usize {
self.pos.load(Ordering::Acquire)
}
}
impl<const CAP: usize> Default for StackPool<CAP> {
fn default() -> Self {
Self::new()
}
}
impl<const CAP: usize> std::fmt::Debug for StackPool<CAP> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "StackPool<{CAP}> used={}/{}", self.used_bytes(), CAP)
}
}
#[cfg(feature = "bumpalo-pool")]
mod bumpalo_backend {
use super::*;
use bumpalo::Bump;
use std::sync::Mutex;
pub struct BumpaloPool {
bump: Mutex<Bump>,
used: AtomicUsize,
}
impl BumpaloPool {
pub fn new() -> Self {
Self {
bump: Mutex::new(Bump::new()),
used: AtomicUsize::new(0),
}
}
}
impl Default for BumpaloPool {
fn default() -> Self {
Self::new()
}
}
impl MemPool for BumpaloPool {
fn alloc_str<'a>(&self, s: &'a str) -> &'a str {
let bump = self.bump.lock().unwrap();
let allocated = bump.alloc_str(s);
self.used.fetch_add(s.len(), Ordering::Relaxed);
unsafe { std::mem::transmute::<&str, &'a str>(allocated) }
}
fn alloc_bytes<'a>(&self, b: &'a [u8]) -> &'a [u8] {
let bump = self.bump.lock().unwrap();
let allocated = bump.alloc_slice_copy(b);
self.used.fetch_add(b.len(), Ordering::Relaxed);
unsafe { std::mem::transmute::<&[u8], &'a [u8]>(allocated) }
}
fn reset(&self) {
let mut bump = self.bump.lock().unwrap();
bump.reset();
self.used.store(0, Ordering::Release);
}
fn used_bytes(&self) -> usize {
self.used.load(Ordering::Acquire)
}
}
impl std::fmt::Debug for BumpaloPool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "BumpaloPool used={}", self.used_bytes())
}
}
}
#[cfg(feature = "bumpalo-pool")]
pub use bumpalo_backend::BumpaloPool;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemPoolType {
Bumpalo,
Stack,
None,
}
#[derive(Debug, Clone)]
pub struct MemPoolConfig {
pub pool_type: MemPoolType,
pub capacity: usize,
}
impl Default for MemPoolConfig {
fn default() -> Self {
Self {
pool_type: MemPoolType::Stack,
capacity: 4096,
}
}
}
pub fn create_pool(config: &MemPoolConfig) -> Option<Box<dyn MemPool>> {
match config.pool_type {
MemPoolType::Stack => match config.capacity {
1024 => Some(Box::new(StackPool::<1024>::new())),
2048 => Some(Box::new(StackPool::<2048>::new())),
4096 => Some(Box::new(StackPool::<4096>::new())),
8192 => Some(Box::new(StackPool::<8192>::new())),
16384 => Some(Box::new(StackPool::<16384>::new())),
32768 => Some(Box::new(StackPool::<32768>::new())),
65536 => Some(Box::new(StackPool::<65536>::new())),
_ => Some(Box::new(StackPool::<4096>::new())),
},
#[cfg(feature = "bumpalo-pool")]
MemPoolType::Bumpalo => Some(Box::new(BumpaloPool::new())),
#[cfg(not(feature = "bumpalo-pool"))]
MemPoolType::Bumpalo => None,
MemPoolType::None => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stack_pool_alloc_str() {
let pool = StackPool::<256>::new();
let s1 = pool.alloc_str("hello");
let s2 = pool.alloc_str("world");
assert_eq!(s1, "hello");
assert_eq!(s2, "world");
assert_eq!(pool.used_bytes(), 10);
}
#[test]
fn test_stack_pool_alloc_bytes() {
let pool = StackPool::<256>::new();
let b1 = pool.alloc_bytes(&[1, 2, 3]);
let b2 = pool.alloc_bytes(&[4, 5]);
assert_eq!(b1, &[1, 2, 3]);
assert_eq!(b2, &[4, 5]);
assert_eq!(pool.used_bytes(), 5);
}
#[test]
fn test_stack_pool_capacity_overflow() {
let pool = StackPool::<8>::new();
let s1 = pool.alloc_str("hello");
assert_eq!(s1, "hello");
let s2 = pool.alloc_str("world");
assert_eq!(s2, "world");
assert_eq!(pool.used_bytes(), 5);
}
#[test]
fn test_stack_pool_reset() {
let pool = StackPool::<256>::new();
let _ = pool.alloc_str("hello");
assert_eq!(pool.used_bytes(), 5);
pool.reset();
assert_eq!(pool.used_bytes(), 0);
}
#[test]
fn test_stack_pool_used_bytes() {
let pool = StackPool::<256>::new();
assert_eq!(pool.used_bytes(), 0);
let _ = pool.alloc_str("abc");
assert_eq!(pool.used_bytes(), 3);
let _ = pool.alloc_bytes(&[1, 2]);
assert_eq!(pool.used_bytes(), 5);
}
#[test]
fn test_stack_pool_empty_alloc() {
let pool = StackPool::<256>::new();
let s = pool.alloc_str("");
assert_eq!(s, "");
assert_eq!(pool.used_bytes(), 0);
let b = pool.alloc_bytes(&[][..]);
assert!(b.is_empty());
assert_eq!(pool.used_bytes(), 0);
}
#[test]
fn test_stack_pool_remaining() {
let pool = StackPool::<256>::new();
assert_eq!(pool.remaining(), 256);
let _ = pool.alloc_str("hello");
assert_eq!(pool.remaining(), 251);
}
#[test]
fn test_stack_pool_capacity() {
assert_eq!(StackPool::<1024>::capacity(), 1024);
assert_eq!(StackPool::<4096>::capacity(), 4096);
}
#[test]
fn test_stack_pool_request_isolation() {
let pool = StackPool::<256>::new();
let s1 = pool.alloc_str("request1");
assert_eq!(s1, "request1");
pool.reset();
let s2 = pool.alloc_str("request2");
assert_eq!(s2, "request2");
assert_eq!(pool.used_bytes(), 8);
}
#[test]
fn test_create_pool_stack() {
let config = MemPoolConfig::default();
let pool = create_pool(&config).unwrap();
let s = pool.alloc_str("hello");
assert_eq!(s, "hello");
}
#[test]
fn test_create_pool_none() {
let config = MemPoolConfig {
pool_type: MemPoolType::None,
capacity: 0,
};
assert!(create_pool(&config).is_none());
}
#[test]
fn test_create_pool_various_capacities() {
for &cap in &[1024, 2048, 4096, 8192, 16384, 32768, 65536] {
let config = MemPoolConfig {
pool_type: MemPoolType::Stack,
capacity: cap,
};
let pool = create_pool(&config).unwrap();
let s = pool.alloc_str("test");
assert_eq!(s, "test");
}
}
#[test]
fn test_mempool_config_default() {
let config = MemPoolConfig::default();
assert_eq!(config.pool_type, MemPoolType::Stack);
assert_eq!(config.capacity, 4096);
}
}