use parking_lot::Mutex;
use std::sync::Arc;
pub struct BufPool {
free: Mutex<Vec<Vec<u8>>>,
capacity: usize,
buf_hint: usize,
}
impl BufPool {
pub fn new(capacity: usize, buf_hint: usize) -> Arc<Self> {
Arc::new(Self {
free: Mutex::new(Vec::with_capacity(capacity)),
capacity,
buf_hint,
})
}
pub fn take(&self) -> Vec<u8> {
match self.free.lock().pop() {
Some(mut b) => {
b.clear();
b
}
None => Vec::with_capacity(self.buf_hint),
}
}
pub fn put(&self, buf: Vec<u8>) {
if buf.capacity() > self.buf_hint * 4 {
return;
}
let mut free = self.free.lock();
if free.len() < self.capacity {
free.push(buf);
}
}
}
pub struct Pooled {
buf: Option<Vec<u8>>,
pool: Arc<BufPool>,
}
impl Pooled {
pub fn new(pool: Arc<BufPool>) -> Self {
let buf = pool.take();
Self {
buf: Some(buf),
pool,
}
}
}
impl std::ops::Deref for Pooled {
type Target = Vec<u8>;
fn deref(&self) -> &Vec<u8> {
self.buf.as_ref().expect("buffer taken only on drop")
}
}
impl std::ops::DerefMut for Pooled {
fn deref_mut(&mut self) -> &mut Vec<u8> {
self.buf.as_mut().expect("buffer taken only on drop")
}
}
impl Drop for Pooled {
fn drop(&mut self) {
if let Some(b) = self.buf.take() {
self.pool.put(b);
}
}
}
pub struct ObjPool<T> {
free: Mutex<Vec<T>>,
capacity: usize,
}
impl<T> ObjPool<T> {
pub fn new(capacity: usize) -> Arc<Self> {
Arc::new(Self {
free: Mutex::new(Vec::with_capacity(capacity)),
capacity,
})
}
pub fn take_or<F: FnOnce() -> T>(&self, make: F) -> T {
match self.free.lock().pop() {
Some(o) => o,
None => make(),
}
}
pub fn put(&self, obj: T) {
let mut free = self.free.lock();
if free.len() < self.capacity {
free.push(obj);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn obj_pool_reuses_and_bounds() {
let pool: Arc<ObjPool<Vec<u32>>> = ObjPool::new(2);
let made = std::sync::atomic::AtomicUsize::new(0);
let mk = || {
made.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Vec::new()
};
let a = pool.take_or(mk);
let b = pool.take_or(mk);
assert_eq!(made.load(std::sync::atomic::Ordering::SeqCst), 2);
pool.put(a);
pool.put(b);
let _ = pool.take_or(mk);
let _ = pool.take_or(mk);
assert_eq!(made.load(std::sync::atomic::Ordering::SeqCst), 2);
for _ in 0..8 {
pool.put(Vec::new());
}
assert_eq!(pool.free.lock().len(), 2);
}
#[test]
fn buffers_are_recycled_not_reallocated() {
let pool = BufPool::new(4, 1024);
let ptr = {
let mut b = Pooled::new(pool.clone());
b.extend_from_slice(&[1u8; 512]);
b.as_ptr()
};
let b2 = Pooled::new(pool.clone());
assert!(b2.is_empty(), "recycled buffers come back empty");
assert_eq!(b2.as_ptr(), ptr, "the same allocation came back");
}
#[test]
fn pool_respects_its_capacity() {
let pool = BufPool::new(2, 128);
let bufs: Vec<_> = (0..8).map(|_| Pooled::new(pool.clone())).collect();
drop(bufs);
assert_eq!(pool.free.lock().len(), 2);
}
#[test]
fn oversized_buffers_are_dropped_rather_than_retained() {
let pool = BufPool::new(4, 100);
{
let mut b = Pooled::new(pool.clone());
b.resize(10_000, 0);
}
assert_eq!(pool.free.lock().len(), 0);
}
#[test]
fn concurrent_take_and_put_is_sound() {
let pool = BufPool::new(8, 4096);
let threads: Vec<_> = (0..8)
.map(|_| {
let pool = pool.clone();
std::thread::spawn(move || {
for _ in 0..1000 {
let mut b = Pooled::new(pool.clone());
b.extend_from_slice(&[7u8; 100]);
assert_eq!(b.len(), 100);
}
})
})
.collect();
for t in threads {
t.join().unwrap();
}
assert!(pool.free.lock().len() <= 8);
}
}