use concurrent_queue::ConcurrentQueue;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
pub struct BytesPool {
pub(crate) queue: ConcurrentQueue<Vec<u8>>,
capacity: usize,
stats: PoolStats,
}
struct PoolStats {
allocations: AtomicUsize,
pool_hits: AtomicUsize,
}
impl BytesPool {
#[must_use]
pub fn new(capacity: usize, prealloc: usize) -> Self {
let queue = ConcurrentQueue::unbounded();
for _ in 0..prealloc {
queue.push(Vec::with_capacity(capacity)).ok();
}
Self {
queue,
capacity,
stats: PoolStats {
allocations: AtomicUsize::new(0),
pool_hits: AtomicUsize::new(0),
},
}
}
#[must_use]
pub fn acquire(self: &Arc<Self>) -> PooledBuffer {
if let Ok(mut buf) = self.queue.pop() {
self.stats.pool_hits.fetch_add(1, Ordering::Relaxed);
buf.clear();
PooledBuffer::new(buf, self.clone())
} else {
self.stats.allocations.fetch_add(1, Ordering::Relaxed);
PooledBuffer::new(Vec::with_capacity(self.capacity), self.clone())
}
}
#[must_use]
pub fn acquire_with_capacity(self: &Arc<Self>, capacity: usize) -> PooledBuffer {
if let Ok(mut buf) = self.queue.pop() {
self.stats.pool_hits.fetch_add(1, Ordering::Relaxed);
buf.clear();
if buf.capacity() < capacity {
self.queue.push(buf).ok();
self.stats.allocations.fetch_add(1, Ordering::Relaxed);
PooledBuffer::new(Vec::with_capacity(capacity), self.clone())
} else {
PooledBuffer::new(buf, self.clone())
}
} else {
self.stats.allocations.fetch_add(1, Ordering::Relaxed);
PooledBuffer::new(Vec::with_capacity(capacity), self.clone())
}
}
#[must_use]
pub fn stats(&self) -> PoolStatsSnapshot {
PoolStatsSnapshot {
allocations: self.stats.allocations.load(Ordering::Relaxed),
pool_hits: self.stats.pool_hits.load(Ordering::Relaxed),
}
}
#[must_use]
pub fn capacity(&self) -> usize {
self.capacity
}
}
#[derive(Debug, Clone, Copy)]
pub struct PoolStatsSnapshot {
pub allocations: usize,
pub pool_hits: usize,
}
impl PoolStatsSnapshot {
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn hit_ratio(&self) -> f64 {
let total = self.allocations + self.pool_hits;
if total == 0 {
return 1.0;
}
self.pool_hits as f64 / total as f64
}
}
pub struct PooledBuffer {
buffer: Vec<u8>,
pool: Arc<BytesPool>,
}
impl PooledBuffer {
fn new(buffer: Vec<u8>, pool: Arc<BytesPool>) -> Self {
Self { buffer, pool }
}
#[must_use]
pub fn owned(buffer: Vec<u8>) -> Self {
let capacity = buffer.capacity();
Self {
buffer,
pool: Arc::new(BytesPool::new(capacity, 0)),
}
}
#[must_use]
pub fn pool_stats(&self) -> PoolStatsSnapshot {
self.pool.stats()
}
}
impl std::ops::Deref for PooledBuffer {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.buffer
}
}
impl std::ops::DerefMut for PooledBuffer {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer
}
}
impl Drop for PooledBuffer {
fn drop(&mut self) {
self.buffer.clear();
self.pool.queue.push(std::mem::take(&mut self.buffer)).ok();
}
}
impl AsRef<[u8]> for PooledBuffer {
fn as_ref(&self) -> &[u8] {
&self.buffer
}
}
impl AsMut<[u8]> for PooledBuffer {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.buffer
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pool_reuses_buffers() {
let pool = Arc::new(BytesPool::new(1024, 2));
let stats = pool.stats();
assert_eq!(stats.allocations, 0);
assert_eq!(stats.pool_hits, 0);
let buf1 = pool.acquire();
let stats1 = pool.stats();
assert_eq!(stats1.pool_hits, 1);
assert_eq!(stats1.allocations, 0);
let _buf2 = pool.acquire();
let stats2 = pool.stats();
assert_eq!(stats2.pool_hits, 2);
assert_eq!(stats2.allocations, 0);
let _buf3 = pool.acquire();
let stats3 = pool.stats();
assert_eq!(stats3.pool_hits, 2);
assert_eq!(stats3.allocations, 1);
drop(buf1);
let _buf4 = pool.acquire();
let stats4 = pool.stats();
assert_eq!(stats4.pool_hits, 3);
assert_eq!(stats4.allocations, 1);
}
#[test]
fn test_buffer_cleared_on_return() {
let pool = Arc::new(BytesPool::new(1024, 1));
{
let mut buf = pool.acquire();
buf.extend_from_slice(b"hello world");
assert_eq!(&buf[..], b"hello world");
}
let buf2 = pool.acquire();
assert!(buf2.is_empty());
assert_eq!(buf2.capacity(), 1024); }
#[test]
fn test_concurrent_access() {
use std::thread;
let pool = Arc::new(BytesPool::new(1024, 10));
let mut handles = vec![];
for i in 0..10 {
let pool_clone = Arc::clone(&pool);
let handle = thread::spawn(move || {
let mut buf = pool_clone.acquire();
buf.extend_from_slice(format!("thread {}", i).as_bytes());
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let stats = pool.stats();
assert_eq!(stats.pool_hits, 10);
}
#[test]
fn test_hit_ratio() {
let pool = Arc::new(BytesPool::new(1024, 1));
let buf1 = pool.acquire(); drop(buf1);
let _buf2 = pool.acquire(); let _buf3 = pool.acquire(); let _buf4 = pool.acquire();
let stats = pool.stats();
assert_eq!(stats.pool_hits, 2);
assert_eq!(stats.allocations, 2);
assert!((stats.hit_ratio() - 0.5).abs() < f64::EPSILON);
}
#[test]
fn test_hit_ratio_empty() {
let pool = Arc::new(BytesPool::new(1024, 0));
let stats = pool.stats();
assert_eq!(stats.hit_ratio(), 1.0);
}
#[test]
fn test_pooled_buffer_deref() {
let pool = Arc::new(BytesPool::new(1024, 1));
let mut buf = pool.acquire();
buf.push(42);
buf.extend_from_slice(&[1, 2, 3]);
assert_eq!(buf.len(), 4);
assert_eq!(buf[0], 42);
}
#[test]
fn test_owned_buffer() {
let pool = Arc::new(BytesPool::new(1024, 1));
let stats_before = pool.stats();
let mut owned = PooledBuffer::owned(Vec::with_capacity(512));
owned.push(1);
let stats_after = pool.stats();
assert_eq!(stats_before.allocations, stats_after.allocations);
assert_eq!(stats_before.pool_hits, stats_after.pool_hits);
}
#[test]
fn test_acquire_with_capacity() {
let pool = Arc::new(BytesPool::new(1024, 1));
let buf = pool.acquire_with_capacity(4096);
assert!(buf.capacity() >= 4096);
}
}