use core::{
cell::UnsafeCell,
mem::{replace, ManuallyDrop, MaybeUninit},
sync::atomic::{AtomicUsize, Ordering},
};
use alloc::vec::Vec;
#[cfg(feature = "nightly")]
use alloc::alloc::{Allocator, Global};
#[cfg(not(feature = "nightly"))]
use allocator_api2::alloc::{Allocator, Global};
use parking_lot::RwLock;
use crate::local::BlinkAlloc;
struct Inner<A: Allocator> {
pop_array: Vec<UnsafeCell<ManuallyDrop<BlinkAlloc<A>>>>,
next_pop: AtomicUsize,
push_array: Vec<UnsafeCell<MaybeUninit<BlinkAlloc<A>>>>,
next_push: AtomicUsize,
}
unsafe impl<A> Sync for Inner<A>
where
A: Allocator,
BlinkAlloc<A>: Send,
{
}
pub struct BlinkAllocCache<A: Allocator = Global> {
inner: RwLock<Inner<A>>,
}
impl<A> Default for BlinkAllocCache<A>
where
A: Allocator,
{
fn default() -> Self {
Self::new()
}
}
impl<A> BlinkAllocCache<A>
where
A: Allocator,
{
pub const fn new() -> Self {
BlinkAllocCache {
inner: RwLock::new(Inner {
pop_array: Vec::new(),
next_pop: AtomicUsize::new(0),
push_array: Vec::new(),
next_push: AtomicUsize::new(0),
}),
}
}
pub fn pop(&self) -> Option<BlinkAlloc<A>> {
let inner = self.inner.read();
if !inner.pop_array.is_empty() {
let idx = inner.next_pop.fetch_add(1, Ordering::Acquire);
if idx < inner.pop_array.len() {
let blink = unsafe { ManuallyDrop::take(&mut *inner.pop_array[idx].get()) };
return Some(blink);
}
prevent_overflow(&inner.next_pop, idx, inner.pop_array.len());
}
if inner.next_push.load(Ordering::Relaxed) == 0 {
return None;
}
drop(inner);
let mut inner = self.inner.write();
Self::flush(&mut inner);
inner
.pop_array
.pop()
.map(|cell| ManuallyDrop::into_inner(cell.into_inner()))
}
pub fn push(&self, blink: BlinkAlloc<A>) {
let inner = self.inner.read();
if !inner.push_array.is_empty() {
let idx = inner.next_push.fetch_add(1, Ordering::Acquire);
if idx < inner.push_array.len() {
MaybeUninit::write(unsafe { &mut *inner.push_array[idx].get() }, blink);
return;
}
prevent_overflow(&inner.next_push, idx, inner.push_array.len());
}
drop(inner);
let mut inner = self.inner.write();
Self::flush(&mut inner);
inner
.pop_array
.push(UnsafeCell::new(ManuallyDrop::new(blink)));
}
fn flush(inner: &mut Inner<A>) {
let pushed = replace(inner.next_push.get_mut(), 0).min(inner.push_array.len());
let popped = replace(inner.next_pop.get_mut(), 0).min(inner.pop_array.len());
let pushed_iter = inner.push_array.drain(..pushed);
inner.pop_array.splice(
..popped,
pushed_iter.map(|cell| {
UnsafeCell::new(ManuallyDrop::new(unsafe {
cell.into_inner().assume_init()
}))
}),
);
}
}
fn prevent_overflow(atomic: &AtomicUsize, current: usize, upper: usize) {
#[cold]
fn cold_store(atomic: &AtomicUsize, upper: usize) {
atomic.store(upper, Ordering::Relaxed);
}
if current >= isize::MAX as usize {
cold_store(atomic, upper);
}
}