#[cfg(not(loom))]
mod sys {
pub use std::{hint, sync};
pub mod cell {
#[derive(Debug)]
pub struct UnsafeCell<T>(std::cell::UnsafeCell<T>);
impl<T> UnsafeCell<T> {
pub fn new(data: T) -> UnsafeCell<T> {
UnsafeCell(std::cell::UnsafeCell::new(data))
}
pub fn with_mut<F, U>(&self, f: F) -> U
where
F: FnOnce(*mut T) -> U,
{
f(self.0.get())
}
}
}
}
#[cfg(loom)]
mod sys {
pub use loom::{cell, sync};
pub mod hint {
pub use loom::hint::*;
pub fn cold_path() {}
}
}
mod builder;
mod cache_padded;
mod close;
mod get;
mod slots;
mod try_lock;
use std::{
cmp,
mem::MaybeUninit,
ops::{Deref, DerefMut, Not},
ptr,
task::Waker,
};
pub use builder::{Async, AsyncBuilder, IntoResult, Sync, SyncBuilder};
pub use close::Close;
use close::ClosingState;
pub use get::{AsyncGet, Get, GetError};
use self::{
slots::{SlotIndex, Slots},
sys::{
cell::UnsafeCell,
hint,
sync::{
Arc,
mpsc::{Receiver, Sender, channel},
},
},
try_lock::TryLock,
};
#[derive(Debug, PartialEq)]
pub enum TryGetError<B> {
Closed,
AllSlotsOccupied,
Builder(B),
}
#[derive(Debug, PartialEq)]
enum TryAcquireError {
Closed,
AllSlotsOccupied,
}
#[derive(Debug, PartialEq)]
enum TryReleaseError {
InvalidPosition,
}
pub struct Pool<T, B> {
inner: Arc<PoolInner<T, B>>,
size: usize,
}
struct PoolInner<T, B> {
blocks: Box<[Slots]>,
objects: Box<[UnsafeCell<Option<T>>]>,
builder: B,
get_wakers_sender: Sender<Waker>,
get_wakers_receiver: Receiver<Waker>,
closing_state: TryLock<ClosingState>,
}
#[must_use = "`Object` will immediately return in the pool if unused"]
pub struct Object<T, B> {
position: Position,
value: MaybeUninit<T>,
pool_inner: Arc<PoolInner<T, B>>,
}
impl<T, B> Pool<T, B> {
pub fn new(desired_size: usize, builder: B) -> Pool<T, Sync<B>>
where
B: SyncBuilder<T>,
{
Pool::new_inner(desired_size, Sync(builder))
}
pub fn new_with_async_builder(
desired_size: usize,
builder: B,
) -> Pool<T, Async<B>>
where
B: AsyncBuilder<T>,
{
Pool::new_inner(desired_size, Async(builder))
}
fn new_inner(desired_size: usize, builder: B) -> Self {
let size = compute_size(desired_size);
let (waker_sender, waker_receiver) = channel();
Self {
size,
inner: Arc::new(PoolInner {
blocks: {
let number_of_slots = size >> Slots::SLOTS_SIZE.ilog2();
let mut keepers = Vec::with_capacity(number_of_slots);
for _ in 0..number_of_slots {
keepers.push(Slots::new());
}
keepers.into_boxed_slice()
},
objects: {
let mut objects = Vec::with_capacity(size);
for _ in 0..size {
objects.push(UnsafeCell::new(None));
}
objects.into_boxed_slice()
},
builder,
get_wakers_sender: waker_sender,
get_wakers_receiver: waker_receiver,
closing_state: TryLock::new(ClosingState::Opened),
}),
}
}
pub fn size(&self) -> usize {
self.size
}
pub fn is_closed(&self) -> bool {
self.inner.is_closed()
}
pub fn close(self) -> Close<T, B> {
Close::new(self)
}
pub fn reopen(&self) {
self.inner.reopen();
}
pub fn get(&self) -> Get<T, B> {
Get::new(&self.inner)
}
}
impl<T, B> Pool<T, Sync<B>>
where
B: SyncBuilder<T>,
{
pub fn try_get(&self) -> Result<Object<T, Sync<B>>, TryGetError<B::Error>> {
let (position, maybe_object_value) = match self.inner.try_acquire() {
Ok(result) => result,
Err(TryAcquireError::Closed) => {
return Err(TryGetError::Closed);
}
Err(TryAcquireError::AllSlotsOccupied) => {
return Err(TryGetError::AllSlotsOccupied);
}
};
let object_value = self
.inner
.build_object_value_if_necessary(&position, maybe_object_value)
.map_err(TryGetError::Builder)?;
Ok(Object::new(position, object_value, self.inner.clone()))
}
}
impl<T, B> Pool<T, Async<B>>
where
B: AsyncBuilder<T>,
{
pub async fn try_get(
&self,
) -> Result<Object<T, Async<B>>, TryGetError<B::Error>> {
let (position, maybe_object_value) = match self.inner.try_acquire() {
Ok(result) => result,
Err(TryAcquireError::Closed) => {
return Err(TryGetError::Closed);
}
Err(TryAcquireError::AllSlotsOccupied) => {
return Err(TryGetError::AllSlotsOccupied);
}
};
let object_value = self
.inner
.build_object_value_if_necessary(position, maybe_object_value)
.await
.map_err(TryGetError::Builder)?;
Ok(Object::new(position, object_value, self.inner.clone()))
}
}
#[inline]
fn compute_size(desired_size: usize) -> usize {
cmp::max(Slots::SLOTS_SIZE as _, desired_size).next_power_of_two()
}
impl<T, B> PoolInner<T, B> {
fn is_closed(&self) -> bool {
loop {
if let Some(closing_state) = self.closing_state.try_read_lock() {
return matches!(*closing_state, ClosingState::Opened).not();
}
hint::cold_path();
hint::spin_loop();
}
}
fn close(&self) {
loop {
if let Some(mut closing_state) = self.closing_state.try_write_lock()
{
*closing_state = ClosingState::Closing(None);
break;
}
hint::cold_path();
hint::spin_loop();
}
self.wake_pending_get_futures();
}
fn reopen(&self) {
loop {
if let Some(mut closing_state) = self.closing_state.try_write_lock()
{
*closing_state = ClosingState::Opened;
break;
}
hint::cold_path();
hint::spin_loop();
}
}
fn try_acquire(&self) -> Result<(Position, Option<T>), TryAcquireError> {
if self.is_closed() {
return Err(TryAcquireError::Closed);
}
for (block_index, slots) in self.blocks.iter().enumerate() {
let Some(slot_index) = slots.try_acquire() else {
continue;
};
let object_position = Position { block_index, slot_index };
let object_index = object_position.as_object_index();
let maybe_object_value = {
assert!(object_index < self.objects.len());
let cell = &self.objects[object_index];
cell.with_mut(|cell_ptr| {
let object_place = unsafe {
&mut *cell_ptr
};
object_place.take()
})
};
return Ok((object_position, maybe_object_value));
}
Err(TryAcquireError::AllSlotsOccupied)
}
fn try_release(
&self,
object_position: &Position,
object_value: T,
) -> Result<(), TryReleaseError> {
let Some(slots) = self.blocks.get(object_position.block_index) else {
return Err(TryReleaseError::InvalidPosition);
};
let object_index = object_position.as_object_index();
{
assert!(object_index < self.objects.len());
let cell = &self.objects[object_index];
cell.with_mut(|cell_ptr| {
unsafe {
ptr::replace(cell_ptr, Some(object_value));
}
});
};
self.release_slot(object_position, slots);
Ok(())
}
fn release_slot(&self, object_position: &Position, slots: &Slots) {
slots.release(object_position.slot_index);
self.wake_pending_get_futures();
self.wake_pending_close_future();
}
fn wake_pending_get_futures(&self) {
let wakers =
TryIter::new(&self.get_wakers_receiver).collect::<Vec<_>>();
for waker in wakers {
waker.wake()
}
}
fn wake_pending_close_future(&self) {
loop {
if let Some(closing_state) = self.closing_state.try_read_lock() {
if let ClosingState::Closing(maybe_waker) = &*closing_state
&& let Some(waker) = maybe_waker.as_ref()
{
waker.wake_by_ref();
}
break;
}
hint::cold_path();
hint::spin_loop();
}
}
}
impl<T, B> PoolInner<T, Sync<B>>
where
B: SyncBuilder<T>,
{
fn build_object_value_if_necessary(
&self,
position: &Position,
maybe_object_value: Option<T>,
) -> Result<T, B::Error> {
Ok(match maybe_object_value {
Some(object_value) => object_value,
None => match self.builder.build_object() {
Ok(object_value) => object_value,
Err(builder_error) => {
self.release_slot(
position,
&self.blocks[position.block_index],
);
return Err(builder_error);
}
},
})
}
}
impl<T, B> PoolInner<T, Async<B>>
where
B: AsyncBuilder<T>,
{
async fn build_object_value_if_necessary(
&self,
position: Position,
maybe_object_value: Option<T>,
) -> Result<T, B::Error> {
Ok(match maybe_object_value {
Some(object_value) => object_value,
None => match self.builder.build_object().await {
Ok(object_value) => object_value,
Err(builder_error) => {
self.release_slot(
&position,
&self.blocks[position.block_index],
);
return Err(builder_error);
}
},
})
}
}
unsafe impl<T, B> std::marker::Send for PoolInner<T, B>
where
T: std::marker::Send,
B: std::marker::Send,
{
}
unsafe impl<T, B> std::marker::Sync for PoolInner<T, B>
where
T: std::marker::Sync,
B: std::marker::Sync,
{
}
impl<T, B> Object<T, B> {
fn new(
position: Position,
value: T,
pool_inner: Arc<PoolInner<T, B>>,
) -> Self {
Self { position, value: MaybeUninit::new(value), pool_inner }
}
}
impl<T, B> Drop for Object<T, B> {
fn drop(&mut self) {
let value = unsafe { self.value.assume_init_read() };
unsafe {
self.value.assume_init_drop();
}
if self.pool_inner.try_release(&self.position, value).is_err() {
hint::cold_path();
unreachable!(
"`Object` has an invalid position, API has been misused"
);
}
}
}
impl<T, B> Deref for Object<T, B> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { self.value.assume_init_ref() }
}
}
impl<T, B> DerefMut for Object<T, B> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.value.assume_init_mut() }
}
}
#[derive(Clone, Copy)]
struct Position {
block_index: usize,
slot_index: SlotIndex,
}
impl Position {
fn as_object_index(&self) -> usize {
self.block_index * Slots::SLOTS_SIZE as usize + self.slot_index as usize
}
}
#[derive(Debug)]
struct TryIter<'a, T: 'a> {
receiver: &'a Receiver<T>,
}
impl<'a, T> TryIter<'a, T> {
fn new(receiver: &'a Receiver<T>) -> Self {
Self { receiver }
}
}
impl<'a, T> Iterator for TryIter<'a, T> {
type Item = T;
fn next(&mut self) -> Option<T> {
self.receiver.try_recv().ok()
}
}
#[cfg(test)]
mod tests_helper {
#[cfg(not(loom))]
pub fn run(test: impl Fn()) {
test()
}
#[cfg(loom)]
pub use loom::model as run;
}
#[cfg(test)]
mod tests {
#[cfg(not(loom))]
mod sys {
pub use std::thread;
pub use futures_executor::block_on;
}
#[cfg(loom)]
mod sys {
pub use loom::future::block_on;
pub mod thread {
pub use std::thread::sleep;
pub use loom::thread::spawn;
}
}
use std::{future::ready, ops::Not, time::Duration};
use self::sys::{block_on, thread};
use super::{GetError, Pool, TryGetError, compute_size, tests_helper::run};
struct T {
x: u8,
}
fn thread_wait() {
thread::sleep(Duration::from_millis(1));
#[cfg(loom)]
loom::thread::yield_now();
}
#[test]
fn test_compute_size() {
for desired_size in 0..=8 {
assert_eq!(compute_size(desired_size), 8);
}
for desired_size in 9..=16 {
assert_eq!(compute_size(desired_size), 16);
}
for desired_size in 17..=32 {
assert_eq!(compute_size(desired_size), 32);
}
}
#[test]
fn test_try_get() {
run(|| {
let pool = Pool::new(8, || T { x: 0 });
let _object_0 = pool.try_get().unwrap();
let _object_1 = pool.try_get().unwrap();
let _object_2 = pool.try_get().unwrap();
let _object_3 = pool.try_get().unwrap();
let _object_4 = pool.try_get().unwrap();
let _object_5 = pool.try_get().unwrap();
let _object_6 = pool.try_get().unwrap();
let _object_7 = pool.try_get().unwrap();
assert!(pool.try_get().is_err());
})
}
#[test]
fn test_try_get_builder_fails() {
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
run(|| {
let fail_now = Arc::new(AtomicBool::new(false));
let builder = {
let fail_now = fail_now.clone();
move || {
if fail_now.load(Ordering::SeqCst) {
Err(())
} else {
Ok(T { x: 0 })
}
}
};
let pool = Pool::<T, _>::new(8, builder);
let _object_0 = pool.try_get().unwrap();
let _object_1 = pool.try_get().unwrap();
let _object_2 = pool.try_get().unwrap();
fail_now.store(true, Ordering::SeqCst);
assert!(matches!(pool.try_get(), Err(TryGetError::Builder(())),));
fail_now.store(false, Ordering::SeqCst);
let object_3 = pool.try_get().unwrap();
let _object_4 = pool.try_get().unwrap();
let _object_5 = pool.try_get().unwrap();
let _object_6 = pool.try_get().unwrap();
let _object_7 = pool.try_get().unwrap();
assert!(matches!(
pool.try_get(),
Err(TryGetError::AllSlotsOccupied),
));
drop(object_3);
fail_now.store(true, Ordering::SeqCst);
let _object_3 = pool.try_get().unwrap();
})
}
#[test]
fn test_async_try_get() {
run(|| {
let pool = Pool::new_with_async_builder(8, || ready(T { x: 0 }));
block_on(async {
let _object_0 = pool.try_get().await.unwrap();
let _object_1 = pool.try_get().await.unwrap();
let _object_2 = pool.try_get().await.unwrap();
let _object_3 = pool.try_get().await.unwrap();
let _object_4 = pool.try_get().await.unwrap();
let _object_5 = pool.try_get().await.unwrap();
let _object_6 = pool.try_get().await.unwrap();
let _object_7 = pool.try_get().await.unwrap();
assert!(pool.try_get().await.is_err());
});
})
}
#[test]
fn test_get() {
run(|| {
let pool = Pool::new(8, || T { x: 0 });
block_on(async {
let mut object_0 = pool.get().await.unwrap();
object_0.x = 7;
});
let _object_0 = block_on(async {
let object = pool.get().await.unwrap();
assert_eq!(object.x, 7);
object
});
let _object_1 = pool.try_get().unwrap();
let _object_2 = pool.try_get().unwrap();
let _object_3 = pool.try_get().unwrap();
let mut object_4 = pool.try_get().unwrap();
let _object_5 = pool.try_get().unwrap();
let _object_6 = pool.try_get().unwrap();
let _object_7 = pool.try_get().unwrap();
assert!(pool.try_get().is_err());
let thread_handle = thread::spawn({
move || {
block_on(async {
assert_eq!(pool.get().await.unwrap().x, 42);
})
}
});
object_4.x = 42;
thread_wait();
drop(object_4);
thread_handle.join().unwrap();
})
}
#[test]
fn test_async_get() {
run(|| {
let pool = Pool::new_with_async_builder(8, || ready(T { x: 0 }));
block_on(async {
{
let mut object_0 = pool.get().await.unwrap();
object_0.x = 7;
}
let object_0 = pool.get().await.unwrap();
assert_eq!(object_0.x, 7);
let _object_1 = pool.try_get().await.unwrap();
let _object_2 = pool.try_get().await.unwrap();
let _object_3 = pool.try_get().await.unwrap();
let mut object_4 = pool.try_get().await.unwrap();
let _object_5 = pool.try_get().await.unwrap();
let _object_6 = pool.try_get().await.unwrap();
let _object_7 = pool.try_get().await.unwrap();
assert!(pool.try_get().await.is_err());
let thread_handle = thread::spawn({
move || {
block_on(async {
assert_eq!(pool.get().await.unwrap().x, 42);
})
}
});
object_4.x = 42;
thread_wait();
drop(object_4);
thread_handle.join().unwrap();
})
})
}
#[test]
fn test_get_builder_fails() {
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
run(|| {
let fail_now = Arc::new(AtomicBool::new(false));
let builder = {
let fail_now = fail_now.clone();
move || {
if fail_now.load(Ordering::SeqCst) {
Err(())
} else {
Ok(T { x: 0 })
}
}
};
let pool = Arc::new(Pool::<T, _>::new(8, builder));
block_on(async {
let _object_0 = pool.get().await.unwrap();
let _object_1 = pool.get().await.unwrap();
let _object_2 = pool.get().await.unwrap();
fail_now.store(true, Ordering::SeqCst);
assert!(
matches!(pool.get().await, Err(GetError::Builder(())),)
);
fail_now.store(false, Ordering::SeqCst);
let object_3 = pool.try_get().unwrap();
let _object_4 = pool.try_get().unwrap();
let _object_5 = pool.try_get().unwrap();
let _object_6 = pool.try_get().unwrap();
let _object_7 = pool.try_get().unwrap();
drop(object_3);
fail_now.store(true, Ordering::SeqCst);
let _object_3 = pool.get().await.unwrap();
});
})
}
#[test]
#[cfg(not(loom))]
fn test_get_returns_none() {
run(|| {
let pool = Pool::new(8, || T { x: 0 });
let object_0 = pool.try_get().unwrap();
let object_1 = pool.try_get().unwrap();
let object_2 = pool.try_get().unwrap();
let object_3 = pool.try_get().unwrap();
let object_4 = pool.try_get().unwrap();
let object_5 = pool.try_get().unwrap();
let object_6 = pool.try_get().unwrap();
let object_7 = pool.try_get().unwrap();
assert!(pool.try_get().is_err());
let get_thread_handle = thread::spawn({
let get_future = pool.get();
move || {
block_on(async {
let maybe_object = get_future.await;
assert!(maybe_object.is_err());
})
}
});
thread::spawn(move || {
thread::sleep(Duration::from_millis(100));
drop(object_0);
drop(object_1);
drop(object_2);
drop(object_3);
drop(object_4);
drop(object_5);
drop(object_6);
drop(object_7);
});
thread_wait();
let pool = block_on(pool.close());
#[cfg(not(loom))]
assert!(get_thread_handle.is_finished());
assert!(pool.is_closed());
})
}
#[test]
fn test_object_drop_releases_the_value() {
run(|| {
let pool = Pool::new(8, || T { x: 0 });
let object_0 = pool.try_get().unwrap();
let _object_1 = pool.try_get().unwrap();
let _object_2 = pool.try_get().unwrap();
let _object_3 = pool.try_get().unwrap();
let object_4 = pool.try_get().unwrap();
let _object_5 = pool.try_get().unwrap();
let _object_6 = pool.try_get().unwrap();
let _object_7 = pool.try_get().unwrap();
assert!(pool.try_get().is_err());
drop(object_0);
let _object_0 = pool.try_get().unwrap();
assert!(pool.try_get().is_err());
drop(object_4);
let _object_4 = pool.try_get().unwrap();
assert!(pool.try_get().is_err());
})
}
#[test]
fn test_object_derefs() {
run(|| {
let pool = Pool::new(8, || T { x: 0 });
{
let object = pool.try_get().unwrap();
assert_eq!(object.x, 0);
}
{
let mut object = pool.try_get().unwrap();
object.x = 42;
assert_eq!(object.x, 42);
}
{
let object0 = pool.try_get().unwrap();
assert_eq!(object0.x, 42);
let object1 = pool.try_get().unwrap();
assert_eq!(object1.x, 0);
}
})
}
#[test]
fn test_close_and_reopen() {
run(|| {
let pool = Pool::new(8, || T { x: 0 });
let mut object_0 = pool.try_get().unwrap();
let object_1 = pool.try_get().unwrap();
let thread_handle =
thread::spawn(move || block_on(async { pool.close().await }));
thread_wait();
object_0.x = 42;
drop(object_0);
thread_wait();
#[cfg(not(loom))]
assert!(thread_handle.is_finished().not());
drop(object_1);
thread_wait();
let pool = thread_handle.join().unwrap();
assert!(pool.is_closed());
assert!(matches!(pool.try_get(), Err(TryGetError::Closed)));
pool.reopen();
assert!(pool.is_closed().not());
assert_eq!(pool.try_get().unwrap().x, 42);
})
}
#[test]
fn test_close_when_all_objects_are_wandering() {
run(|| {
let pool = Pool::new(8, || T { x: 0 });
let object_0 = pool.try_get().unwrap();
let object_1 = pool.try_get().unwrap();
let object_2 = pool.try_get().unwrap();
let object_3 = pool.try_get().unwrap();
let object_4 = pool.try_get().unwrap();
let object_5 = pool.try_get().unwrap();
let object_6 = pool.try_get().unwrap();
let object_7 = pool.try_get().unwrap();
let thread_handle =
thread::spawn(|| block_on(async { pool.close().await }));
thread_wait();
drop(object_0);
drop(object_1);
drop(object_2);
drop(object_3);
drop(object_4);
drop(object_5);
drop(object_6);
drop(object_7);
let pool = thread_handle.join().unwrap();
assert!(pool.is_closed());
})
}
#[test]
fn test_reopen_on_non_closed_pool() {
run(|| {
let pool = Pool::new(8, || T { x: 0 });
pool.reopen();
})
}
}