Skip to content

Commit

Permalink
Enable switching between implementations using conditional compilation (
Browse files Browse the repository at this point in the history
  • Loading branch information
Johnabell authored Sep 26, 2024
1 parent 4a134ff commit 212bec8
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ categories = ["concurrency", "rust-patterns", "memory-management"]
[features]
default = ["std"]
std = []
bicephany = []

[build-dependencies]
rustc_version = "0.4"
Expand Down
57 changes: 57 additions & 0 deletions src/domain/hazard_pointer_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use crate::sync::{AtomicBool, AtomicPtr, Ordering};

use super::list::LockFreeList;

#[derive(Debug)]
pub(crate) struct Node {
pub(crate) ptr: AtomicPtr<usize>,
pub(crate) active: AtomicBool,
}

pub(super) type HazardPointerList = LockFreeList<Node>;

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<usize>) -> &Node {
&unsafe {
&*self.push(Node {
ptr,
active: AtomicBool::new(true),
})
}
.value
}
}
54 changes: 54 additions & 0 deletions src/domain/list.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -14,6 +17,30 @@ pub(super) struct Node<T> {
pub(super) next: AtomicPtr<Node<T>>,
}

#[cfg(any(test, not(feature = "bicephany")))]
pub(super) struct ListIterator<'a, T> {
node: *const Node<T>,
_list: PhantomData<&'a LockFreeList<T>>,
}

#[cfg(any(test, not(feature = "bicephany")))]
impl<'a, T> Iterator for ListIterator<'a, T> {
type Item = &'a T;

fn next(&mut self) -> Option<Self::Item> {
// # 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<T> LockFreeList<T> {
conditional_const!(
"Creates a new `LockFreeList`",
Expand Down Expand Up @@ -72,6 +99,14 @@ impl<T> LockFreeList<T> {
}
}
}

#[cfg(any(test, not(feature = "bicephany")))]
pub(super) fn iter(&self) -> ListIterator<T> {
ListIterator {
node: self.head.load(Ordering::Acquire),
_list: PhantomData,
}
}
}

impl<T> Drop for LockFreeList<T> {
Expand All @@ -88,6 +123,7 @@ impl<T> Drop for LockFreeList<T> {
#[cfg(test)]
mod test {
use super::*;
use alloc::vec;
use alloc::vec::Vec;

#[test]
Expand Down Expand Up @@ -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
Expand Down
35 changes: 29 additions & 6 deletions src/domain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<AtomicPtr<usize>>>;
#[cfg(feature = "bicephany")]
type HazardPointers = Bicephaly<AtomicPtr<usize>>;

#[cfg(not(test))]
pub(crate) struct HazardPointer<'a>(&'a bicephaly::Node<AtomicPtr<usize>>);
pub(crate) struct Pointer<'a, T>(&'a T);
#[cfg(test)]
pub(crate) struct HazardPointer<'a>(pub(super) &'a bicephaly::Node<AtomicPtr<usize>>);
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) {
Expand Down Expand Up @@ -86,7 +109,7 @@ impl Retire {
#[derive(Debug)]
pub struct Domain<const DOMAIN_ID: usize> {
retired: LockFreeList<Retire>,
hazard_ptrs: Bicephaly<AtomicPtr<usize>>,
hazard_ptrs: HazardPointers,
reclaim_strategy: ReclaimStrategy,
}

Expand Down Expand Up @@ -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,
}
Expand All @@ -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()
}
Expand All @@ -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())),
)
Expand Down
5 changes: 5 additions & 0 deletions src/sync.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -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;

0 comments on commit 212bec8

Please sign in to comment.