From 212bec8b43834c6a873fdf241e7543928a2fa6c3 Mon Sep 17 00:00:00 2001 From: John Bell Date: Thu, 26 Sep 2024 08:01:24 +0100 Subject: [PATCH] Enable switching between implementations using conditional compilation (#4) --- .github/workflows/main.yml | 2 +- Cargo.toml | 1 + src/domain/hazard_pointer_list.rs | 57 +++++++++++++++++++++++++++++++ src/domain/list.rs | 54 +++++++++++++++++++++++++++++ src/domain/mod.rs | 35 +++++++++++++++---- src/sync.rs | 5 +++ 6 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 src/domain/hazard_pointer_list.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e63660a..3a6610c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -102,7 +102,7 @@ jobs: profile: minimal toolchain: ${{ matrix.rust }} override: true - - run: RUSTFLAGS="--cfg loom" cargo test --test concurrency_tests --release + - run: RUSTFLAGS="--cfg loom" cargo test --test concurrency_tests --release --features=bicephany miri: name: Miri diff --git a/Cargo.toml b/Cargo.toml index 90240ff..2055eea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ categories = ["concurrency", "rust-patterns", "memory-management"] [features] default = ["std"] std = [] +bicephany = [] [build-dependencies] rustc_version = "0.4" diff --git a/src/domain/hazard_pointer_list.rs b/src/domain/hazard_pointer_list.rs new file mode 100644 index 0000000..0d7c274 --- /dev/null +++ b/src/domain/hazard_pointer_list.rs @@ -0,0 +1,57 @@ +use crate::sync::{AtomicBool, AtomicPtr, Ordering}; + +use super::list::LockFreeList; + +#[derive(Debug)] +pub(crate) struct Node { + pub(crate) ptr: AtomicPtr, + pub(crate) active: AtomicBool, +} + +pub(super) type HazardPointerList = LockFreeList; + +impl Node { + pub(crate) fn reset(&self) { + self.ptr.store(core::ptr::null_mut(), Ordering::Release); + } + + fn try_acquire(&self) -> bool { + let active = self.active.load(Ordering::Acquire); + !active + && self + .active + .compare_exchange(active, true, Ordering::Release, Ordering::Relaxed) + .is_ok() + } + pub(crate) fn release(&self) { + self.active.store(false, Ordering::Release); + } + pub(crate) fn load(&self, ordering: Ordering) -> *mut usize { + self.ptr.load(ordering) + } + pub(crate) fn store(&self, value: *mut usize, ordering: Ordering) { + self.ptr.store(value, ordering) + } +} + +impl HazardPointerList { + pub(crate) fn get_available(&self) -> Option<&Node> { + self.iter() + .find(|node| !node.ptr.load(Ordering::Acquire).is_null() && node.try_acquire()) + } + + pub(crate) fn set_node_available(&self, node: &Node) { + node.reset(); + node.release(); + } + + pub(crate) fn push_in_use(&self, ptr: AtomicPtr) -> &Node { + &unsafe { + &*self.push(Node { + ptr, + active: AtomicBool::new(true), + }) + } + .value + } +} diff --git a/src/domain/list.rs b/src/domain/list.rs index 95f7d8f..78414f0 100644 --- a/src/domain/list.rs +++ b/src/domain/list.rs @@ -1,3 +1,6 @@ +#[cfg(any(test, not(feature = "bicephany")))] +use core::marker::PhantomData; + use crate::macros::conditional_const; use crate::sync::{AtomicIsize, AtomicPtr, Ordering}; use alloc::boxed::Box; @@ -14,6 +17,30 @@ pub(super) struct Node { pub(super) next: AtomicPtr>, } +#[cfg(any(test, not(feature = "bicephany")))] +pub(super) struct ListIterator<'a, T> { + node: *const Node, + _list: PhantomData<&'a LockFreeList>, +} + +#[cfg(any(test, not(feature = "bicephany")))] +impl<'a, T> Iterator for ListIterator<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + // # Safety + // + // Nodes are only deallocated when the domain is dropped. Nodes are allocated via box so + // maintain all the safety guarantees associated with Box. + let node = unsafe { self.node.as_ref() }; + + node.map(|node| { + self.node = node.next.load(Ordering::Acquire); + &node.value + }) + } +} + impl LockFreeList { conditional_const!( "Creates a new `LockFreeList`", @@ -72,6 +99,14 @@ impl LockFreeList { } } } + + #[cfg(any(test, not(feature = "bicephany")))] + pub(super) fn iter(&self) -> ListIterator { + ListIterator { + node: self.head.load(Ordering::Acquire), + _list: PhantomData, + } + } } impl Drop for LockFreeList { @@ -88,6 +123,7 @@ impl Drop for LockFreeList { #[cfg(test)] mod test { use super::*; + use alloc::vec; use alloc::vec::Vec; #[test] @@ -115,6 +151,24 @@ mod test { ); } + #[test] + fn test_iterator() { + // Arrange + let list = LockFreeList::new(); + + list.push(0); + list.push(1); + list.push(2); + list.push(3); + list.push(4); + + // Act + let members: Vec<_> = list.iter().collect(); + + // Assert + assert_eq!(vec![&4, &3, &2, &1, &0], members); + } + #[test] fn test_push_all() { // Arrange diff --git a/src/domain/mod.rs b/src/domain/mod.rs index 02e6146..d212913 100644 --- a/src/domain/mod.rs +++ b/src/domain/mod.rs @@ -26,7 +26,10 @@ //! let atom_box = AtomBox::new_with_domain("Hello World", &CUSTOM_DOMAIN); //! ``` +#[cfg(feature = "bicephany")] mod bicephaly; +#[cfg(not(feature = "bicephany"))] +pub(crate) mod hazard_pointer_list; mod list; mod reclaim_strategy; @@ -35,18 +38,38 @@ use crate::sync::{AtomicPtr, Ordering}; use alloc::boxed::Box; #[cfg(not(feature = "std"))] use alloc::collections::BTreeSet as Set; +#[cfg(feature = "bicephany")] use bicephaly::Bicephaly; use list::{LockFreeList, Node}; pub use reclaim_strategy::{ReclaimStrategy, TimedCappedSettings}; #[cfg(feature = "std")] use std::collections::HashSet as Set; +#[cfg(not(feature = "bicephany"))] +use self::hazard_pointer_list::HazardPointerList; + pub(crate) trait Retirable {} +#[cfg(not(feature = "bicephany"))] +pub(crate) type HazardPointer<'a> = Pointer<'a, hazard_pointer_list::Node>; +#[cfg(not(feature = "bicephany"))] +type HazardPointers = HazardPointerList; + +#[cfg(feature = "bicephany")] +pub(crate) type HazardPointer<'a> = Pointer<'a, bicephaly::Node>>; +#[cfg(feature = "bicephany")] +type HazardPointers = Bicephaly>; + #[cfg(not(test))] -pub(crate) struct HazardPointer<'a>(&'a bicephaly::Node>); +pub(crate) struct Pointer<'a, T>(&'a T); #[cfg(test)] -pub(crate) struct HazardPointer<'a>(pub(super) &'a bicephaly::Node>); +pub(crate) struct Pointer<'a, T>(pub(super) &'a T); + +impl<'a, T> Pointer<'a, T> { + fn new(value: &'a T) -> Self { + Pointer(value) + } +} impl<'a> HazardPointer<'a> { pub(crate) fn reset(&self) { @@ -86,7 +109,7 @@ impl Retire { #[derive(Debug)] pub struct Domain { retired: LockFreeList, - hazard_ptrs: Bicephaly>, + hazard_ptrs: HazardPointers, reclaim_strategy: ReclaimStrategy, } @@ -124,7 +147,7 @@ On nightly this will panic if the domain id is equal to the shared domain's id ( pub(crate), fn _new(reclaim_strategy: ReclaimStrategy) -> Self { Self { - hazard_ptrs: Bicephaly::new(), + hazard_ptrs: HazardPointers::new(), retired: LockFreeList::new(), reclaim_strategy, } @@ -133,7 +156,7 @@ On nightly this will panic if the domain id is equal to the shared domain's id ( pub(crate) fn acquire_haz_ptr(&self) -> HazardPointer { if let Some(haz_ptr) = self.hazard_ptrs.get_available() { - HazardPointer(haz_ptr) + HazardPointer::new(haz_ptr) } else { self.acquire_new_haz_ptr() } @@ -145,7 +168,7 @@ On nightly this will panic if the domain id is equal to the shared domain's id ( } fn acquire_new_haz_ptr(&self) -> HazardPointer { - HazardPointer( + HazardPointer::new( self.hazard_ptrs .push_in_use(AtomicPtr::new(core::ptr::null_mut())), ) diff --git a/src/sync.rs b/src/sync.rs index aab1224..f263e38 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -1,3 +1,5 @@ +#[cfg(all(loom, not(feature = "bicephany")))] +pub(crate) use loom::sync::atomic::AtomicBool; #[cfg(loom)] pub(crate) use loom::sync::atomic::{AtomicIsize, AtomicPtr, AtomicU64}; @@ -6,4 +8,7 @@ pub(crate) use core::sync::atomic::AtomicU64; #[cfg(not(loom))] pub(crate) use core::sync::atomic::{AtomicIsize, AtomicPtr}; +#[cfg(all(not(loom), not(feature = "bicephany")))] +pub(crate) use core::sync::atomic::AtomicBool; + pub(crate) use core::sync::atomic::Ordering;