use bumpalo::Bump;
use std::cell::RefCell;
const MAX_POOLED: usize = 8;
const MAX_RETAINED_BYTES: usize = 4 * 1024 * 1024;
thread_local! {
static POOL: RefCell<Vec<Bump>> = const { RefCell::new(Vec::new()) };
}
pub(crate) fn take() -> Bump {
POOL.try_with(|pool| pool.borrow_mut().pop())
.ok()
.flatten()
.unwrap_or_default()
}
pub(crate) fn give(mut arena: Bump) {
arena.reset();
if arena.allocated_bytes() > MAX_RETAINED_BYTES {
return;
}
let _ = POOL.try_with(|pool| {
let mut pool = pool.borrow_mut();
if pool.len() < MAX_POOLED {
pool.push(arena);
}
});
}
pub(crate) fn used(arena: &Bump) -> usize {
arena
.allocated_bytes()
.saturating_sub(arena.chunk_capacity())
}
pub(crate) fn with_arena<R>(f: impl FnOnce(&Bump) -> R) -> R {
let arena = take();
let result = f(&arena);
give(arena);
result
}
#[cfg(test)]
pub(crate) fn pool_len() -> usize {
POOL.with(|pool| pool.borrow().len())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_returned_arena_is_reset_and_pooled() {
assert_eq!(pool_len(), 0);
with_arena(|a| {
a.alloc_str("hello");
});
assert_eq!(pool_len(), 1);
with_arena(|a| assert!(a.allocated_bytes() > 0));
assert_eq!(pool_len(), 1);
}
#[test]
fn nested_calls_get_distinct_arenas() {
let (outer, inner) = with_arena(|a| {
let outer = std::ptr::from_ref(a);
let inner = with_arena(std::ptr::from_ref);
(outer, inner)
});
assert_ne!(outer, inner);
assert_eq!(pool_len(), 2);
}
#[test]
fn at_most_eight_arenas_are_pooled() {
fn nest(depth: usize) {
if depth > 0 {
with_arena(|_| nest(depth - 1));
}
}
nest(12);
assert_eq!(pool_len(), 8);
}
#[test]
fn an_arena_still_over_four_mib_after_reset_is_dropped() {
with_arena(|a| {
a.alloc_slice_fill_copy(8 * 1024 * 1024, 0u8);
});
assert_eq!(pool_len(), 0);
}
#[test]
fn a_given_arena_comes_back_reset() {
let a = take();
a.alloc_slice_fill_copy(1000, 7u8);
assert!(used(&a) >= 1000);
give(a);
let b = take();
assert!(used(&b) < 64, "reset on the way in: {}", used(&b));
give(b);
}
#[test]
fn used_counts_allocations_not_chunks() {
let mut a = Bump::with_capacity(1 << 20);
assert!(used(&a) < 64);
a.alloc_slice_fill_copy(10_000, 1u8);
let after = used(&a);
assert!((10_000..11_000).contains(&after), "{after}");
a.reset();
assert!(used(&a) < 64);
}
}