use std::{
cell::Cell,
ops::Deref,
sync::atomic::{self, AtomicPtr},
};
use crate::{
per_thread_storage::this_thread_does_have_allocated_storage_slot,
synchronize_rcu,
utils::{PhantomUnsend, PtrMutSendSync},
};
thread_local! {
static THIS_THREAD_CUR_NUM_LIVE_GUARDS: Cell<usize> = const { Cell::new(0) };
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RcuPtrReadGuard<'a, T> {
value: &'a T,
_phantom: PhantomUnsend,
}
impl<'a, T> Deref for RcuPtrReadGuard<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value
}
}
impl<'a, T> Drop for RcuPtrReadGuard<'a, T> {
fn drop(&mut self) {
THIS_THREAD_CUR_NUM_LIVE_GUARDS.set(
THIS_THREAD_CUR_NUM_LIVE_GUARDS
.get()
.checked_sub(1)
.unwrap(),
);
}
}
pub struct RcuPtrOldData<T> {
old_data_ptr: PtrMutSendSync<T>,
}
impl<T> RcuPtrOldData<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> {
assert_eq!(
THIS_THREAD_CUR_NUM_LIVE_GUARDS.get(),
0,
"cannot wait for an rcu grace period while holding rcu read guards on the current thread"
);
synchronize_rcu().await;
let res = unsafe { Box::from_raw(self.old_data_ptr.ptr()) };
std::mem::forget(self);
res
}
}
impl<T> Drop for RcuPtrOldData<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_ptr_wait_multiple<T: MultipleRcuOldDataInstances>(items: T) -> T::WaitResult {
items.wait().await
}
pub trait MultipleRcuOldDataInstances {
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),+> MultipleRcuOldDataInstances for ($(RcuPtrOldData<$t>),+) {
type WaitResult = ($(Box<$t>),+);
async fn wait(self) -> Self::WaitResult {
assert_eq!(
THIS_THREAD_CUR_NUM_LIVE_GUARDS.get(),
0,
"cannot wait for an rcu grace period while holding rcu read guards on the current thread"
);
synchronize_rcu().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 RcuPtr<T> {
value_ptr: AtomicPtr<T>,
}
impl<T> RcuPtr<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,
{
let guard = unsafe { self.read() };
f(&*guard)
}
pub unsafe fn read(&self) -> RcuPtrReadGuard<'_, T> {
assert!(
this_thread_does_have_allocated_storage_slot(),
"attempted to read an rcu protected pointer outside of an rcu-enabled tokio runtime"
);
let ptr = self.value_ptr.load(
atomic::Ordering::Acquire,
);
THIS_THREAD_CUR_NUM_LIVE_GUARDS.set(
THIS_THREAD_CUR_NUM_LIVE_GUARDS
.get()
.checked_add(1)
.unwrap(),
);
RcuPtrReadGuard {
value: unsafe { &*ptr },
_phantom: PhantomUnsend::new(),
}
}
pub fn swap_nowait(&self, new_value: Box<T>) -> RcuPtrOldData<T> {
let new_value_ptr = Box::leak(new_value);
let old_value_ptr = self.value_ptr.swap(
new_value_ptr,
atomic::Ordering::AcqRel,
);
unsafe { RcuPtrOldData::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> RcuPtr<T> {
pub fn read_clone(&self) -> T {
self.with(|x| x.clone())
}
}
impl<T> Drop for RcuPtr<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 RcuPtr<T> {}
unsafe impl<T: Sync> Sync for RcuPtr<T> {}