grafos_fence/
fenced.rs

1use crate::FenceEpoch;
2
3/// A value tagged with the epoch in which it was produced.
4///
5/// Use this to stamp messages or state snapshots with an epoch so that
6/// recipients can reject stale data.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct Fenced<T> {
10    epoch: FenceEpoch,
11    value: T,
12}
13
14impl<T> Fenced<T> {
15    /// Wrap a value with the given epoch.
16    pub fn new(epoch: FenceEpoch, value: T) -> Self {
17        Self { epoch, value }
18    }
19
20    /// Wrap a value at epoch 0.
21    pub fn at_zero(value: T) -> Self {
22        Self {
23            epoch: FenceEpoch::zero(),
24            value,
25        }
26    }
27
28    /// The epoch this value was produced in.
29    pub fn epoch(&self) -> FenceEpoch {
30        self.epoch
31    }
32
33    /// Borrow the inner value.
34    pub fn value(&self) -> &T {
35        &self.value
36    }
37
38    /// Consume the wrapper and return the inner value.
39    pub fn into_value(self) -> T {
40        self.value
41    }
42
43    /// Returns `true` if this value's epoch is behind `current_epoch`.
44    pub fn is_stale(&self, current_epoch: &FenceEpoch) -> bool {
45        self.epoch.is_stale(current_epoch)
46    }
47
48    /// Transform the inner value while keeping the same epoch.
49    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Fenced<U> {
50        Fenced {
51            epoch: self.epoch,
52            value: f(self.value),
53        }
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn create_and_access() {
63        let f = Fenced::new(FenceEpoch::new(3), "hello");
64        assert_eq!(f.epoch(), FenceEpoch::new(3));
65        assert_eq!(*f.value(), "hello");
66        assert_eq!(f.into_value(), "hello");
67    }
68
69    #[test]
70    fn at_zero_convenience() {
71        let f = Fenced::at_zero(42u32);
72        assert_eq!(f.epoch(), FenceEpoch::zero());
73        assert_eq!(*f.value(), 42);
74    }
75
76    #[test]
77    fn staleness_check() {
78        let current = FenceEpoch::new(5);
79        let old = Fenced::new(FenceEpoch::new(3), ());
80        let fresh = Fenced::new(FenceEpoch::new(5), ());
81        let future = Fenced::new(FenceEpoch::new(7), ());
82
83        assert!(old.is_stale(&current));
84        assert!(!fresh.is_stale(&current));
85        assert!(!future.is_stale(&current));
86    }
87
88    #[test]
89    fn map_transforms_value_keeps_epoch() {
90        let f = Fenced::new(FenceEpoch::new(2), 10u32);
91        let mapped = f.map(|v| v * 3);
92        assert_eq!(mapped.epoch(), FenceEpoch::new(2));
93        assert_eq!(*mapped.value(), 30);
94    }
95}