use std::{
ops::Deref,
sync::atomic::{self, AtomicPtr},
};
use crate::{
per_thread_storage::this_thread_does_have_allocated_storage_slot,
synchronize_rcu,
utils::{PhantomUnsend, PtrMutSendSync},
};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RcuBoxReadGuard<'a, T> {
value: &'a T,
_phantom: PhantomUnsend,
}
impl<'a, T> Deref for RcuBoxReadGuard<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value
}
}
pub struct RcuBoxOldData<T> {
old_data_ptr: PtrMutSendSync<T>,
}
impl<T> RcuBoxOldData<T> {
unsafe fn new(old_data_ptr: *mut T) -> Self {
Self {
old_data_ptr: unsafe {
PtrMutSendSync::new(old_data_ptr)
},
}
}
pub async fn wait(self) -> Box<T> {
synchronize_rcu(true).await;
let res = unsafe { Box::from_raw(self.old_data_ptr.ptr()) };
std::mem::forget(self);
res
}
}
impl<T> Drop for RcuBoxOldData<T> {
#[track_caller]
#[inline]
fn drop(&mut self) {
if !std::thread::panicking() {
panic!(
"{} can't be dropped since concurrent readers may be using it. it must first be waited for.",
std::any::type_name::<Self>()
);
} else {
}
}
}
pub async fn rcu_box_wait_multiple<T: MultipleRcuBoxOldDataInstances>(items: T) -> T::WaitResult {
items.wait().await
}
pub trait MultipleRcuBoxOldDataInstances {
type WaitResult;
fn wait(self) -> impl Future<Output = Self::WaitResult>;
}
macro_rules! impl_multiple_rcu_old_data_instances_for_tuple {
{ $(($index: tt, $t: ident)),+ } => {
impl<$($t),+> MultipleRcuBoxOldDataInstances for ($(RcuBoxOldData<$t>),+) {
type WaitResult = ($(Box<$t>),+);
async fn wait(self) -> Self::WaitResult {
synchronize_rcu(true).await;
let results = unsafe {
($(
Box::from_raw(self.$index.old_data_ptr.ptr())
),+)
};
std::mem::forget(self);
results
}
}
};
}
impl_multiple_rcu_old_data_instances_for_tuple! { (0, A), (1, B) }
impl_multiple_rcu_old_data_instances_for_tuple! { (0, A), (1, B), (2, C) }
impl_multiple_rcu_old_data_instances_for_tuple! { (0, A), (1, B), (2, C), (3, D) }
impl_multiple_rcu_old_data_instances_for_tuple! { (0, A), (1, B), (2, C), (3, D), (4, E) }
impl_multiple_rcu_old_data_instances_for_tuple! { (0, A), (1, B), (2, C), (3, D), (4, E), (5, F) }
impl_multiple_rcu_old_data_instances_for_tuple! { (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G) }
impl_multiple_rcu_old_data_instances_for_tuple! { (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H) }
impl_multiple_rcu_old_data_instances_for_tuple! { (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I) }
impl_multiple_rcu_old_data_instances_for_tuple! {
(0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)
}
impl_multiple_rcu_old_data_instances_for_tuple! {
(0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J), (10, K)
}
impl_multiple_rcu_old_data_instances_for_tuple! {
(0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J), (10, K), (11, L)
}
pub struct RcuBox<T> {
value_ptr: AtomicPtr<T>,
}
impl<T> RcuBox<T> {
pub fn new(value: Box<T>) -> Self {
Self {
value_ptr: AtomicPtr::new(Box::leak(value)),
}
}
#[inline(always)]
pub fn with<F, R>(&self, f: F) -> R
where
F: FnOnce(&T) -> R,
{
assert!(
this_thread_does_have_allocated_storage_slot(),
"attempted to read an rcu box outside of an rcu-enabled tokio runtime"
);
let guard = unsafe { self.read() };
f(&*guard)
}
#[inline(always)]
pub unsafe fn with_unchecked<F, R>(&self, f: F) -> R
where
F: FnOnce(&T) -> R,
{
let guard = unsafe { self.read() };
f(&*guard)
}
pub unsafe fn read(&self) -> RcuBoxReadGuard<'_, T> {
let ptr = self.value_ptr.load(
atomic::Ordering::Acquire,
);
RcuBoxReadGuard {
value: unsafe { &*ptr },
_phantom: PhantomUnsend::new(),
}
}
pub fn swap_nowait(&self, new_value: Box<T>) -> RcuBoxOldData<T> {
let new_value_ptr = Box::leak(new_value);
let old_value_ptr = self.value_ptr.swap(
new_value_ptr,
atomic::Ordering::AcqRel,
);
unsafe { RcuBoxOldData::new(old_value_ptr) }
}
pub async fn swap(&self, new_value: Box<T>) -> Box<T> {
self.swap_nowait(new_value).wait().await
}
}
impl<T: Clone> RcuBox<T> {
pub fn read_clone(&self) -> T {
self.with(|x| x.clone())
}
}
impl<T> Drop for RcuBox<T> {
fn drop(&mut self) {
let ptr = self.value_ptr.load(
atomic::Ordering::Acquire,
);
let _ = unsafe { Box::from_raw(ptr) };
}
}
unsafe impl<T: Send> Send for RcuBox<T> {}
unsafe impl<T: Send + Sync> Sync for RcuBox<T> {}