use std::sync::Arc;
use parking_lot::Mutex;
pub const MAX_PACKET_SIZE: usize = 1024;
pub struct PooledBuffer {
data: Vec<u8>,
pool: Arc<Mutex<Vec<Vec<u8>>>>,
}
impl PooledBuffer {
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [u8] {
&mut self.data
}
#[inline]
pub fn as_slice(&self) -> &[u8] {
&self.data
}
#[inline]
pub fn resize(&mut self, len: usize) {
debug_assert!(len <= MAX_PACKET_SIZE);
self.data.resize(len, 0);
}
#[inline]
pub fn clear(&mut self) {
self.data.clear();
}
#[inline]
pub const fn len(&self) -> usize {
self.data.len()
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
impl Drop for PooledBuffer {
fn drop(&mut self) {
let mut buffer = std::mem::take(&mut self.data);
buffer.clear();
if buffer.capacity() <= MAX_PACKET_SIZE * 2 {
self.pool.lock().push(buffer);
}
}
}
impl AsRef<[u8]> for PooledBuffer {
fn as_ref(&self) -> &[u8] {
&self.data
}
}
impl AsMut<[u8]> for PooledBuffer {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.data
}
}
#[derive(Clone)]
pub struct BufferPool {
buffers: Arc<Mutex<Vec<Vec<u8>>>>,
initial_capacity: usize,
}
impl BufferPool {
pub fn new(pool_size: usize) -> Self {
let buffers: Vec<Vec<u8>> = (0..pool_size)
.map(|_| Vec::with_capacity(MAX_PACKET_SIZE))
.collect();
Self {
buffers: Arc::new(Mutex::new(buffers)),
initial_capacity: pool_size,
}
}
pub fn get(&self) -> PooledBuffer {
let data = self.buffers.lock().pop().unwrap_or_else(|| {
Vec::with_capacity(MAX_PACKET_SIZE)
});
PooledBuffer {
data,
pool: Arc::clone(&self.buffers),
}
}
pub fn get_zeroed(&self, len: usize) -> PooledBuffer {
let mut buffer = self.get();
buffer.data.resize(len, 0);
buffer
}
pub fn available(&self) -> usize {
self.buffers.lock().len()
}
pub const fn capacity(&self) -> usize {
self.initial_capacity
}
}
impl Default for BufferPool {
fn default() -> Self {
Self::new(64)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_get_and_return_buffers_to_pool() {
let pool = BufferPool::new(4);
assert_eq!(pool.available(), 4);
let buf1 = pool.get();
assert_eq!(pool.available(), 3);
let buf2 = pool.get();
assert_eq!(pool.available(), 2);
drop(buf1);
assert_eq!(pool.available(), 3);
drop(buf2);
assert_eq!(pool.available(), 4);
}
#[test]
fn should_allocate_new_buffer_when_pool_exhausted() {
let pool = BufferPool::new(2);
let _buf1 = pool.get();
let _buf2 = pool.get();
assert_eq!(pool.available(), 0);
let _buf3 = pool.get();
assert_eq!(pool.available(), 0);
}
#[test]
fn should_support_resize_and_clear_operations() {
let pool = BufferPool::new(1);
let mut buf = pool.get();
assert!(buf.is_empty());
buf.resize(100);
assert_eq!(buf.len(), 100);
buf.as_mut_slice()[0] = 42;
assert_eq!(buf.as_slice()[0], 42);
buf.clear();
assert!(buf.is_empty());
}
#[test]
fn should_return_zeroed_buffer_of_specified_length() {
let pool = BufferPool::new(1);
let buf = pool.get_zeroed(50);
assert_eq!(buf.len(), 50);
assert!(buf.as_slice().iter().all(|&b| b == 0));
}
#[test]
fn should_share_underlying_pool_when_cloned() {
let pool1 = BufferPool::new(4);
let pool2 = pool1.clone();
let _buf = pool1.get();
assert_eq!(pool2.available(), 3);
}
}