use num::Integer;
use crate::{DropPolicy, PinnedPoolBuilder, PinnedSlab, PinnedSlabInserter};
use std::pin::Pin;
#[derive(Debug)]
pub struct PinnedPool<T> {
slabs: Vec<PinnedSlab<T, SLAB_CAPACITY>>,
slab_with_vacant_slot_index: Option<usize>,
drop_policy: DropPolicy,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Key {
index_in_pool: usize,
}
#[cfg(not(miri))]
const SLAB_CAPACITY: usize = 128;
#[cfg(miri)]
const SLAB_CAPACITY: usize = 4;
impl<T> PinnedPool<T> {
#[must_use]
pub(crate) fn new_inner(drop_policy: DropPolicy) -> Self {
assert!(
size_of::<T>() > 0,
"PinnedPool must have non-zero item size"
);
Self {
slabs: Vec::new(),
drop_policy,
slab_with_vacant_slot_index: None,
}
}
#[must_use]
pub fn new() -> Self {
Self::builder().build()
}
pub fn builder() -> PinnedPoolBuilder<T> {
PinnedPoolBuilder::new()
}
#[must_use]
pub fn len(&self) -> usize {
self.slabs.iter().map(PinnedSlab::len).sum()
}
#[must_use]
pub fn capacity(&self) -> usize {
self.slabs.len()
.checked_mul(SLAB_CAPACITY)
.expect("overflow here would mean the pool can hold more items than virtual memory can fit, which makes no sense - it would never grow that big")
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.slabs.iter().all(PinnedSlab::is_empty)
}
#[must_use]
pub fn get(&self, key: Key) -> Pin<&T> {
let coordinates = ItemCoordinates::<SLAB_CAPACITY>::from_key(key);
self.slabs
.get(coordinates.slab_index)
.map(|s| s.get(coordinates.index_in_slab))
.expect("key was not associated with an item in the pool")
}
#[must_use]
pub fn get_mut(&mut self, key: Key) -> Pin<&mut T> {
let index = ItemCoordinates::<SLAB_CAPACITY>::from_key(key);
self.slabs
.get_mut(index.slab_index)
.map(|s| s.get_mut(index.index_in_slab))
.expect("key was not associated with an item in the pool")
}
#[must_use]
pub fn begin_insert<'a, 'b>(&'a mut self) -> PinnedPoolInserter<'b, T>
where
'a: 'b,
{
let slab_index = self.index_of_slab_with_vacant_slot();
let slab = self
.slabs
.get_mut(slab_index)
.expect("we just verified that there is a slab with a vacant slot at this index");
let predicted_slab_filled_slots = slab.len()
.checked_add(1)
.expect("we cannot overflow because there is at least one free slot, so it means there must be room to increment");
if predicted_slab_filled_slots == SLAB_CAPACITY {
self.slab_with_vacant_slot_index = None;
}
let slab_inserter = slab.begin_insert();
PinnedPoolInserter {
slab_inserter,
slab_index,
}
}
#[must_use]
pub fn insert(&mut self, value: T) -> Key {
let inserter = self.begin_insert();
let key = inserter.key();
inserter.insert(value);
key
}
pub fn remove(&mut self, key: Key) {
let index = ItemCoordinates::<SLAB_CAPACITY>::from_key(key);
let Some(slab) = self.slabs.get_mut(index.slab_index) else {
panic!("key was not associated with an item in the pool")
};
slab.remove(index.index_in_slab);
if self
.slab_with_vacant_slot_index
.is_none_or(|current| current > index.slab_index)
{
self.slab_with_vacant_slot_index = Some(index.slab_index);
}
}
#[must_use]
fn index_of_slab_with_vacant_slot(&mut self) -> usize {
if let Some(index) = self.slab_with_vacant_slot_index {
return index;
}
let index = if let Some((index, _)) = self
.slabs
.iter()
.enumerate()
.find(|(_, slab)| !slab.is_full())
{
index
} else {
self.slabs.push(PinnedSlab::new(self.drop_policy));
self.slabs
.len()
.checked_sub(1)
.expect("we just pushed a slab, so this cannot overflow because len >= 1")
};
self.slab_with_vacant_slot_index = Some(index);
index
}
#[cfg_attr(test, mutants::skip)] #[cfg(debug_assertions)]
#[expect(dead_code, reason = "we will probably use it later")]
pub(crate) fn integrity_check(&self) {
for slab in &self.slabs {
slab.integrity_check();
}
}
}
impl<T> Default for PinnedPool<T> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct PinnedPoolInserter<'s, T> {
slab_inserter: PinnedSlabInserter<'s, T, SLAB_CAPACITY>,
slab_index: usize,
}
impl<'s, T> PinnedPoolInserter<'s, T> {
pub fn insert<'v>(self, value: T) -> Pin<&'v T>
where
's: 'v,
{
self.slab_inserter.insert(value)
}
pub fn insert_mut<'v>(self, value: T) -> Pin<&'v mut T>
where
's: 'v,
{
self.slab_inserter.insert_mut(value)
}
#[must_use]
pub fn key(&self) -> Key {
ItemCoordinates::<SLAB_CAPACITY>::from_parts(self.slab_index, self.slab_inserter.index())
.to_key()
}
}
#[derive(Debug)]
struct ItemCoordinates<const SLAB_CAPACITY: usize> {
slab_index: usize,
index_in_slab: usize,
}
impl<const SLAB_CAPACITY: usize> ItemCoordinates<SLAB_CAPACITY> {
#[must_use]
fn from_parts(slab: usize, index_in_slab: usize) -> Self {
Self {
slab_index: slab,
index_in_slab,
}
}
#[must_use]
fn from_key(key: Key) -> Self {
let (slab_index, index_in_slab) = key.index_in_pool.div_rem(&SLAB_CAPACITY);
Self {
slab_index,
index_in_slab,
}
}
#[must_use]
fn to_key(&self) -> Key {
Key {
index_in_pool: self.slab_index.checked_mul(SLAB_CAPACITY)
.and_then(|x| x.checked_add(self.index_in_slab))
.expect("key indicates an item beyond the range of virtual memory - impossible to reach this point from a valid history")
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::indexing_slicing, reason = "panic is fine in test code")]
use super::*;
use std::{
cell::RefCell,
ptr,
sync::{Arc, Mutex},
thread,
};
#[test]
fn smoke_test() {
let mut pool = PinnedPool::<u32>::new();
assert_eq!(pool.len(), 0);
assert!(pool.is_empty());
let key_a = pool.insert(42);
let key_b = pool.insert(43);
let key_c = pool.insert(44);
assert_eq!(pool.len(), 3);
assert!(!pool.is_empty());
assert!(pool.capacity() >= 3);
assert_eq!(*pool.get(key_a), 42);
assert_eq!(*pool.get(key_b), 43);
assert_eq!(*pool.get(key_c), 44);
pool.remove(key_b);
let key_d = pool.insert(45);
assert_eq!(*pool.get(key_a), 42);
assert_eq!(*pool.get(key_c), 44);
assert_eq!(*pool.get(key_d), 45);
}
#[test]
#[should_panic]
fn panic_when_empty_oob_get() {
let pool = PinnedPool::<u32>::new();
_ = pool.get(Key { index_in_pool: 0 });
}
#[test]
#[should_panic]
fn panic_when_oob_get() {
let mut pool = PinnedPool::<u32>::new();
_ = pool.insert(42);
_ = pool.get(Key {
index_in_pool: 1234,
});
}
#[test]
fn begin_insert_returns_correct_key() {
let mut pool = PinnedPool::<u32>::new();
let inserter = pool.begin_insert();
let key = inserter.key();
assert_eq!(key.index_in_pool, 0);
inserter.insert(10);
assert_eq!(*pool.get(key), 10);
let inserter = pool.begin_insert();
let key = inserter.key();
assert_eq!(key.index_in_pool, 1);
inserter.insert(11);
assert_eq!(*pool.get(key), 11);
let inserter = pool.begin_insert();
let key = inserter.key();
assert_eq!(key.index_in_pool, 2);
inserter.insert(12);
assert_eq!(*pool.get(key), 12);
}
#[test]
fn abandoned_inserter_is_noop() {
let mut pool = PinnedPool::<u32>::new();
_ = pool.begin_insert();
let inserter = pool.begin_insert();
let key = inserter.key();
inserter.insert(20);
assert_eq!(*pool.get(key), 20);
_ = pool.insert(123);
_ = pool.insert(456);
}
#[test]
#[should_panic]
fn remove_empty_panics() {
let mut pool = PinnedPool::<u32>::new();
pool.remove(Key { index_in_pool: 0 });
}
#[test]
#[should_panic]
fn remove_vacant_panics() {
let mut pool = PinnedPool::<u32>::new();
_ = pool.insert(1234);
pool.remove(Key { index_in_pool: 1 });
}
#[test]
#[should_panic]
fn remove_oob_panics() {
let mut pool = PinnedPool::<u32>::new();
_ = pool.insert(1234);
pool.remove(Key {
index_in_pool: 9999999,
});
}
#[test]
#[should_panic]
fn get_vacant_panics() {
let mut pool = PinnedPool::<u32>::new();
_ = pool.insert(1234);
_ = pool.get(Key { index_in_pool: 1 });
}
#[test]
#[should_panic]
fn get_mut_vacant_panics() {
let mut pool = PinnedPool::<u32>::new();
_ = pool.insert(1234);
_ = pool.get_mut(Key { index_in_pool: 1 });
}
#[test]
fn in_refcell_works_fine() {
let pool = RefCell::new(PinnedPool::<u32>::new());
let key_a = {
let mut pool = pool.borrow_mut();
let key_a = pool.insert(42);
let key_b = pool.insert(43);
let key_c = pool.insert(44);
assert_eq!(*pool.get(key_a), 42);
assert_eq!(*pool.get(key_b), 43);
assert_eq!(*pool.get(key_c), 44);
pool.remove(key_b);
let key_d = pool.insert(45);
assert_eq!(*pool.get(key_a), 42);
assert_eq!(*pool.get(key_c), 44);
assert_eq!(*pool.get(key_d), 45);
key_a
};
{
let pool = pool.borrow();
assert_eq!(*pool.get(key_a), 42);
}
}
#[test]
fn multithreaded_via_mutex() {
let shared_pool = Arc::new(Mutex::new(PinnedPool::<u32>::new()));
let key_a;
let key_b;
let key_c;
{
let mut pool = shared_pool.lock().unwrap();
key_a = pool.insert(42);
key_b = pool.insert(43);
key_c = pool.insert(44);
assert_eq!(*pool.get(key_a), 42);
assert_eq!(*pool.get(key_b), 43);
assert_eq!(*pool.get(key_c), 44);
}
thread::spawn({
let shared_pool = Arc::clone(&shared_pool);
move || {
let mut pool = shared_pool.lock().unwrap();
pool.remove(key_b);
let d = pool.insert(45);
assert_eq!(*pool.get(key_a), 42);
assert_eq!(*pool.get(key_c), 44);
assert_eq!(*pool.get(d), 45);
}
});
let chain = shared_pool.lock().unwrap();
assert!(!chain.is_empty());
}
#[test]
#[should_panic]
fn drop_item_with_forbidden_to_drop_policy_panics() {
let mut pool = PinnedPool::<u32>::builder()
.drop_policy(DropPolicy::MustNotDropItems)
.build();
_ = pool.insert(123);
}
#[test]
fn drop_itemless_with_forbidden_to_drop_policy_ok() {
drop(
PinnedPool::<u32>::builder()
.drop_policy(DropPolicy::MustNotDropItems)
.build(),
);
}
#[test]
fn out_of_band_access() {
let mut pool = PinnedPool::<u32>::new();
let key_a = pool.insert(42);
let a_ptr = ptr::from_mut(pool.get_mut(key_a).get_mut());
unsafe {
*a_ptr += 1;
}
let inserter = pool.begin_insert();
unsafe {
*a_ptr += 1;
}
_ = inserter.insert(123);
pool.remove(key_a);
}
#[test]
fn fill_first_slab_before_allocating_second() {
let mut pool = PinnedPool::<u32>::new();
for _ in 0..SLAB_CAPACITY {
_ = pool.insert(1234);
}
assert_eq!(pool.slabs.len(), 1);
assert!(pool.slabs[0].is_full());
_ = pool.insert(1234);
assert_eq!(pool.slabs.len(), 2);
}
#[test]
fn fill_first_slab_even_after_abandoned_insert() {
let mut pool = PinnedPool::<u32>::new();
for _ in 0..(SLAB_CAPACITY - 1) {
_ = pool.insert(1234);
}
assert_eq!(pool.slabs.len(), 1);
assert!(!pool.slabs[0].is_full());
_ = pool.begin_insert();
_ = pool.insert(1234);
assert_eq!(pool.slabs.len(), 1);
assert!(pool.slabs[0].is_full());
}
#[test]
fn fill_hole_before_allocating_new_slab() {
let mut pool = PinnedPool::<u32>::new();
for _ in 0..SLAB_CAPACITY {
_ = pool.insert(1234);
}
let key_to_remove = Key { index_in_pool: 0 };
pool.remove(key_to_remove);
let key_filled = pool.insert(5678);
assert_eq!(key_filled.index_in_pool, 0);
assert_eq!(*pool.get(key_filled), 5678);
}
#[test]
fn fill_first_hole_ascending() {
let mut pool = PinnedPool::<u32>::new();
for _ in 0..SLAB_CAPACITY {
_ = pool.insert(1234);
}
for _ in 0..SLAB_CAPACITY {
_ = pool.insert(5678);
}
let key_to_remove = Key { index_in_pool: 0 };
pool.remove(key_to_remove);
let key_to_remove = Key {
index_in_pool: SLAB_CAPACITY,
};
pool.remove(key_to_remove);
let key_filled = pool.insert(91011);
assert_eq!(key_filled.index_in_pool, 0);
assert_eq!(*pool.get(key_filled), 91011);
}
#[test]
fn fill_first_hole_descending() {
let mut pool = PinnedPool::<u32>::new();
for _ in 0..SLAB_CAPACITY {
_ = pool.insert(1234);
}
for _ in 0..SLAB_CAPACITY {
_ = pool.insert(5678);
}
let key_to_remove = Key {
index_in_pool: SLAB_CAPACITY,
};
pool.remove(key_to_remove);
let key_to_remove = Key { index_in_pool: 0 };
pool.remove(key_to_remove);
let key_filled = pool.insert(91011);
assert_eq!(key_filled.index_in_pool, 0);
assert_eq!(*pool.get(key_filled), 91011);
}
#[test]
#[should_panic]
fn zst_is_panic() {
drop(PinnedPool::<()>::new());
}
#[test]
fn insert_mut_then_get_is_correct_value() {
let mut pool = PinnedPool::<u32>::new();
let inserter = pool.begin_insert();
let key = inserter.key();
let mut item = inserter.insert_mut(42);
*item = 99;
assert_eq!(*pool.get(key), 99);
}
#[test]
fn default_works_fine() {
let mut pool: PinnedPool<u32> = PinnedPool::default();
assert!(pool.is_empty());
assert_eq!(pool.len(), 0);
assert_eq!(pool.capacity(), 0);
let key = pool.insert(1234);
assert!(!pool.is_empty());
assert_eq!(pool.len(), 1);
assert_eq!(pool.get(key).get_ref(), &1234);
pool.remove(key);
}
}