#![cfg(target_has_atomic = "128")]
#![feature(integer_atomics)]
#![no_std]
extern crate alloc;
use alloc::boxed::Box;
use parking_lot::RwLock;
use core::{
hint,
marker::PhantomData,
ops::Deref,
ptr::NonNull,
sync::atomic::{AtomicU128, Ordering},
};
const COUNTER_MASK: u128 = 0xffff_ffff_ffff_ffff;
struct Backoff {
step: u32,
}
impl Backoff {
fn new() -> Self {
Self { step: 0 }
}
fn spin(&mut self) {
for _ in 0..1u32 << self.step.min(6) {
hint::spin_loop();
}
if self.step <= 6 {
self.step += 1;
}
}
}
#[derive(Debug)]
pub struct RcuGuard<'a, T> {
ptr: NonNull<T>,
cell: &'a RcuCell<T>,
}
impl<T> Deref for RcuGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { self.ptr.as_ref() }
}
}
impl<T> Drop for RcuGuard<'_, T> {
fn drop(&mut self) {
let mut backoff = Backoff::new();
loop {
let ptr_counter = self.cell.ptr_counter_latest.load(Ordering::Acquire);
if (ptr_counter >> 64) as usize == self.ptr.as_ptr() as usize {
if self
.cell
.ptr_counter_latest
.compare_exchange_weak(
ptr_counter,
ptr_counter - 1,
Ordering::AcqRel,
Ordering::Relaxed,
)
.is_ok()
{
return;
}
} else {
break;
}
backoff.spin();
}
let mut backoff = Backoff::new();
loop {
let ptr_counter = self.cell.ptr_counter_to_clear.load(Ordering::Acquire);
if (ptr_counter >> 64) as usize == self.ptr.as_ptr() as usize
&& self
.cell
.ptr_counter_to_clear
.compare_exchange_weak(
ptr_counter,
ptr_counter - 1,
Ordering::AcqRel,
Ordering::Relaxed,
)
.is_ok()
{
return;
}
backoff.spin();
}
}
}
#[derive(Debug)]
pub struct RcuCell<T> {
ptr_counter_latest: AtomicU128,
ptr_counter_to_clear: AtomicU128,
data: PhantomData<T>,
update_token: RwLock<()>,
}
impl<T: Default> Default for RcuCell<T> {
fn default() -> Self {
Self::new(Default::default())
}
}
impl<T> Drop for RcuCell<T> {
fn drop(&mut self) {
let ptr = (*self.ptr_counter_latest.get_mut() >> 64) as usize as *mut T;
unsafe {
let _ = Box::from_raw(ptr);
}
}
}
impl<T> RcuCell<T> {
pub fn new(value: T) -> Self {
Self {
ptr_counter_latest: AtomicU128::new((Box::into_raw(Box::new(value)) as u128) << 64),
ptr_counter_to_clear: AtomicU128::new(0),
data: PhantomData,
update_token: RwLock::new(()),
}
}
pub fn read(&self) -> RcuGuard<'_, T> {
let ptr = unsafe {
NonNull::new_unchecked(
(self.ptr_counter_latest.fetch_add(1, Ordering::AcqRel) >> 64) as usize as *mut T,
)
};
RcuGuard { cell: self, ptr }
}
pub fn write(&self, value: T) {
let new_ptr_counter = (Box::into_raw(Box::new(value)) as u128) << 64;
let token_shared = self.update_token.read();
let old_ptr_counter = self
.ptr_counter_latest
.swap(new_ptr_counter, Ordering::AcqRel);
drop(token_shared);
self.clear(old_ptr_counter);
}
pub fn update(&self, f: impl FnOnce(&T) -> T) {
let token_exclusive = self.update_token.write();
let old_value =
unsafe { &*((self.ptr_counter_latest.load(Ordering::Acquire) >> 64) as *const T) };
let new_value = f(old_value);
let new_ptr_counter = (Box::into_raw(Box::new(new_value)) as u128) << 64;
let old_ptr_counter = self
.ptr_counter_latest
.swap(new_ptr_counter, Ordering::AcqRel);
drop(token_exclusive);
self.clear(old_ptr_counter);
}
fn clear(&self, old_ptr_counter: u128) {
if old_ptr_counter & COUNTER_MASK == 0 {
unsafe {
let _ = Box::from_raw((old_ptr_counter >> 64) as usize as *mut T);
}
return;
}
let mut backoff = Backoff::new();
while self
.ptr_counter_to_clear
.compare_exchange_weak(0, old_ptr_counter, Ordering::AcqRel, Ordering::Relaxed)
.is_err()
{
while self.ptr_counter_to_clear.load(Ordering::Relaxed) != 0 {
backoff.spin();
}
}
let mut backoff = Backoff::new();
while self.ptr_counter_to_clear.load(Ordering::Acquire) & COUNTER_MASK != 0 {
backoff.spin();
}
self.ptr_counter_to_clear.store(0, Ordering::Release);
unsafe {
let _ = Box::from_raw((old_ptr_counter >> 64) as usize as *mut T);
}
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
extern crate alloc;
use alloc::sync::Arc;
use alloc::vec::Vec;
use std::thread;
#[test]
fn basic_read_write() {
let cell = RcuCell::new(42);
assert_eq!(*cell.read(), 42);
cell.write(100);
assert_eq!(*cell.read(), 100);
}
#[test]
fn update_applies_closure() {
let cell = RcuCell::new(10);
cell.update(|&v| v + 5);
assert_eq!(*cell.read(), 15);
cell.update(|&v| v * 2);
assert_eq!(*cell.read(), 30);
}
#[test]
fn default_trait() {
let cell: RcuCell<i32> = RcuCell::default();
assert_eq!(*cell.read(), 0);
}
#[test]
fn multiple_guards_same_value() {
let cell = RcuCell::new(42);
let g1 = cell.read();
let g2 = cell.read();
let g3 = cell.read();
assert_eq!(*g1, 42);
assert_eq!(*g2, 42);
assert_eq!(*g3, 42);
drop(g1);
drop(g2);
drop(g3);
}
#[test]
fn guard_sees_value_at_read_time() {
let cell = Arc::new(RcuCell::new(1));
let guard = cell.read();
let cell2 = cell.clone();
let handle = thread::spawn(move || {
cell2.write(2);
});
assert_eq!(*guard, 1);
drop(guard);
handle.join().unwrap();
assert_eq!(*cell.read(), 2);
}
#[test]
fn drop_frees_value() {
let inner = Arc::new(42);
let cell = RcuCell::new(inner.clone());
assert_eq!(Arc::strong_count(&inner), 2);
drop(cell);
assert_eq!(Arc::strong_count(&inner), 1);
}
#[test]
fn write_frees_old_value() {
let v1 = Arc::new(1);
let v2 = Arc::new(2);
let cell = RcuCell::new(v1.clone());
assert_eq!(Arc::strong_count(&v1), 2);
cell.write(v2.clone());
assert_eq!(Arc::strong_count(&v1), 1);
assert_eq!(Arc::strong_count(&v2), 2);
}
#[test]
#[cfg(not(miri))] fn old_value_freed_after_guard_drop() {
let v1 = Arc::new(1);
let cell = RcuCell::new(v1.clone());
let guard = cell.read();
assert_eq!(Arc::strong_count(&v1), 2);
let cell_ref = &cell;
let v2 = Arc::new(2);
let v2_clone = v2.clone();
thread::scope(|s| {
s.spawn(move || {
cell_ref.write(v2_clone);
});
thread::sleep(std::time::Duration::from_millis(10));
assert_eq!(Arc::strong_count(&v1), 2);
drop(guard);
});
assert_eq!(Arc::strong_count(&v1), 1);
assert_eq!(*cell.read(), v2);
}
#[test]
#[cfg(not(miri))]
fn concurrent_readers() {
let cell = Arc::new(RcuCell::new(0u64));
let mut handles = Vec::new();
for _ in 0..4 {
let cell = cell.clone();
handles.push(thread::spawn(move || {
for _ in 0..1000 {
let guard = cell.read();
let _ = *guard; }
}));
}
for h in handles {
h.join().unwrap();
}
}
#[test]
#[cfg(not(miri))]
fn concurrent_read_write() {
let cell = Arc::new(RcuCell::new(0u64));
thread::scope(|s| {
let cell_w = cell.clone();
s.spawn(move || {
for i in 0..100 {
cell_w.write(i);
}
});
for _ in 0..4 {
let cell_r = cell.clone();
s.spawn(move || {
for _ in 0..1000 {
let guard = cell_r.read();
let val = *guard;
assert!(val < 100);
}
});
}
});
}
#[test]
#[cfg(not(miri))]
fn concurrent_updates() {
let cell = Arc::new(RcuCell::new(0u64));
thread::scope(|s| {
for _ in 0..4 {
let cell = cell.clone();
s.spawn(move || {
for _ in 0..100 {
cell.update(|&v| v + 1);
}
});
}
});
assert_eq!(*cell.read(), 400);
}
#[test]
#[cfg(not(miri))]
fn stress_mixed_operations() {
let cell = Arc::new(RcuCell::new(0u64));
thread::scope(|s| {
for _ in 0..2 {
let cell = cell.clone();
s.spawn(move || {
for i in 0..20 {
cell.write(i);
}
});
}
for _ in 0..2 {
let cell = cell.clone();
s.spawn(move || {
for _ in 0..20 {
cell.update(|&v| v.wrapping_add(1));
}
});
}
for _ in 0..2 {
let cell = cell.clone();
s.spawn(move || {
let mut guards = Vec::new();
for i in 0..80 {
guards.push(cell.read());
if guards.len() > 4 {
guards.remove(0);
}
if i % 10 == 0 {
guards.clear();
}
}
});
}
});
}
}