use core::mem::ManuallyDrop;
use core::ops::{Deref, DerefMut};
pub(crate) const SPARES: usize = 64;
pub(crate) trait Reused: Sized + 'static {
fn with_spares<R>(f: impl FnOnce(&mut Vec<Box<Self>>) -> R) -> Option<R>;
}
macro_rules! reused {
($($ty:ty),* $(,)?) => {$(
impl $crate::buf::spare::Reused for $ty {
fn with_spares<R>(f: impl FnOnce(&mut Vec<Box<Self>>) -> R) -> Option<R> {
thread_local! {
static SPARES: core::cell::RefCell<Vec<Box<$ty>>> =
const { core::cell::RefCell::new(Vec::new()) };
}
SPARES
.try_with(|spares| spares.try_borrow_mut().ok().map(|mut s| f(&mut s)))
.ok()
.flatten()
}
}
)*};
}
pub(crate) use reused;
pub(crate) struct Spare<T: Reused>(ManuallyDrop<Box<T>>);
impl<T: Reused> Spare<T> {
pub(crate) fn new(value: T) -> Spare<T> {
let boxed = match T::with_spares(Vec::pop).flatten() {
Some(mut spare) => {
*spare = value;
spare
}
None => Box::new(value),
};
Spare(ManuallyDrop::new(boxed))
}
}
impl<T: Reused> Deref for Spare<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T: Reused> DerefMut for Spare<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T: Reused> Drop for Spare<T> {
fn drop(&mut self) {
let mut boxed = Some(unsafe { ManuallyDrop::take(&mut self.0) });
T::with_spares(|spares| {
if spares.len() < SPARES
&& let Some(spare) = boxed.take()
{
spares.push(spare);
}
});
drop(boxed);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq)]
struct Probe(u64);
reused!(Probe);
fn spares() -> usize {
Probe::with_spares(|s| s.len()).unwrap()
}
#[test]
fn a_dropped_box_is_the_next_ones() {
Probe::with_spares(Vec::clear);
let first = Spare::new(Probe(1));
let at = core::ptr::from_ref::<Probe>(&first) as usize;
drop(first);
assert_eq!(spares(), 1);
let second = Spare::new(Probe(2));
assert_eq!(
core::ptr::from_ref::<Probe>(&second) as usize,
at,
"the box was reused"
);
assert_eq!(*second, Probe(2));
assert_eq!(spares(), 0);
}
#[test]
fn a_thread_keeps_at_most_its_bound() {
Probe::with_spares(Vec::clear);
let many: Vec<_> = (0..SPARES as u64 + 10)
.map(|i| Spare::new(Probe(i)))
.collect();
drop(many);
assert_eq!(spares(), SPARES);
std::thread::spawn(|| assert_eq!(spares(), 0))
.join()
.unwrap();
}
}