#![cfg_attr(feature = "allocator_api", feature(allocator_api))]
extern crate alloc;
use std::{
cell::UnsafeCell,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
};
use thread_local::ThreadLocal;
mod error;
pub use error::ResetError;
#[cfg(any(feature = "allocator_api", feature = "allocator-api2"))]
mod alloc_api;
#[cfg(any(feature = "allocator_api", feature = "allocator-api2"))]
pub use alloc_api::Allocator;
struct ThreadGuard {
alive: Arc<AtomicBool>,
}
impl ThreadGuard {
fn new() -> Self {
Self {
alive: Arc::new(AtomicBool::new(true)),
}
}
}
impl Drop for ThreadGuard {
fn drop(&mut self) {
self.alive.store(false, Ordering::Release);
}
}
thread_local! {
static THREAD_GUARD: ThreadGuard = ThreadGuard::new();
}
#[derive(Default, Clone)]
pub struct Bump {
inner: Arc<BumpInner>,
}
impl Bump {
pub fn new() -> Self {
Self::default()
}
pub fn builder() -> BumpBuilder {
BumpBuilder::new()
}
#[inline]
pub fn local(&self) -> &BumpLocal {
self.inner.local()
}
#[inline]
pub fn reset_all(&mut self) -> Result<(), ResetError> {
match Arc::get_mut(&mut self.inner) {
Some(inner) => {
inner.reset_all();
Ok(())
}
None => Err(ResetError),
}
}
}
#[derive(Default)]
pub struct BumpBuilder {
threads_capacity: Option<usize>,
bump_alloc_limit: Option<usize>,
bump_capacity: usize,
}
impl BumpBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn threads_capacity(mut self, capacity: usize) -> Self {
self.threads_capacity = Some(capacity);
self
}
pub fn bump_allocation_limit(mut self, limit: usize) -> Self {
self.bump_alloc_limit = Some(limit);
self
}
pub fn bump_capacity(mut self, capacity: usize) -> Self {
self.bump_capacity = capacity;
self
}
pub fn build(self) -> Bump {
Bump {
inner: Arc::new(BumpInner {
locals: match self.threads_capacity {
Some(cap) => ThreadLocal::with_capacity(cap),
None => ThreadLocal::new(),
},
capacity: self.bump_capacity,
alloc_limit: self.bump_alloc_limit,
}),
}
}
}
pub struct BumpLocal {
inner: UnsafeCell<Option<BumpLocalInner>>,
}
impl BumpLocal {
fn new(capacity: usize, limit: Option<usize>, thread_alive: Arc<AtomicBool>) -> Self {
let bump = bumpalo::Bump::with_capacity(capacity);
bump.set_allocation_limit(limit);
Self {
inner: UnsafeCell::new(Some(BumpLocalInner {
inner: bump,
thread_alive,
})),
}
}
#[inline]
pub fn as_inner(&self) -> &bumpalo::Bump {
unsafe { &(*self.inner.get()).as_ref().unwrap().inner }
}
#[inline]
pub fn reset(&self) {
unsafe {
(*self.inner.get()).as_mut().unwrap().inner.reset();
}
}
#[inline]
fn needs_init(&self) -> bool {
unsafe { (*self.inner.get()).is_none() }
}
#[cold]
fn init(&self, capacity: usize, limit: Option<usize>, thread_alive: Arc<AtomicBool>) {
let bump = bumpalo::Bump::with_capacity(capacity);
bump.set_allocation_limit(limit);
unsafe {
*self.inner.get() = Some(BumpLocalInner {
inner: bump,
thread_alive,
})
}
}
#[cold]
fn clear(&mut self) {
#[cold]
fn drop_inner(bump: &mut BumpLocal) {
unsafe {
let _ = (*bump.inner.get()).take();
}
}
let inner = unsafe { &*self.inner.get() };
let Some(inner) = inner.as_ref() else {
return;
};
if inner.thread_alive.load(Ordering::Acquire) {
self.reset();
} else {
drop_inner(self);
}
}
}
struct BumpLocalInner {
inner: bumpalo::Bump,
thread_alive: Arc<AtomicBool>,
}
#[derive(Default)]
struct BumpInner {
locals: ThreadLocal<BumpLocal>,
capacity: usize,
alloc_limit: Option<usize>,
}
impl BumpInner {
#[inline]
fn local(&self) -> &BumpLocal {
let bump = self.locals.get_or(|| {
let thread_alive = THREAD_GUARD.with(|guard| guard.alive.clone());
BumpLocal::new(self.capacity, self.alloc_limit, thread_alive)
});
if bump.needs_init() {
self.reinit_local(bump);
}
bump
}
#[cold]
fn reinit_local(&self, bump: &BumpLocal) {
let thread_alive = THREAD_GUARD.with(|guard| guard.alive.clone());
bump.init(self.capacity, self.alloc_limit, thread_alive);
}
#[inline]
fn reset_all(&mut self) {
for local in self.locals.iter_mut() {
local.clear();
}
}
}
#[cfg(test)]
mod tests {
use std::thread;
use super::*;
#[test]
fn thread_guard_sets_alive_false_on_drop() {
let handle = thread::spawn(move || THREAD_GUARD.with(|g| g.alive.clone()));
let alive = handle.join().unwrap();
assert!(!alive.load(Ordering::Acquire));
}
#[test]
fn reset_resets_alive_thread() {
let mut bump = Bump::builder().bump_capacity(100).build();
let (tx, rx) = std::sync::mpsc::channel();
let handle = {
let bump = bump.clone();
thread::spawn(move || {
let _ = bump.local().as_inner().alloc(1_u8);
let capacity_before = bump.local().as_inner().chunk_capacity();
drop(bump);
tx.send(capacity_before).unwrap();
thread::park();
})
};
let capacity_before = rx.recv().unwrap();
bump.reset_all().unwrap();
let inner = Arc::get_mut(&mut bump.inner).unwrap();
let locals: Vec<_> = inner.locals.iter_mut().collect();
assert_eq!(locals.len(), 1);
let local = locals.first().unwrap();
assert!(!local.needs_init());
assert!(local.as_inner().chunk_capacity() > capacity_before);
handle.thread().unpark();
handle.join().unwrap();
}
#[test]
fn reset_drops_dead_thread_bump() {
let mut bump = Bump::builder().bump_capacity(100).build();
let handle = {
let bump = bump.clone();
thread::spawn(move || {
let _ = bump.local().as_inner().alloc(1_u8);
})
};
handle.join().unwrap();
bump.reset_all().unwrap();
let inner = Arc::get_mut(&mut bump.inner).unwrap();
let locals: Vec<_> = inner.locals.iter_mut().collect();
assert_eq!(locals.len(), 1);
let local = locals.first().unwrap();
assert!(local.needs_init());
}
}