use crate::{Buffer, Error, Result, Stream, View};
use std::{
collections::BTreeMap,
sync::{Arc, Mutex},
};
const DEFAULT_LIMIT: usize = 512 << 20;
#[derive(Default)]
struct FreeList {
cached: usize,
blocks: BTreeMap<usize, Vec<Buffer>>,
stream: Option<usize>,
}
pub struct BufferPool {
limit: usize,
free: Mutex<FreeList>,
}
impl Default for BufferPool {
fn default() -> Self {
Self::with_limit(DEFAULT_LIMIT)
}
}
impl BufferPool {
#[must_use]
pub fn new() -> Arc<Self> {
Arc::new(Self::default())
}
#[must_use]
pub fn with_limit(limit: usize) -> Self {
Self {
limit,
free: Mutex::new(FreeList::default()),
}
}
pub fn acquire(self: &Arc<Self>, stream: &Stream, bytes: usize) -> Result<PooledBuffer> {
if bytes == 0 {
return Err(Error::Message("empty pooled allocation".into()));
}
let mut free = self.free.lock().unwrap_or_else(|error| error.into_inner());
match free.stream {
Some(owner) if owner != stream.id() => {
return Err(Error::Message(
"a buffer pool serves one stream because reuse follows that queue's order"
.into(),
));
}
Some(_) => {}
None => free.stream = Some(stream.id()),
}
let reusable = free
.blocks
.range_mut(bytes..=bytes.saturating_mul(2))
.next()
.map(|(&size, blocks)| (size, blocks.pop()));
if let Some((size, Some(buffer))) = reusable {
free.cached -= size;
if free.blocks[&size].is_empty() {
free.blocks.remove(&size);
}
return Ok(PooledBuffer {
buffer: Some(buffer),
pool: self.clone(),
});
}
drop(free);
Ok(PooledBuffer {
buffer: Some(stream.allocate(bytes)?),
pool: self.clone(),
})
}
#[must_use]
pub fn cached_bytes(&self) -> usize {
self.free
.lock()
.unwrap_or_else(|error| error.into_inner())
.cached
}
fn release(&self, buffer: Buffer) {
let mut free = self.free.lock().unwrap_or_else(|error| error.into_inner());
if buffer.bytes() > self.limit.saturating_sub(free.cached) {
return;
}
free.cached += buffer.bytes();
free.blocks.entry(buffer.bytes()).or_default().push(buffer);
}
}
pub struct PooledBuffer {
buffer: Option<Buffer>,
pool: Arc<BufferPool>,
}
impl PooledBuffer {
#[must_use]
pub fn buffer(&self) -> &Buffer {
self.buffer.as_ref().expect("live pooled buffer")
}
#[must_use]
pub fn binding(&self) -> View<'_> {
self.buffer().binding()
}
}
impl Drop for PooledBuffer {
fn drop(&mut self) {
if let Some(buffer) = self.buffer.take() {
self.pool.release(buffer);
}
}
}