#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
use core::mem;
use arrayvec::ArrayVec;
use reclaim::{Reclaim, Retired};
use crate::epoch::PossibleAge;
use crate::EPOCH_CACHE_SIZE;
const BAG_POOL_SIZE: usize = 16;
#[derive(Debug)]
pub struct BagPool<R: Reclaim + 'static>(ArrayVec<[Box<BagNode<R>>; BAG_POOL_SIZE]>);
impl<R: Reclaim + 'static> Default for BagPool<R> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<R: Reclaim + 'static> BagPool<R> {
#[inline]
pub fn new() -> Self {
Self(ArrayVec::default())
}
#[inline]
pub fn with_bags() -> Self {
Self((0..BAG_POOL_SIZE).map(|_| BagNode::boxed()).collect())
}
#[inline]
fn allocate_bag(&mut self) -> Box<BagNode<R>> {
self.0.pop().unwrap_or_else(BagNode::boxed)
}
#[inline]
fn recycle_bag(&mut self, bag: Box<BagNode<R>>) {
debug_assert!(bag.is_empty());
if let Err(cap) = self.0.try_push(bag) {
mem::drop(cap.element());
}
}
}
const BAG_QUEUE_COUNT: usize = 3;
#[derive(Debug)]
pub struct EpochBagQueues<R: Reclaim + 'static> {
queues: [BagQueue<R>; BAG_QUEUE_COUNT],
curr_idx: usize,
}
impl<R: Reclaim + 'static> Default for EpochBagQueues<R> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<R: Reclaim + 'static> EpochBagQueues<R> {
#[inline]
pub fn new() -> Self {
Self { queues: [BagQueue::new(), BagQueue::new(), BagQueue::new()], curr_idx: 0 }
}
#[inline]
pub fn into_sorted(self) -> [BagQueue<R>; BAG_QUEUE_COUNT] {
let [a, b, c] = self.queues;
match self.curr_idx {
0 => [a, c, b],
1 => [b, a, c],
2 => [c, b, a],
_ => unreachable!(),
}
}
#[inline]
pub fn retire_record(&mut self, record: Retired<R>, bag_pool: &mut BagPool<R>) {
self.retire_record_by_age(record, PossibleAge::SameEpoch, bag_pool);
}
#[inline]
pub fn retire_record_by_age(
&mut self,
record: Retired<R>,
age: PossibleAge,
bag_pool: &mut BagPool<R>,
) {
let queue = match age {
PossibleAge::SameEpoch => &mut self.queues[self.curr_idx],
PossibleAge::OneEpoch => &mut self.queues[(self.curr_idx + 2) % BAG_QUEUE_COUNT],
PossibleAge::TwoEpochs => &mut self.queues[(self.curr_idx + 1) % BAG_QUEUE_COUNT],
};
queue.retire_record(record, bag_pool);
}
#[inline]
pub unsafe fn retire_final_record(&mut self, record: Retired<R>) {
let curr = &mut self.queues[self.curr_idx];
curr.head.retired_records.push_unchecked(record);
}
#[inline]
pub unsafe fn rotate_and_reclaim(&mut self, bag_pool: &mut BagPool<R>) {
self.curr_idx = (self.curr_idx + 1) % BAG_QUEUE_COUNT;
self.queues[self.curr_idx].reclaim_full_bags(bag_pool);
}
}
#[derive(Debug)]
pub struct BagQueue<R: Reclaim + 'static> {
head: Box<BagNode<R>>,
}
impl<R: Reclaim + 'static> BagQueue<R> {
#[inline]
pub fn into_non_empty(self) -> Option<Box<BagNode<R>>> {
if !self.is_empty() {
Some(self.head)
} else {
None
}
}
#[inline]
fn new() -> Self {
Self { head: BagNode::boxed() }
}
#[inline]
fn is_empty(&self) -> bool {
self.head.is_empty()
}
#[inline]
fn retire_record(&mut self, record: Retired<R>, bag_pool: &mut BagPool<R>) {
unsafe { self.head.retired_records.push_unchecked(record) };
if self.head.retired_records.is_full() {
let mut old_head = bag_pool.allocate_bag();
mem::swap(&mut self.head, &mut old_head);
self.head.next = Some(old_head);
}
}
#[inline]
unsafe fn reclaim_full_bags(&mut self, bag_pool: &mut BagPool<R>) {
let mut node = self.head.next.take();
while let Some(mut bag) = node {
bag.reclaim_all();
node = bag.next.take();
bag_pool.recycle_bag(bag);
}
}
}
#[derive(Debug)]
pub struct BagNode<R: Reclaim + 'static> {
next: Option<Box<BagNode<R>>>,
retired_records: ArrayVec<[Retired<R>; EPOCH_CACHE_SIZE]>,
}
impl<R: Reclaim> BagNode<R> {
#[inline]
pub unsafe fn reclaim_all(&mut self) {
self.reclaim_inner();
let mut curr = self.next.take();
while let Some(mut node) = curr {
node.reclaim_inner();
curr = node.next.take();
}
}
#[inline]
fn boxed() -> Box<Self> {
Box::new(Self { next: None, retired_records: ArrayVec::default() })
}
#[inline]
fn is_empty(&self) -> bool {
self.next.is_none() && self.retired_records.len() == 0
}
#[inline]
unsafe fn reclaim_inner(&mut self) {
for mut record in self.retired_records.drain(..) {
record.reclaim();
}
}
}
impl<R: Reclaim + 'static> Drop for BagNode<R> {
#[inline]
fn drop(&mut self) {
debug_assert!(
self.is_empty(),
"`BagNode`s must not be dropped unless empty (would leak memory)"
);
}
}
#[cfg(test)]
mod tests {
use std::ptr::NonNull;
use reclaim::leak::Leaking;
use super::{BAG_QUEUE_COUNT, EPOCH_CACHE_SIZE};
use crate::epoch::PossibleAge;
type EpochBagQueues = super::EpochBagQueues<Leaking>;
type BagPool = super::BagPool<Leaking>;
type BagQueue = super::BagQueue<Leaking>;
type Retired = reclaim::Retired<Leaking>;
fn retired() -> Retired {
let ptr: NonNull<()> = NonNull::dangling();
unsafe { Retired::new_unchecked(ptr) }
}
#[test]
fn empty_bag_queue() {
let bag_queue = BagQueue::new();
assert!(bag_queue.is_empty());
assert!(bag_queue.into_non_empty().is_none());
}
#[test]
fn non_empty_bag_queue() {
let mut pool = BagPool::new();
let mut bag_queue = BagQueue::new();
for _ in 0..EPOCH_CACHE_SIZE - 1 {
bag_queue.retire_record(retired(), &mut pool);
}
assert!(!bag_queue.is_empty());
assert!(bag_queue.head.next.is_none());
bag_queue.retire_record(retired(), &mut pool);
assert_eq!(bag_queue.head.retired_records.len(), 0);
assert!(bag_queue.head.next.is_some());
assert!(!bag_queue.is_empty());
let mut node = bag_queue.into_non_empty().unwrap();
unsafe { node.reclaim_all() };
}
#[test]
fn rotate_and_reclaim() {
let mut pool = BagPool::new();
let mut bags = EpochBagQueues::new();
for _ in 0..=EPOCH_CACHE_SIZE {
bags.retire_record(retired(), &mut pool);
}
unsafe { bags.rotate_and_reclaim(&mut pool) };
unsafe { bags.rotate_and_reclaim(&mut pool) };
unsafe { bags.rotate_and_reclaim(&mut pool) };
assert_eq!(pool.0.len(), 1);
assert_eq!(bags.queues[0].head.retired_records.len(), 1);
unsafe { bags.queues[0].head.reclaim_all() };
}
#[test]
fn retire_by_age() {
let mut pool = BagPool::new();
let mut bags = EpochBagQueues::new();
for _ in 0..BAG_QUEUE_COUNT {
for _ in 0..EPOCH_CACHE_SIZE - 1 {
bags.retire_record(retired(), &mut pool);
unsafe { bags.rotate_and_reclaim(&mut pool) };
}
}
bags.retire_record_by_age(retired(), PossibleAge::TwoEpochs, &mut pool);
assert_eq!(bags.curr_idx, 0);
assert_eq!(bags.queues[1].head.retired_records.len(), 0);
assert!(bags.queues[1].head.next.is_some());
unsafe { bags.rotate_and_reclaim(&mut pool) };
assert_eq!(pool.0.len(), 1);
bags.retire_record_by_age(retired(), PossibleAge::OneEpoch, &mut pool);
assert_eq!(bags.curr_idx, 1);
assert_eq!(bags.queues[0].head.retired_records.len(), 0);
assert!(bags.queues[0].head.next.is_some());
assert_eq!(pool.0.len(), 0);
unsafe { bags.rotate_and_reclaim(&mut pool) };
bags.retire_record_by_age(retired(), PossibleAge::SameEpoch, &mut pool);
assert_eq!(bags.curr_idx, 2);
assert_eq!(pool.0.len(), 0);
assert_eq!(bags.queues[2].head.retired_records.len(), 0);
assert!(bags.queues[2].head.next.is_some());
unsafe { bags.rotate_and_reclaim(&mut pool) };
assert_eq!(bags.curr_idx, 0);
assert_eq!(pool.0.len(), 1);
unsafe { bags.rotate_and_reclaim(&mut pool) };
assert_eq!(bags.curr_idx, 1);
unsafe { bags.rotate_and_reclaim(&mut pool) };
assert_eq!(bags.curr_idx, 2);
assert_eq!(pool.0.len(), 2);
}
}