hazptr/
lib.rs

1//! Hazard pointer based concurrent memory reclamation.
2//!
3//! A difficult problem that has to be considered when implementing lock-free
4//! collections or data structures is deciding, when a removed entry can be
5//! safely deallocated.
6//! It is usually not correct to deallocate removed entries right away, because
7//! different threads might still hold references to such entries and could
8//! consequently access already freed memory.
9//!
10//! Concurrent memory reclamation schemes solve that problem by extending the
11//! lifetime of removed entries for a certain *grace period*.
12//! After this period it must be impossible for other threads to have any
13//! references to these entries anymore and they can be finally deallocated.
14//! This is similar to the concept of *Garbage Collection* in languages like Go
15//! and Java, but with a much more limited scope.
16//!
17//! The Hazard-pointer reclamation scheme was described by Maged M. Michael in
18//! 2004 [[1]].
19//! It requires every *read* of an entry from shared memory to be accompanied by
20//! a global announcement marking the read entry as protected.
21//! Threads must store removed (retired) entries in a local cache and regularly
22//! attempt to reclaim all cached records in bulk.
23//! A record is safe to be reclaimed, once there is no hazard pointer protecting
24//! it anymore.
25//!
26//! # Reclamation Interface and Pointer Types
27//!
28//! The API of this library follows the abstract interface defined by the
29//! [`reclaim`][reclaim] crate.
30//! Hence, it uses the following types for atomically reading and writing from
31//! and to shared memory:
32//!
33//! - [`Atomic`]
34//! - [`Owned`]
35//! - [`Shared`]
36//! - [`Unlinked`]
37//! - [`Unprotected`]
38//!
39//! The primary type exposed by this API is [`Atomic`], which is a
40//! shared atomic pointer with similar semantics to `Option<Box<T>>`.
41//! It provides all operations that are also supported by `AtomicPtr`, such as
42//! `store`, `load` or `compare_exchange`.
43//! All *load* operations on an [`Atomic`] return (optional) [`Shared`]
44//! references.
45//! [`Shared`] is a non-nullable pointer type that is protected by a hazard
46//! pointer and has similar semantics to `&T`.
47//! *Read-Modify-Write* operations (`swap`, `compare_exchange`,
48//! `compare_exchange_weak`) return [`Unlinked`] values if they succeed.
49//! Only values that are successfully unlinked in this manner can be retired,
50//! which means they will be automatically reclaimed at some some point when it
51//! is safe to do so.
52//! [`Unprotected`] is useful for comparing and storing values, which do not
53//! need to be de-referenced and hence don't need to be protected by hazard
54//! pointers.
55//!
56//! # Compare-and-Swap
57//!
58//! The atomic [`compare_exchange`][reclaim::Atomic::compare_exchange] method of
59//! the [`Atomic`] type is highly versatile and uses generics and (internal)
60//! traits in order to achieve some degree of argument *overloading*.
61//! The `current` and `new` arguments accept a wide variety of pointer types,
62//! interchangeably.
63//!
64//! For instance, `current` accepts values of either types [`Shared`],
65//! [`Option<Shared>`][Option], or [`Marked<Shared>`][Marked].
66//! The same range of types and wrappers is also accepted for [`Unprotected`]
67//! values.
68//! A *compare-and-swap*  can only succeed if the `current` value is equal to
69//! the value that is actually stored in the [`Atomic`].
70//! Consequently, the return type of this method adapts to the input type:
71//! When `current` is either a [`Shared`] or an [`Unprotected`], the return
72//! type is [`Unlinked`], since all of these types are non-nullable.
73//! However, when `current` is an `Option`, the return type is
74//! `Option<Unlinked>`.
75//!
76//! The `new` argument accepts types like [`Owned`], [`Shared`], [`Unlinked`],
77//! [`Unprotected`] or `Option` thereof.
78//! Care has to be taken when inserting a [`Shared`] in this way, as it is
79//! possible to insert the value twice at different positions of the same
80//! collection, which violates the primary reclamation invariant (which is also
81//! the reason why `retire` is unsafe):
82//! It must be impossible for a thread to read a reference to a value that has
83//! previously been retired.
84//!
85//! When a *compare-and-swap* fails, a [`struct`][reclaim::CompareExchangeFailure]
86//! is returned that contains both the *actual* value and the value that was
87//! attempted to be inserted.
88//! This ensures that move-only types like [`Owned`] and [`Unlinked`] can be
89//! retrieved again in the case of a failed *compare-and-swap*.
90//! The actually loaded value is returned in the form a [`MarkedPtr`][reclaim::MarkedPtr].
91//!
92//! The other methods of [`Atomic`][Atomic] are similarly versatile in terms of
93//! accepted argument types.
94//!
95//! # Pointer Tagging
96//!
97//! Many concurrent algorithms require the use of atomic pointers with
98//! additional information stored in one or more of a pointer's lower bits.
99//! For this purpose the [`reclaim`][reclaim] crate provides a type-based
100//! generic solution for making pointer types markable.
101//! The number of usable lower bits is part of the type signature of types like
102//! [`Atomic`] or [`Owned`].
103//! If the pointed-to type is not able to provide the required number of mark
104//! bits (which depends on its alignment) this will lead to a compilation error.
105//! Since the number of mark bits is part of the types themselves, using zero
106//! mark bits also has zero runtime overhead.
107//!
108//! [1]: https://dl.acm.org/citation.cfm?id=987595
109//! [reclaim]: https://github.com/oliver-giersch/reclaim
110
111#![cfg_attr(not(any(test, feature = "std")), no_std)]
112#![warn(missing_docs)]
113
114#[cfg(not(feature = "std"))]
115extern crate alloc;
116
117pub use reclaim;
118pub use reclaim::typenum;
119
120use cfg_if::cfg_if;
121use reclaim::prelude::*;
122use typenum::Unsigned;
123
124/// A specialization of [`Atomic`][reclaim::Atomic] for the [`HP`] reclamation
125/// scheme.
126pub type Atomic<T, N> = reclaim::Atomic<T, HP, N>;
127/// A specialization of [`Shared`][reclaim::Shared] for the [`HP`] reclamation
128/// scheme.
129pub type Shared<'g, T, N> = reclaim::Shared<'g, T, HP, N>;
130/// A specialization of [`Owned`][reclaim::Owned] for the [`HP`] reclamation
131/// scheme.
132pub type Owned<T, N> = reclaim::Owned<T, HP, N>;
133/// A specialization of [`Unlinked`][reclaim::Unlinked] for the [`HP`]
134/// reclamation scheme.
135pub type Unlinked<T, N> = reclaim::Unlinked<T, HP, N>;
136/// A specialization of [`Unprotected`][reclaim::Unprotected] for the [`HP`]
137/// reclamation scheme.
138pub type Unprotected<T, N> = reclaim::Unprotected<T, HP, N>;
139
140#[cfg(any(test, feature = "std"))]
141mod default;
142
143mod global;
144mod guard;
145mod hazard;
146mod local;
147mod retired;
148
149cfg_if! {
150    if #[cfg(feature = "std")] {
151        /// A guarded pointer that can be used to acquire hazard pointers.
152        pub type Guard = crate::default::Guard;
153    } else {
154        pub use crate::local::{Local, RecycleError};
155        /// A **thread local** guarded pointer that can be used to acquire
156        /// hazard pointers.
157        pub type LocalGuard<'a> = crate::guarded::Guard<&'a Local>;
158    }
159}
160
161use crate::retired::Retired;
162
163////////////////////////////////////////////////////////////////////////////////////////////////////
164// HP
165////////////////////////////////////////////////////////////////////////////////////////////////////
166
167/// Hazard Pointer based reclamation scheme.
168#[derive(Debug, Default, Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
169pub struct HP;
170
171unsafe impl Reclaim for HP {
172    type Local = crate::local::Local;
173    type RecordHeader = (); // no extra header per allocated record is required
174
175    #[inline]
176    unsafe fn retire_local<T: 'static, N: Unsigned>(local: &Self::Local, unlinked: Unlinked<T, N>) {
177        Self::retire_local_unchecked(local, unlinked)
178    }
179
180    #[inline]
181    unsafe fn retire_local_unchecked<T, N: Unsigned>(
182        local: &Self::Local,
183        unlinked: Unlinked<T, N>,
184    ) {
185        let unmarked = Unlinked::into_marked_non_null(unlinked).decompose_non_null();
186        local.retire_record(Retired::new_unchecked(unmarked));
187    }
188}
189
190// The ThreadSanitizer can not correctly asses ordering restraints from explicit
191// fences, so memory operations around such fences need stricter ordering than
192// `Relaxed`, when instrumentation is chosen.
193
194#[cfg(not(feature = "sanitize-threads"))]
195mod sanitize {
196    use core::sync::atomic::Ordering;
197
198    pub const RELAXED_LOAD: Ordering = Ordering::Relaxed;
199    pub const RELAXED_STORE: Ordering = Ordering::Relaxed;
200
201    pub const RELEASE_SUCCESS: Ordering = Ordering::Release;
202    pub const RELEASE_FAIL: Ordering = Ordering::Relaxed;
203}
204
205#[cfg(feature = "sanitize-threads")]
206mod sanitize {
207    use core::sync::atomic::Ordering;
208
209    pub const RELAXED_LOAD: Ordering = Ordering::Acquire;
210    pub const RELAXED_STORE: Ordering = Ordering::Release;
211
212    pub const RELEASE_SUCCESS: Ordering = Ordering::AcqRel;
213    pub const RELEASE_FAIL: Ordering = Ordering::Acquire;
214}