use parking_lot::Mutex;
const DEFAULT_MAX_POOL_BYTES: usize = 64 * 1024 * 1024;
pub struct BufferPool<T> {
pool: Mutex<Vec<Vec<T>>>,
max_size: usize,
max_pool_bytes: usize,
current_pool_bytes: Mutex<usize>,
}
impl<T> BufferPool<T> {
pub fn new(max_size: usize) -> Self {
Self {
pool: Mutex::new(Vec::with_capacity(max_size)),
max_size,
max_pool_bytes: DEFAULT_MAX_POOL_BYTES,
current_pool_bytes: Mutex::new(0),
}
}
pub fn with_max_bytes(max_size: usize, max_pool_bytes: usize) -> Self {
Self {
pool: Mutex::new(Vec::with_capacity(max_size)),
max_size,
max_pool_bytes,
current_pool_bytes: Mutex::new(0),
}
}
pub fn pooled_bytes(&self) -> usize {
*self.current_pool_bytes.lock()
}
pub fn get(&self, min_capacity: usize) -> Vec<T> {
let mut pool = self.pool.lock();
if let Some(mut buf) = pool.pop() {
let buf_bytes = std::mem::size_of::<T>() * buf.capacity();
*self.current_pool_bytes.lock() -= buf_bytes;
if buf.capacity() < min_capacity {
buf.reserve(min_capacity - buf.len());
}
buf
} else {
Vec::with_capacity(min_capacity)
}
}
pub fn put(&self, mut buf: Vec<T>) {
buf.clear();
let buf_bytes = std::mem::size_of::<T>() * buf.capacity();
let mut pool = self.pool.lock();
let mut bytes = self.current_pool_bytes.lock();
if pool.len() < self.max_size && *bytes + buf_bytes <= self.max_pool_bytes {
*bytes += buf_bytes;
pool.push(buf);
}
}
pub fn available(&self) -> usize {
self.pool.lock().len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_and_put() {
let pool = BufferPool::<f64>::new(2);
let mut buf = pool.get(64);
buf.extend_from_slice(&[1.0, 2.0, 3.0]);
assert_eq!(buf.len(), 3);
let cap = buf.capacity();
pool.put(buf);
assert_eq!(pool.available(), 1);
let buf2 = pool.get(1);
assert!(buf2.capacity() >= cap);
assert!(buf2.is_empty());
assert_eq!(pool.available(), 0);
}
#[test]
fn pool_max_size_respected() {
let pool = BufferPool::<f64>::new(1);
pool.put(vec![1.0, 2.0]);
pool.put(vec![3.0, 4.0]); assert_eq!(pool.available(), 1);
}
#[test]
fn min_capacity_honored() {
let pool = BufferPool::<f64>::new(2);
pool.put(Vec::with_capacity(4));
let buf = pool.get(100);
assert!(buf.capacity() >= 100);
}
#[test]
fn byte_budget_rejects_oversized_buffer() {
let pool = BufferPool::<f64>::with_max_bytes(4, 64);
pool.put(Vec::<f64>::with_capacity(100));
assert_eq!(pool.available(), 0, "oversized buffer should be dropped");
assert_eq!(pool.pooled_bytes(), 0);
}
#[test]
fn byte_budget_accepts_small_buffer() {
let pool = BufferPool::<f64>::with_max_bytes(4, 1024);
pool.put(Vec::<f64>::with_capacity(8)); assert_eq!(pool.available(), 1);
assert_eq!(pool.pooled_bytes(), 64);
}
#[test]
fn byte_budget_decremented_on_get() {
let pool = BufferPool::<f64>::with_max_bytes(4, 1024);
pool.put(Vec::<f64>::with_capacity(8));
assert_eq!(pool.pooled_bytes(), 64);
let _buf = pool.get(1);
assert_eq!(pool.pooled_bytes(), 0);
}
}