arcu-0.1.2/.cargo_vcs_info.json0000644000000001360000000000100117660ustar { "git": { "sha1": "54ab0ccd2d92a784261dc4a5ebbb32ed7ee9d9b1" }, "path_in_vcs": "" }arcu-0.1.2/.github/workflows/rust.yml000064400000000000000000000024241046102023000156750ustar 00000000000000name: Rust on: push: branches: [ "main" ] pull_request: branches: [ "main" ] env: CARGO_TERM_COLOR: always jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install latest stable uses: dtolnay/rust-toolchain@master with: toolchain: stable components: rustfmt, clippy - name: Cache dependencies uses: Swatinem/rust-cache@v2.7.3 - name: Check Rustfmt run: cargo fmt -- --check - name: Check Clippy run: cargo clippy - name: Check semver uses: obi1kenobi/cargo-semver-checks-action@v2 build: strategy: matrix: channel: ["stable", "beta", "1.76.0"] include: - { channel: "nightly", components: "miri"} needs: [lint] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install latest stable uses: dtolnay/rust-toolchain@master with: toolchain: ${{matrix.channel}} components: ${{matrix.components}} - name: Cache dependencies uses: Swatinem/rust-cache@v2.7.3 - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose - name: Run miri if: matrix.channel == 'nightly' run: cargo miri test --verbose arcu-0.1.2/.gitignore000064400000000000000000000000101046102023000125350ustar 00000000000000/target arcu-0.1.2/.vscode/settings.json000064400000000000000000000000771046102023000146560ustar 00000000000000{ "cSpell.words": [ "arcu", "Arcu" ] } arcu-0.1.2/Cargo.toml0000644000000022520000000000100077650ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.76.0" name = "arcu" version = "0.1.2" build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "An Rcu implementation using an Arc to keep the read critical section short and handle cleanup" readme = false keywords = [ "rcu", "synchronization", ] categories = [ "no-std", "concurrency", "data-structures", ] license = "MIT OR Apache-2.0" repository = "https://github.com/Skgland/Arcu" [lib] name = "arcu" path = "src/lib.rs" [[test]] name = "basic" path = "tests/basic.rs" [dependencies] [features] global_counters = ["std"] std = [] thread_local_counter = [ "std", "global_counters", ] arcu-0.1.2/Cargo.toml.orig000064400000000000000000000011011046102023000134360ustar 00000000000000[package] name = "arcu" version = "0.1.2" edition = "2021" rust-version = "1.76.0" description = "An Rcu implementation using an Arc to keep the read critical section short and handle cleanup" repository = "https://github.com/Skgland/Arcu" license = "MIT OR Apache-2.0" keywords = ["rcu", "synchronization"] categories = ["no-std", "concurrency", "data-structures"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] global_counters = ["std"] thread_local_counter = ["std", "global_counters"] std = [] [dependencies] arcu-0.1.2/src/atomic.rs000064400000000000000000000157121046102023000131750ustar 00000000000000//! Thi module contains the atomic and Arc based Rcu extern crate alloc; #[cfg(feature = "thread_local_counter")] use core::ops::Deref; use core::sync::atomic::{AtomicPtr, Ordering}; use std::marker::PhantomData; use alloc::sync::Arc; #[cfg(feature = "thread_local_counter")] use crate::epoch_counters::GlobalEpochCounterPool; use crate::epoch_counters::{EpochCounter, EpochCounterPool}; use super::Rcu; /// A Rcu based on an atomic pointer to an [`Arc`] and a [`EpochCounterPool`] /// pub struct Arcu { // Safety invariant // - the pointer has been created with Arc::into_raw // - Arcu "owns" one strong reference count active_value: AtomicPtr, epoch_counter_pool: P, phantom: PhantomData>, } #[cfg(feature = "thread_local_counter")] impl core::fmt::Display for Arcu { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let data = self.read(); core::fmt::Display::fmt(&data.deref(), f) } } impl core::fmt::Debug for Arcu { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Rcu") .field("active_value", &"Opaque") .field("epoch_counter_pool", &"Opaque") .finish() } } /// ## Safety /// - When mixing safe and unsafe functions care needs to be taken that write operations see all Epochs used by concurrent read operations /// - The safe read operations assume that the writer will observe `epoch_counters::THREAD_EPOCH_COUNTER`, see `epoch_counters::with_thread_local_epoch_counter`. /// - The safe writers assume that the readers will use one of the epoch counters in `epoch_counters::GLOBAL_EPOCH_COUNTERS`, see `epoch_counters::register_epoch_counter`. impl Rcu for Arcu { type Item = T; type Pool = P; #[inline] fn new(initial: impl Into>, epoch_counter_pool: P) -> Self { Arcu { active_value: AtomicPtr::new(Arc::into_raw(initial.into()).cast_mut()), epoch_counter_pool, phantom: PhantomData, } } /// ## Safety /// - The epoch counter must not be used concurrently /// - The epoch counter must be made available to write operations #[inline] unsafe fn raw_read(&self, epoch_counter: &EpochCounter) -> Arc { epoch_counter.enter_rcs(); let arc_ptr = self.active_value.load(Ordering::SeqCst); // Safety: See comments inside the block let arc = unsafe { // Safety: // - the ptr was created in Rcu::new or Rcu::replace with Arc::into_raw // - the Rcu is responsible for of the arc's strong references // - the Rcu is alive as this function takes a reference to the Rcu // - replace will wait with decrementing the old values strong count until our epoch counter is even again Arc::increment_strong_count(arc_ptr); // Safety: // - the ptr was created in Rcu::new or Rcu::replace with Arc::into_raw // - we have just ensured an additional strong count by incrementing the count Arc::from_raw(arc_ptr) }; epoch_counter.leave_rcs(); arc } /// ## Safety /// - `get_epoch_counters` must return a vector containing all epoch counters used with this Rcu that are odd at the time it is called /// - the vector may contain more epoch counters than required, i.e. epoch counters that are even and epoch counters in use with this Rcu #[inline] fn replace(&self, new_value: impl Into>) -> Arc { let arc_ptr = self.active_value.swap( Arc::into_raw(new_value.into()).cast_mut(), Ordering::Acquire, ); self.epoch_counter_pool.wait_for_epochs(); // Safety: // - the ptr was created in Arcu::new or Arcu::replace with Arc::into_raw // - we took the strong count of the Rcu // - we witnessed all threads either with an even epoch count or with a new odd count, // as such they must have left the critical section at some point unsafe { Arc::from_raw(arc_ptr) } } /// Update the Rcu using the provided update function /// Retries when the Rcu has been updated/replaced between reading the old value and writing the new value /// Aborts when the update function returns None /// /// ## Safety /// - `epoch_counter` must be valid for `raw_read` /// - `get_epoch_counters` must be valid for `raw_replace` unsafe fn raw_try_update<'a>( &self, mut update: impl FnMut(&T) -> Option>, epoch_counter: &EpochCounter, ) -> Option> { loop { let old = self.raw_read(epoch_counter); let new = Arc::into_raw(update(&old)?); // we now exchange the ownership of rcu(old) for rcu(new) // if rcu(?) is rcu(old) let result = self.active_value.compare_exchange_weak( Arc::as_ptr(&old).cast_mut(), new.cast_mut(), Ordering::AcqRel, Ordering::Relaxed, ); match result { Ok(old) => { // Compare Exchange Succeeded, ensure the old Arc gets dropped after waiting for all readers to leave the read critical section // we exchanged the old/new arc pointer // we are now responsible for one strong count of old, // in exchange for giving the rcu the responsibility of one strong count of new self.epoch_counter_pool.wait_for_epochs(); // Safety: // - the ptr was created in Arcu::new, Arcu::raw_replace, Arcu::raw_try_update with Arc::into_raw // - we took the strong count of the Arcu // - we witnessed all threads either with an even epoch count or with a new odd count, // as such they must have left the critical section at some point return Some(unsafe { Arc::from_raw(old) }); } Err(_new_old) => { // Compare Exchange failed, reclaim the new arc we leaked with Arc::into_raw above // Safety: // - the ptr was just created using Arc::into_raw // - there still one strong count left // we haven't exchanged the references so we are still responsible to clean up one strong count of new let _ = unsafe { Arc::from_raw(new) }; continue; } } } } } impl Drop for Arcu { fn drop(&mut self) { // Safety: // - The Pointer was created by Arc::into_raw // - The Arcu is responsible for one strong count, so the string count is at least 1 unsafe { Arc::from_raw(self.active_value.load(Ordering::Acquire)) }; } } arcu-0.1.2/src/doc_tests.rs000064400000000000000000000014721046102023000137060ustar 00000000000000//! Example from Issue #1 //! //! ```compile_fail //! struct Thing { //! rcu: Arcu, GlobalEpochCounterPool>, //! } //! impl Thing { //! fn send(&self) { //! std::thread::scope(|scope| { //! scope.spawn(|| { //! let mut ref_mut = self.rcu.read(); //! let mut ref_mut = ref_mut.borrow_mut(); //! *ref_mut = 1; //! println!("{}", ref_mut); //! }); //! //! let mut ref_mut = self.rcu.read(); //! let mut ref_mut = ref_mut.borrow_mut(); //! *ref_mut = 2; //! println!("{}", ref_mut); //! }); //! } //! } //! //! let thing = Thing { //! rcu: Arcu::new(std::cell::RefCell::new(0), GlobalEpochCounterPool) //! }; //! //! thing.send(); //! ``` arcu-0.1.2/src/epoch_counters.rs000064400000000000000000000153571046102023000147460ustar 00000000000000//! This module contains [`EpochCounter`], [`EpochCounterPool`] and related functionality. use alloc::sync::{Arc, Weak}; use core::sync::atomic::{AtomicU8, Ordering}; // the epoch counters of all threads that have ever accessed an Rcu // threads that have finished will have a dangling Weak reference and can be cleaned up // having this be shared between all Rcu's is a tradeoff: // - writes will be slower as more epoch counters need to be waited for // - reads should be faster as a thread only needs to register itself once on the first read #[cfg(feature = "global_counters")] static GLOBAL_EPOCH_COUNTERS: std::sync::RwLock>> = std::sync::RwLock::new(Vec::new()); #[cfg(feature = "global_counters")] pub fn register_epoch_counter(epoch_counter: alloc::sync::Weak) { GLOBAL_EPOCH_COUNTERS.write().unwrap().push(epoch_counter) } #[cfg(feature = "global_counters")] pub fn global_counters() -> Vec<::alloc::sync::Weak> { GLOBAL_EPOCH_COUNTERS.read().unwrap().clone() } #[cfg(feature = "thread_local_counter")] thread_local! { // odd value means the current thread is about to access the active_epoch of an Rcu // - threads observing this while leaving the write critical section will need to wait for this to change to a different (odd or even) value // a thread has a single epoch counter for all Rcu it accesses, as a thread can only access one Rcu at a time static THREAD_EPOCH_COUNTER: std::cell::OnceCell> = const { std::cell::OnceCell::new() }; } #[cfg(feature = "global_counters")] pub struct GlobalEpochCounterPool; #[cfg(feature = "global_counters")] unsafe impl EpochCounterPool for GlobalEpochCounterPool { fn wait_for_epochs(&self) { global_counters.wait_for_epochs() } } /// Calls the provided function with the thread local epoch counter /// /// Per Thread: On first use registers the epoch counter #[cfg(feature = "thread_local_counter")] pub(crate) fn with_thread_local_epoch_counter(fun: impl FnOnce(&EpochCounter) -> T) -> T { THREAD_EPOCH_COUNTER.with(|epoch_counter| { let epoch_counter = epoch_counter.get_or_init(|| { let epoch_counter = Arc::new(EpochCounter::new()); // register the current threads epoch counter on init register_epoch_counter(Arc::downgrade(&epoch_counter)); epoch_counter }); fun(&epoch_counter) }) } /// An epoch counter for Arcu /// /// This is used to prevent deallocating /// the old content or an Arcu while a reader is reading /// /// An even counter values means the EpochCounter is inactive i.e outside the critical section. /// An odd counter value means the EpochCounter is active i.e. in the critical section. #[repr(transparent)] pub struct EpochCounter(core::sync::atomic::AtomicU8); impl EpochCounter { /// Create a new EpochCounter #[inline] pub const fn new() -> Self { Self(AtomicU8::new(0)) } /// Increment the epoch counter to enter the read-critical-section /// /// # Panics /// - when the Epoch counter odd i.e. is already active/in the read critical section #[inline] pub(crate) fn enter_rcs(&self) { let old = self.0.fetch_add(1, Ordering::Acquire); assert!(old % 2 == 0, "Old Epoch counter value should be even!"); } /// Increment the epoch counter to leave the read-critical-section /// /// # Panics /// - when the Epoch counter even i.e. is inactive/outside the read critical section #[inline] pub(crate) fn leave_rcs(&self) { let old = self.0.fetch_add(1, Ordering::Release); assert!(old % 2 != 0, "Old Epoch counter value should be odd!"); } /// Get the current epoch counter value pub(crate) fn get_epoch(&self) -> u8 { self.0.load(Ordering::Acquire) } } impl Default for EpochCounter { fn default() -> Self { Self::new() } } /// ## Safety /// `wait_for_epochs` must not return normally until all epoch counters have been witnessed to be even or to have changed /// /// The first one is necessary to not get stuck on inactive EpochCounters /// The second one is necessary to not get stuck when we race to only witness the EpochCounter in different visits to the read-critical-section. /// It is sufficient to witness a change rather than inactivity as the only way for the epoch counter to change is /// - to go from inactive to active or /// - to go from active to inactive pub unsafe trait EpochCounterPool { /// Wait for each epoch counter of the pool to be inactive at least once /// /// We know that an epoch counter has been inactive at least once when have witnessed it to /// - be inactive /// - have changed fn wait_for_epochs(&self); } // Safety: // `wait_for_epochs` does not return normally until all epoch counters have been witnessed to be even or to have changed unsafe impl Vec>> EpochCounterPool for F { fn wait_for_epochs(&self) { // Get the current state of the epoch counters, // we can only drop the old value once we have observed all to be even or to have changed let epochs = self(); let mut epochs = epochs .into_iter() .flat_map(|elem| { let arc = elem.upgrade()?; let init_val = arc.get_epoch(); if init_val % 2 == 0 { // already even can be ignored return None; } // odd initial value thread is in the read critical section // we need to wait for the value to change before we can drop the arc Some((init_val, elem)) }) .collect::>(); while !epochs.is_empty() { epochs.retain(|elem| { let Some(arc) = elem.1.upgrade() else { // as the thread is dead it can't have a pointer to the old arc return false; }; // the epoch counter has not changed so the thread is still in the same instance of the critical section // any different value is ok as // - even values indicate the thread is outside of the critical section // - a different odd value indicates the thread has left the critical section and can subsequently only read the new active_value arc.get_epoch() == elem.0 }) } } } // Safety: // `wait_for_epochs` does not return normally until all epoch counters have been witnessed to be even or to have changed unsafe impl EpochCounterPool for [Arc; N] { fn wait_for_epochs(&self) { (|| self.iter().map(Arc::downgrade).collect::>()).wait_for_epochs() } } arcu-0.1.2/src/lib.rs000064400000000000000000000121171046102023000124630ustar 00000000000000// #![cfg_attr(not(feature = "std"), no_std)] #![deny(clippy::undocumented_unsafe_blocks)] #![warn(missing_docs)] //! Arc based Rcu implementation originally implementated in [mthom/scryer-prolog#1980](https://github.com/mthom/scryer-prolog/pull/1980) //! //! ```text //! A r c //! R c u //! A r c u //! ``` //! //! The atomics based version performs lock-free[^1] reads. //! By using Arc we keep the **r**ead-**c**ritical-**s**ection short and free of user defined code and //! automatically perform cleanup when no reference remains. //! //! To coordinate reads and writes [EpochCounter]s from an [EpochCounterPool] are used. //! Each read used an `EpochCounter` from the `EpochCounterPool` of the `Arcu` incrementing it once before entering the RCS and once more on leaving the RCS. //! Each write checks against all `EpochCounter`s in the pool, blocking until it is safe to decrement the strong count of the `Arc` that was replaced by the write. //! //! [^1]: when using thread local epoch counter with the global epoch counter pool, the initial read may block while adding the threads epoch counter to the pool //! extern crate alloc; pub mod epoch_counters; use alloc::sync::Arc; use epoch_counters::EpochCounterPool; use crate::epoch_counters::EpochCounter; pub mod atomic; pub mod rwlock; pub mod rcu_ref; mod doc_tests; /// An abstract Rcu to abstract over the atomic based [`atomic::Arcu`] and the RwLock based [`rwlock::Arcu`] pub trait Rcu { /// The type contained in this Rcu type Item; /// The type for the pool of epoch counters used by this Rcu type Pool: EpochCounterPool; /// Create a new Rcu with the given initial value and epoch counter pool fn new(initial: impl Into>, epoch_counter_pool: Self::Pool) -> Self; /// Read the value of the Rcu for the current epoch /// /// ## Blocking /// The initial read on each thread may block while registering the epoch counter. /// Further read on the same thread won't block even for different Rcu. /// /// ## Procedure /// /// 1. Register the Epoch Counter (only done once per thread, may block) /// 2. atomically increment the epoch counter (by one from even to odd) /// 3. atomically load the arc pointer /// 4. atomically increment the arc strong count /// 5. atomically increment the epoch counter (by one from odd back to even) #[cfg(feature = "thread_local_counter")] fn read(&self) -> rcu_ref::RcuRef where Self: Rcu, { let arc = crate::epoch_counters::with_thread_local_epoch_counter(|epoch_counter| { // Safety: // - we just registered the epoch counter // - this is a thread local epoch counter that is only used here, so there can't be a concurrent use unsafe { self.raw_read(epoch_counter) } }); rcu_ref::RcuRef::::new(arc) } /// Replace the Rcu's content with a new value /// /// This does not synchronize writes and the last to update the active_value pointer wins. /// /// all writes that do not win will be lost, though not leaked. /// This will block until the old value can be reclaimed, /// i.e. all threads witnessed to be in the read critical sections /// have been witnessed to have left the critical section at least once fn replace(&self, new_value: impl Into>) -> Arc; /// Update the Rcu using the provided update function /// Retries when the Rcu has been updated/replaced between reading the old value and writing the new value /// Aborts when the update function returns None #[cfg(feature = "thread_local_counter")] fn try_update(&self, mut update: F) -> Option> where Self: Rcu, F: FnMut(&Self::Item) -> Option, R: Into>, { // Safety: // epoch_counter is thread local and as such can't be in use concurrently // get_epoch_counters returns the list of all registered epoch counters crate::epoch_counters::with_thread_local_epoch_counter(|epoch_counter| unsafe { self.raw_try_update(move |old| update(old).map(Into::into), epoch_counter) }) } /// ## Safety /// - The epoch counter must not be used concurrently /// - The epoch counter must belong to the EpochCounterPool of this Rcu unsafe fn raw_read(&self, epoch_counter: &EpochCounter) -> Arc; /// Update the Rcu using the provided update function /// Retries when the Rcu has been updated/replaced between reading the old value and writing the new value /// Aborts when the update function returns None /// /// ## Safety /// - The epoch counter must not be used concurrently /// - The epoch counter must belong to the EpochCounterPool of this Rcu unsafe fn raw_try_update( &self, update: impl FnMut(&Self::Item) -> Option>, epoch_counter: &EpochCounter, ) -> Option>; } arcu-0.1.2/src/rcu_ref.rs000064400000000000000000000063251046102023000133460ustar 00000000000000//! This module contains the [`RcuRef`] type which is a smart pointer to the content of an [`super::Rcu`] use alloc::sync::Arc; use core::{fmt::Debug, ops::Deref, ptr::NonNull}; /// A smard pointer for a reference to the content of an [`super::Rcu`] pub struct RcuRef where T: ?Sized, M: ?Sized, { // we keep the arc to ensure its still alive, but we only access its data through data #[allow(dead_code)] arc: Arc, data: NonNull, } impl Debug for RcuRef { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("RcuRef") .field("data", &self.deref()) .finish() } } impl RcuRef { /// Create a new `RcuRef` from an `Arc` pub fn new(arc: Arc) -> Self { Self { data: arc.as_ref().into(), arc, } } } // use associated functions rather than methods so that we don't overlap // with functions of the Deref Target type impl RcuRef { /// apply the mapping function to the reference in this RcuRef pub fn map FnOnce(&'a M) -> &'a N>( reference: Self, f: F, ) -> RcuRef { RcuRef { arc: reference.arc, // Safety: See deref data: f(unsafe { reference.data.as_ref() }).into(), } } /// try to apply the faillable mapping function to the reference in this RcuRef pub fn try_map FnOnce(&'a M) -> Option<&'a N>>( reference: Self, f: F, ) -> Option> { // Safety: See deref let val = f(unsafe { reference.data.as_ref() })?; Some(RcuRef { arc: Arc::clone(&reference.arc), data: val.into(), }) } /// Check whether the two RcuRefs reference values in the same epoch pub fn same_epoch(this: &Self, other: &RcuRef) -> bool { Arc::ptr_eq(&this.arc, &other.arc) } /// Compares the RcuRefs references via [`core::ptr::eq`] pub fn ptr_eq(this: &Self, other: &Self) -> bool { core::ptr::eq(this.data.as_ptr(), other.data.as_ptr()) } /// Compares the RcuRefs references via [`core::ptr::addr_eq`] pub fn ptr_addr_eq(this: &Self, other: &Self) -> bool { std::ptr::addr_eq(this.data.as_ptr(), other.data.as_ptr()) } /// Clones the RcuRef /// /// Not implementing clone to not shadow the inner types clone impl #[allow(clippy::should_implement_trait)] pub fn clone(this: &Self) -> Self { Self { arc: Arc::clone(&this.arc), data: this.data, } } /// Get a reference to root of the RcuRef /// /// i.e. the value that was stored in the Rcu /// before applying any mappings pub fn get_root(this: &Self) -> &T { &this.arc } } impl Deref for RcuRef { type Target = M; fn deref(&self) -> &Self::Target { // Safety: The pointer points into the arc we are holding // while we are alive so is the target // as the content is in an Rcu no mutable access is given out unsafe { self.data.as_ref() } } } arcu-0.1.2/src/rwlock.rs000064400000000000000000000054441046102023000132230ustar 00000000000000//! This module contains the RwLock and Arc based Rcu //! //! This is primarily intended to sanity check the atomic based one in [`super::atomic`] extern crate alloc; use std::{marker::PhantomData, sync::RwLock}; use alloc::sync::Arc; use crate::epoch_counters::{EpochCounter, EpochCounterPool}; use super::Rcu; /// An Rcu based on an RwLock containing an Arc. /// /// You probably want the Atomics basec one [`super::atomic::Arcu`]. /// /// This Rcu uses a RwLocks for synchronization instead of the EpochCounterPool. /// The EpochCounterPool is kept to keep the API compatible with the atomics based one. pub struct Arcu { active_value: RwLock>, epoch_counter_pool: PhantomData

, } impl core::fmt::Display for Arcu { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { T::fmt(&self.active_value.read().unwrap(), f) } } impl core::fmt::Debug for Arcu { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Rcu") .field("active_value", &self.active_value.read().unwrap()) .field("epoch_counter_pool", &"Opaque") .finish() } } impl Rcu for Arcu { type Item = T; type Pool = P; #[inline] fn new(initial: impl Into>, _epoch_counter_pool: P) -> Self { Arcu { // active_value: AtomicPtr::new(Arc::into_raw(initial.into()).cast_mut()), active_value: RwLock::new(initial.into()), epoch_counter_pool: PhantomData, } } /// ## Safety /// - this impl is actually safe #[inline] unsafe fn raw_read(&self, _epoch_counter: &EpochCounter) -> Arc { self.active_value.read().unwrap().clone() } #[inline] fn replace(&self, new_value: impl Into>) -> Arc { std::mem::replace(&mut self.active_value.write().unwrap(), new_value.into()) } /// Update the Rcu using the provided update function /// Retries when the Rcu has been updated/replaced between reading the old value and writing the new value /// Aborts when the update function returns None /// /// ## Safety /// - this impl is actually safe #[inline] unsafe fn raw_try_update<'a>( &self, mut update: impl FnMut(&T) -> Option>, _epoch_counter: &EpochCounter, ) -> Option> { loop { let old = self.active_value.read().unwrap().clone(); let new = update(&old)?; let mut cur = self.active_value.write().unwrap(); if Arc::ptr_eq(&cur, &old) { return Some(std::mem::replace(&mut cur, new)); } else { println!("Ptr neq, retry!") } } } } arcu-0.1.2/tests/basic.rs000064400000000000000000000120611046102023000133470ustar 00000000000000use alloc::sync::Arc; use core::ops::Deref; use std::sync::RwLock; use arcu::{epoch_counters::EpochCounter, Rcu}; extern crate alloc; #[cfg(all(feature = "global_counters", feature = "thread_local_counter"))] struct Loud(T); #[cfg(all(feature = "global_counters", feature = "thread_local_counter"))] impl Drop for Loud { fn drop(&mut self) { println!("Dropping: {:?}", self.0); } } #[cfg(all(feature = "global_counters", feature = "thread_local_counter"))] #[test] fn std_replace() { use arcu::epoch_counters::GlobalEpochCounterPool; let rcu = arcu::atomic::Arcu::new(Loud(11), GlobalEpochCounterPool); assert_eq!(rcu.read().0, 11); rcu.replace(Loud(55)); assert_eq!(rcu.read().0, 55); } #[cfg(all(feature = "global_counters", feature = "thread_local_counter"))] #[test] fn std_update() { use arcu::epoch_counters::GlobalEpochCounterPool; let rcu = arcu::atomic::Arcu::new(Loud((0, 0)), GlobalEpochCounterPool); let rcu_ref = &rcu; assert_eq!(rcu.read().0, (0, 0)); std::thread::scope(|scope| { for idx in 0..100 { scope .spawn(move || rcu_ref.try_update(|old| Some(Arc::new(Loud((idx, old.0 .1 + 1)))))); } }); assert_eq!(rcu.read().0 .1, 100); } #[test] fn raw_replace_atomic() { raw_replace::>() } #[test] fn raw_replace_rwlock() { raw_replace::>() } fn raw_replace; 100]> + Send + Sync>() { let epoch_counters: [_; 100] = std::array::from_fn(|_| Arc::new(EpochCounter::new())); let rcu = Arcu::new(201, epoch_counters.clone()); let val = unsafe { rcu.raw_read(&epoch_counters[0]) }; assert_eq!(val.deref(), &201); let epoch_counters: &_ = &epoch_counters; std::thread::scope(|scope| { for idx in 0..100 { let new = Arc::new(idx); scope.spawn(|| { let to_drop = rcu.replace(new); println!("Dropping: {to_drop}"); }); } }); let val = unsafe { rcu.raw_read(&epoch_counters[0]) }; assert!((0..100).contains(val.deref())); } #[test] fn raw_update1_atomic() { raw_update1::>() } #[test] fn raw_update1_rwlock() { raw_update1::>() } fn raw_update1, Pool = [Arc; 100]> + Send + Sync>() { let epoch_counters: [_; 100] = std::array::from_fn(|_| Arc::new(EpochCounter::new())); let mut idx = 0; let epoch_counters_plus: [_; 100] = epoch_counters.clone().map(|counter| { ( counter, Arc::new(RwLock::new({ let old = idx; idx += 1; old })), ) }); let rcu = Arcu::new(RwLock::new(0), epoch_counters.clone()); let epoch_counters_ref: &_ = &epoch_counters_plus; std::thread::scope(|scope| { for (epoch_counter, arc) in epoch_counters_ref { scope.spawn(|| { let to_drop = unsafe { rcu.raw_try_update( |old| { let old = *old.read().unwrap(); println!("Old: {old}"); *arc.write().unwrap() = old + 1; Some(arc.clone()) }, epoch_counter.deref(), ) }; if let Some(to_drop) = to_drop { let to_drop = *to_drop.read().unwrap(); println!("Dropping: {to_drop}"); } }); } }); let final_val = unsafe { rcu.raw_read(&epoch_counters_ref[0].0) }; assert_eq!(final_val.read().unwrap().deref(), &epoch_counters_ref.len()); drop(epoch_counters); } #[test] fn raw_update2_atomic() { raw_update2::>() } #[test] fn raw_update2_rwlock() { raw_update2::>() } fn raw_update2; 100]> + Send + Sync>() { let epoch_counters: [_; 100] = std::array::from_fn(|_idx| Arc::new(EpochCounter::new())); let rcu = Arcu::new(Arc::new(0), epoch_counters.clone()); let epoch_counters_ref: &_ = &epoch_counters; std::thread::scope(|scope| { for epoch_counter in epoch_counters_ref { scope.spawn(|| { let to_drop = unsafe { rcu.raw_try_update( |old: &usize| { println!("Old: {old}"); Some(Arc::new(old + 1)) }, epoch_counter.deref(), ) }; if let Some(to_drop) = to_drop { println!("Dropping: {to_drop}"); } }); } }); let final_val = unsafe { rcu.raw_read(&epoch_counters_ref[0]) }; assert_eq!(final_val.deref(), &epoch_counters_ref.len()); drop(epoch_counters); }