debra_common/
epoch.rs

1//! Type-safe epoch counters for tracking operations of threads.
2
3use core::fmt;
4use core::ops::{Add, Sub};
5use core::sync::atomic::{AtomicUsize, Ordering};
6
7////////////////////////////////////////////////////////////////////////////////////////////////////
8// Epoch
9////////////////////////////////////////////////////////////////////////////////////////////////////
10
11const EPOCH_INCREMENT: usize = 2;
12
13/// A monotonically increasing epoch counter with wrapping overflow behaviour.
14#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
15pub struct Epoch(usize);
16
17impl Epoch {
18    /// Creates a new [`Epoch`] starting at zero.
19    #[inline]
20    pub fn new() -> Self {
21        Self(0)
22    }
23
24    /// Returns the [`PossibleAge`] of the epoch relative to `global_epoch`.
25    ///
26    /// Since the global epoch is explicitly allowed to wrap around, it is not
27    /// possible to unambiguously determine the relative age of an epoch.
28    /// However, since epochs are monotonically increasing it is certain that
29    /// any previously observed epoch must be older of equal than the global
30    /// epoch.
31    /// Consequently, it is possible to determine if an epoch **could** be
32    /// within the critical range of two epochs, during which reclamation of
33    /// records **must** be avoided, and is in order to be conservative.
34    #[inline]
35    pub fn relative_age(self, global_epoch: Epoch) -> Result<PossibleAge, Undetermined> {
36        match global_epoch.0.wrapping_sub(self.0) {
37            0 => Ok(PossibleAge::SameEpoch),
38            2 => Ok(PossibleAge::OneEpoch),
39            4 => Ok(PossibleAge::TwoEpochs),
40            _ => Err(Undetermined),
41        }
42    }
43
44    #[inline]
45    pub(crate) fn with_epoch(epoch: usize) -> Self {
46        debug_assert_eq!(epoch % EPOCH_INCREMENT, 0);
47        Self(epoch)
48    }
49
50    #[inline]
51    pub(crate) fn into_inner(self) -> usize {
52        self.0
53    }
54}
55
56impl Add<usize> for Epoch {
57    type Output = Self;
58
59    #[inline]
60    fn add(self, rhs: usize) -> Self::Output {
61        Self(self.0.wrapping_add(rhs * EPOCH_INCREMENT))
62    }
63}
64
65impl Sub<usize> for Epoch {
66    type Output = Self;
67
68    #[inline]
69    fn sub(self, rhs: usize) -> Self::Output {
70        Self(self.0.wrapping_sub(rhs * EPOCH_INCREMENT))
71    }
72}
73
74impl fmt::Display for Epoch {
75    #[inline]
76    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
77        write!(f, "epoch {}", self.0 / EPOCH_INCREMENT)
78    }
79}
80
81////////////////////////////////////////////////////////////////////////////////////////////////////
82// AtomicEpoch
83////////////////////////////////////////////////////////////////////////////////////////////////////
84
85/// An atomic and concurrently accessible [`Epoch`].
86pub struct AtomicEpoch(AtomicUsize);
87
88impl AtomicEpoch {
89    /// Creates a new [`AtomicEpoch`] starting at zero.
90    #[inline]
91    pub const fn new() -> Self {
92        Self(AtomicUsize::new(0))
93    }
94
95    /// Loads the [`Epoch`] value.
96    ///
97    /// `load` takes an [`Ordering`][ordering] argument, which describes the
98    /// memory ordering of this operation.
99    ///
100    /// # Panics
101    ///
102    /// Panics if `order` is [`Release`][release] or [`AcqRel`][acq_rel].
103    ///
104    /// [ordering]: core::sync::atomic::Ordering
105    /// [release]: core::sync::atomic::Ordering::Release
106    /// [acq_rel]: core::sync::atomic::Ordering::AcqRel
107    #[inline]
108    pub fn load(&self, order: Ordering) -> Epoch {
109        Epoch(self.0.load(order))
110    }
111
112    /// Stores a value into the [`Epoch`] if the current value is equal to
113    /// `current`.
114    ///
115    /// The return value is always the previous value. If it is equal to
116    /// `current`, then the value was updated.
117    ///
118    /// `compare_and_swap` also takes an [`Ordering`][ordering] argument, which
119    /// describes the memory ordering of this operation.
120    /// Notice that even when using [`AcqRel`][acq_rel], the operation might
121    /// fail and hence just perform an `Acquire` load, but not have `Release`
122    /// semantics.
123    /// Using [`Acquire`][acquire] makes the store part of this operation
124    /// [`Relaxed`][relaxed], if it happens, and using [`Release`][release]
125    /// makes the load part [`Relaxed`][relaxed].
126    ///
127    /// [ordering]: core::sync::atomic::Ordering
128    /// [acquire]: core::sync::atomic::Ordering::Acquire
129    /// [acq_rel]: core::sync::atomic::Ordering::AcqRel
130    /// [relaxed]: core::sync::atomic::Ordering::Relaxed
131    /// [release]: core::sync::atomic::Ordering::Release
132    #[inline]
133    pub fn compare_and_swap(&self, current: Epoch, new: Epoch, order: Ordering) -> Epoch {
134        Epoch(self.0.compare_and_swap(current.0, new.0, order))
135    }
136}
137
138impl fmt::Debug for AtomicEpoch {
139    #[inline]
140    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141        f.debug_struct("AtomicEpoch").field("epoch", &self.0.load(Ordering::SeqCst)).finish()
142    }
143}
144
145impl fmt::Display for AtomicEpoch {
146    #[inline]
147    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
148        write!(f, "epoch {}", self.0.load(Ordering::SeqCst) / EPOCH_INCREMENT)
149    }
150}
151
152////////////////////////////////////////////////////////////////////////////////////////////////////
153// PossibleAge
154////////////////////////////////////////////////////////////////////////////////////////////////////
155
156/// The possible age of an epoch in relation to global epoch within a two-epoch
157/// range.
158///
159/// See [`relative_age`][Epoch::relative_age] for more details.
160#[derive(Debug, Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
161pub enum PossibleAge {
162    /// Epoch *may* be the same as the global epoch.
163    SameEpoch,
164    /// Epoch *may* be one epoch older than the global epoch.
165    OneEpoch,
166    /// Epoch *may* be two epochs older than the global epoch.
167    TwoEpochs,
168}
169
170impl fmt::Display for PossibleAge {
171    #[inline]
172    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
173        match *self {
174            PossibleAge::SameEpoch => write!(f, "epoch could be the same"),
175            PossibleAge::OneEpoch => write!(f, "epoch could be one epoch old"),
176            PossibleAge::TwoEpochs => write!(f, "epoch could be two epochs old"),
177        }
178    }
179}
180
181////////////////////////////////////////////////////////////////////////////////////////////////////
182// Undetermined
183////////////////////////////////////////////////////////////////////////////////////////////////////
184
185/// A type indicating that the age of an [`Epoch`] can not be determined, but is
186/// definitely older than two epochs.
187#[derive(Debug, Default, Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
188pub struct Undetermined;
189
190impl fmt::Display for Undetermined {
191    #[inline]
192    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
193        write!(f, "epoch age is undetermined, but older than two epochs")
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use std::sync::atomic::Ordering::Relaxed;
200
201    use super::{AtomicEpoch, Epoch, PossibleAge};
202
203    #[test]
204    fn new_epoch() {
205        let zero = Epoch::new();
206        assert_eq!(zero.into_inner(), 0);
207        let ten = Epoch::with_epoch(20);
208        assert_eq!(ten.into_inner(), 20);
209        assert_eq!(zero + 10, ten);
210    }
211
212    #[cfg(debug_assertions)]
213    #[test]
214    #[should_panic]
215    fn illegal_epoch() {
216        let _epoch = Epoch::with_epoch(11);
217    }
218
219    #[test]
220    fn relative_age() {
221        let zero = Epoch::new();
222        assert_eq!(zero.relative_age(zero).unwrap(), PossibleAge::SameEpoch);
223        assert_eq!(zero.relative_age(zero + 1).unwrap(), PossibleAge::OneEpoch);
224        assert_eq!(zero.relative_age(zero + 2).unwrap(), PossibleAge::TwoEpochs);
225
226        let max = Epoch::with_epoch(std::usize::MAX - 1);
227        assert_eq!(max.relative_age(zero).unwrap(), PossibleAge::OneEpoch);
228        assert_eq!((max - 1).relative_age(zero).unwrap(), PossibleAge::TwoEpochs);
229    }
230
231    #[test]
232    fn atomic_epoch() {
233        let atomic_epoch = AtomicEpoch::new();
234        let epoch = atomic_epoch.load(Relaxed);
235        assert_eq!(epoch.into_inner(), 0);
236    }
237
238    #[test]
239    fn atomic_epoch_cas() {
240        let atomic_epoch = AtomicEpoch::new();
241        let epoch = atomic_epoch.load(Relaxed);
242        let prev = atomic_epoch.compare_and_swap(Epoch::new(), epoch + 1, Relaxed);
243
244        assert_eq!(prev, Epoch::new());
245        assert_eq!(atomic_epoch.load(Relaxed), Epoch::new() + 1);
246    }
247}