use bytes::{Bytes, BytesMut};
use std::collections::VecDeque;
use std::sync::Arc;
use tokio::sync::{Mutex, Semaphore};
use tracing::debug;
#[derive(Clone)]
pub struct BufferPool {
inner: Arc<Mutex<BufferPoolInner>>,
buffer_limit: Arc<Semaphore>,
buffer_size: usize,
}
struct BufferPoolInner {
available: VecDeque<BytesMut>,
allocated: usize,
bytes_allocated: usize,
max_buffers: usize,
buffer_size: usize,
}
#[allow(dead_code)] pub struct PooledBuffer {
buffer: Option<BytesMut>,
original_capacity: usize,
pool: BufferPool,
#[allow(dead_code)] permit: Option<tokio::sync::OwnedSemaphorePermit>,
}
impl BufferPool {
pub fn new(buffer_size: usize, initial_capacity: usize, max_buffers: usize) -> Self {
let initial_capacity = initial_capacity.min(max_buffers);
let mut available = VecDeque::with_capacity(initial_capacity);
let mut bytes_allocated = 0;
for _ in 0..initial_capacity {
let buffer = BytesMut::with_capacity(buffer_size);
bytes_allocated += buffer.capacity();
available.push_back(buffer);
}
let inner = BufferPoolInner {
available,
allocated: initial_capacity,
bytes_allocated,
max_buffers,
buffer_size,
};
let buffer_limit = Arc::new(Semaphore::new(max_buffers as usize));
if initial_capacity > 0 {
match buffer_limit
.clone()
.try_acquire_many_owned(initial_capacity as u32)
{
Ok(_) => {
}
Err(_) => {
panic!("Failed to acquire initial permits - semaphore capacity too small");
}
}
}
Self {
inner: Arc::new(Mutex::new(inner)),
buffer_limit,
buffer_size,
}
}
pub async fn get_buffer(&self) -> PooledBuffer {
let permit = match self.buffer_limit.clone().acquire_owned().await {
Ok(permit) => permit,
Err(_) => {
panic!("Buffer pool semaphore was closed");
}
};
let buffer = {
let mut inner = self.inner.lock().await;
inner.available.pop_front()
};
let buffer = if let Some(buffer) = buffer {
buffer
} else {
let new_buffer = BytesMut::with_capacity(self.buffer_size);
let mut inner = self.inner.lock().await;
inner.allocated += 1;
inner.bytes_allocated += new_buffer.capacity();
new_buffer
};
let original_capacity = buffer.capacity();
PooledBuffer {
buffer: Some(buffer),
original_capacity,
pool: self.clone(),
permit: Some(permit), }
}
pub async fn try_get_buffer(&self) -> Option<PooledBuffer> {
let permit = self.buffer_limit.try_acquire();
if permit.is_err() {
debug!("Buffer pool at capacity, can't allocate more buffers");
return None; }
let mut buffer = {
let mut inner = self.inner.lock().await;
inner.available.pop_front()
};
if buffer.is_none() {
let new_buffer = BytesMut::with_capacity(self.buffer_size);
let mut inner = self.inner.lock().await;
inner.allocated += 1;
inner.bytes_allocated += new_buffer.capacity();
buffer = Some(new_buffer);
}
let buffer = buffer.unwrap();
let original_capacity = buffer.capacity();
Some(PooledBuffer {
buffer: Some(buffer),
original_capacity,
pool: self.clone(),
permit: None, })
}
async fn return_buffer(&self, mut buffer: BytesMut, original_capacity: usize) {
let current_capacity = buffer.capacity();
if current_capacity == original_capacity {
buffer.clear();
let mut inner = self.inner.lock().await;
if inner.available.len() < inner.max_buffers {
inner.available.push_back(buffer);
} else {
inner.allocated -= 1;
inner.bytes_allocated -= original_capacity;
debug!("Dropping buffer due to pool capacity constraints");
}
} else {
let mut inner = self.inner.lock().await;
inner.bytes_allocated -= original_capacity;
inner.bytes_allocated += current_capacity;
debug!(
"Buffer resized from {} to {} bytes, not returning to pool",
original_capacity, current_capacity
);
}
}
pub async fn stats(&self) -> BufferPoolStats {
let inner = self.inner.lock().await;
BufferPoolStats {
allocated: inner.allocated,
available: inner.available.len(),
bytes_allocated: inner.bytes_allocated,
max_buffers: inner.max_buffers,
buffer_size: inner.buffer_size,
}
}
}
#[derive(Debug, Clone)]
pub struct BufferPoolStats {
pub allocated: usize,
pub available: usize,
pub bytes_allocated: usize,
pub max_buffers: usize,
pub buffer_size: usize,
}
impl PooledBuffer {
pub fn buffer(&self) -> Option<&BytesMut> {
self.buffer.as_ref()
}
pub fn buffer_mut(&mut self) -> Option<&mut BytesMut> {
self.buffer.as_mut()
}
pub fn into_inner(mut self) -> BytesMut {
self.buffer.take().unwrap()
}
pub fn freeze(mut self) -> Bytes {
let buffer = self.buffer.take().unwrap();
buffer.freeze()
}
}
impl Drop for PooledBuffer {
fn drop(&mut self) {
if let Some(buffer) = self.buffer.take() {
let pool = self.pool.clone();
let original_capacity = self.original_capacity;
tokio::spawn(async move {
pool.return_buffer(buffer, original_capacity).await;
});
}
}
}
pub struct SharedPools {
pub small: BufferPool,
pub medium: BufferPool,
pub large: BufferPool,
pub extra_large: BufferPool,
}
impl SharedPools {
pub fn new(max_buffers: usize) -> Self {
Self {
small: BufferPool::new(128, 1000, max_buffers),
medium: BufferPool::new(1024, 500, max_buffers / 2),
large: BufferPool::new(8 * 1024, 100, max_buffers / 10),
extra_large: BufferPool::new(64 * 1024, 10, max_buffers / 100),
}
}
pub async fn get_buffer_for_size(&self, size: usize) -> PooledBuffer {
if size <= 128 {
self.small.get_buffer().await
} else if size <= 1024 {
self.medium.get_buffer().await
} else if size <= 8 * 1024 {
self.large.get_buffer().await
} else {
self.extra_large.get_buffer().await
}
}
pub async fn try_get_buffer_for_size(&self, size: usize) -> Option<PooledBuffer> {
if size <= 128 {
self.small.try_get_buffer().await
} else if size <= 1024 {
self.medium.try_get_buffer().await
} else if size <= 8 * 1024 {
self.large.try_get_buffer().await
} else {
self.extra_large.try_get_buffer().await
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[tokio::test]
async fn test_buffer_pool() {
let pool = BufferPool::new(1024, 5, 10);
let mut buffers = Vec::new();
for _i in 0..5 {
let buffer = pool.get_buffer().await;
buffers.push(buffer);
}
let stats = pool.stats().await;
assert_eq!(stats.allocated, 5, "Should have 5 buffers allocated");
assert_eq!(stats.available, 0, "Should have 0 buffers available");
buffers.clear();
tokio::time::sleep(Duration::from_millis(50)).await;
let stats = pool.stats().await;
assert_eq!(stats.allocated, 5, "Should still have 5 buffers allocated");
assert_eq!(stats.available, 5, "Should have 5 buffers available now");
let mut buffers = Vec::new();
for i in 0..10 {
let buffer = match pool.get_buffer().await {
buffer => {
assert!(true, "Got buffer {} successfully", i);
buffer
}
};
buffers.push(buffer);
}
let stats = pool.stats().await;
assert_eq!(
stats.allocated, 10,
"Should have 10 buffers allocated total"
);
assert_eq!(stats.available, 0, "Should have 0 buffers available");
let result = tokio::time::timeout(Duration::from_millis(100), pool.get_buffer()).await;
assert!(result.is_err(), "11th buffer allocation should timeout");
if !buffers.is_empty() {
buffers.pop(); }
tokio::time::sleep(Duration::from_millis(50)).await;
let buffer = tokio::time::timeout(Duration::from_millis(100), pool.get_buffer()).await;
assert!(
buffer.is_ok(),
"Should be able to get a buffer after releasing one"
);
buffers.push(buffer.unwrap());
let result = tokio::time::timeout(Duration::from_millis(100), pool.get_buffer()).await;
assert!(
result.is_err(),
"11th buffer allocation should still timeout"
);
let stats = pool.stats().await;
assert_eq!(
stats.allocated, 10,
"Should have 10 buffers allocated total"
);
assert_eq!(stats.max_buffers, 10, "Max buffers should be 10");
}
}