use flume::{Receiver, Sender};
#[derive(Debug, Clone)]
pub struct BufferPool {
tx: Sender<Vec<u8>>,
rx: Receiver<Vec<u8>>,
buffer_size: usize,
}
impl BufferPool {
#[must_use]
pub fn new(capacity: usize, buffer_size: usize) -> Self {
let (tx, rx) = flume::bounded(capacity);
for _ in 0..capacity {
let _ = tx.send(vec![0u8; buffer_size]);
}
Self { tx, rx, buffer_size }
}
#[inline]
pub async fn lease(&self) -> Vec<u8> {
self.rx.recv_async().await.unwrap_or_else(|_| vec![0u8; self.buffer_size])
}
#[inline]
#[must_use]
pub fn lease_sync(&self) -> Vec<u8> {
self.rx.recv().unwrap_or_else(|_| vec![0u8; self.buffer_size])
}
#[inline]
pub fn release(&self, mut buf: Vec<u8>) {
if buf.capacity() < self.buffer_size {
buf = vec![0u8; self.buffer_size];
} else {
buf.resize(self.buffer_size, 0);
}
let _ = self.tx.try_send(buf);
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::BufferPool;
#[test]
fn lease_sync_blocks_until_release_frees_a_slot() {
let pool = BufferPool::new(1, 16);
let leased = pool.lease_sync();
let pool2 = pool.clone();
let (done_tx, done_rx) = std::sync::mpsc::channel();
let handle = std::thread::spawn(move || {
let _buf = pool2.lease_sync(); done_tx.send(()).unwrap();
});
std::thread::sleep(Duration::from_millis(100));
assert!(
done_rx.try_recv().is_err(),
"lease_sync returned before any buffer was released — it isn't blocking"
);
pool.release(leased);
done_rx
.recv_timeout(Duration::from_secs(2))
.expect("lease_sync did not unblock after release");
handle.join().unwrap();
}
#[test]
fn release_never_blocks_when_pool_is_over_capacity() {
let pool = BufferPool::new(2, 16);
for _ in 0..10 {
pool.release(vec![0u8; 16]);
}
let buf = pool.lease_sync();
assert_eq!(buf.len(), 16);
}
#[test]
fn balanced_lease_release_cycle_never_exceeds_capacity() {
let pool = BufferPool::new(4, 16);
let mut in_flight = Vec::new();
for _ in 0..200 {
in_flight.push(pool.lease_sync());
if in_flight.len() > 2 {
pool.release(in_flight.remove(0));
}
}
for buf in in_flight {
pool.release(buf);
}
}
}