use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::size_class::{ClassTable, SizeClass};
use crate::tls::TlsState;
static NEXT_POOL_ID: AtomicU64 = AtomicU64::new(1);
#[derive(Debug)]
pub(crate) struct PoolState {
pub id: u64,
pub table: ClassTable,
pub tls_cache_size: usize,
pub min_buffer_size: usize,
pub pinned_memory: bool,
pub batch_size: usize,
}
#[derive(Clone, Debug)]
pub struct BufferPool {
pub(crate) state: Arc<PoolState>,
}
impl BufferPool {
#[inline]
pub fn new() -> Self {
crate::Builder::default().build()
}
#[inline]
pub fn builder() -> crate::Builder {
crate::Builder::default()
}
pub(crate) fn from_state(state: PoolState) -> Self {
Self { state: Arc::new(state) }
}
pub(crate) fn next_id() -> u64 {
NEXT_POOL_ID.fetch_add(1, Ordering::Relaxed)
}
#[inline]
#[must_use]
pub fn get(&self, size: usize) -> crate::PooledBuffer {
let Some((class_idx, class)) = self.state.table.route(size) else {
return crate::PooledBuffer::new(ClassTable::oversize(size), self.clone(), u8::MAX);
};
let tls_result = TlsState::with(|state| {
if !state.owns(self.state.id) {
state.bind(self.state.id, self.state.tls_cache_size);
}
if let Some(buf) = state.caches[class_idx].pop() {
return Some(buf);
}
state.refill(class_idx, class, self.state.batch_size)
});
let ci = class_idx as u8;
if let Some(mut buf) = tls_result {
SizeClass::resize(&mut buf, size);
return crate::PooledBuffer::new(buf, self.clone(), ci);
}
crate::PooledBuffer::new(class.allocate(size), self.clone(), ci)
}
#[inline(always)]
pub(crate) fn put(&self, mut buffer: Vec<u8>, class_hint: u8) {
buffer.clear();
if class_hint == u8::MAX {
return;
}
let cap = buffer.capacity();
if cap < self.state.min_buffer_size {
return;
}
let class_idx = if cap >= ClassTable::boundary(class_hint as usize) {
class_hint as usize
} else {
let Some((idx, _)) = self.state.table.route_capacity(cap) else {
return;
};
idx
};
self.pin(&mut buffer);
let overflow = TlsState::with(|state| {
if !state.owns(self.state.id) {
state.bind(self.state.id, self.state.tls_cache_size);
}
let class = &self.state.table[class_idx];
if state.caches[class_idx].len() >= state.limit {
state.spill(class_idx, class, self.state.batch_size);
}
if state.caches[class_idx].len() < state.limit {
state.caches[class_idx].push(buffer);
return None;
}
Some(buffer)
});
if let Some(buf) = overflow {
let _ = self.state.table[class_idx].push(buf);
}
}
pub fn preallocate(&self, count: usize, size: usize) {
let Some((_, class)) = self.state.table.route(size) else {
return;
};
for _ in 0..count {
let mut buf = Vec::with_capacity(class.class_size);
self.pin(&mut buf);
if class.push(buf).is_err() {
break;
}
}
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.state.table.total_buffered()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.state.table.all_empty()
}
pub fn clear(&self) {
self.state.table.clear_all();
}
#[inline(always)]
fn pin(&self, buffer: &mut Vec<u8>) {
if !self.state.pinned_memory {
return;
}
if buffer.capacity() == 0 {
return;
}
unsafe { buffer.set_len(buffer.capacity()) };
let _ = region::lock(buffer.as_ptr(), buffer.len());
buffer.clear();
}
}
impl Default for BufferPool {
fn default() -> Self {
Self::new()
}
}