grafos_fence/
guard.rs

1use crate::{FenceEpoch, StaleEpochError};
2
3/// A stateful epoch checker that rejects operations with stale epochs.
4///
5/// Holds the "current" epoch and provides `check()` / `advance()` methods.
6/// Typical use: place a `FenceGuard` in front of a resource and call `check()`
7/// on every incoming operation's epoch before allowing it through.
8#[derive(Debug, Clone)]
9pub struct FenceGuard {
10    expected: FenceEpoch,
11}
12
13impl FenceGuard {
14    /// Create a guard expecting the given epoch.
15    pub fn new(expected: FenceEpoch) -> Self {
16        Self { expected }
17    }
18
19    /// Check that `incoming` is not stale. Returns `Ok(())` if `incoming`
20    /// is equal to or newer than the expected epoch. Returns
21    /// `Err(StaleEpochError)` if `incoming` is behind.
22    pub fn check(&self, incoming: FenceEpoch) -> Result<(), StaleEpochError> {
23        if incoming.is_stale(&self.expected) {
24            Err(StaleEpochError {
25                expected: self.expected,
26                received: incoming,
27            })
28        } else {
29            Ok(())
30        }
31    }
32
33    /// Bump the expected epoch by one and return the new epoch.
34    pub fn advance(&mut self) -> FenceEpoch {
35        self.expected = self.expected.bump();
36        self.expected
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    #[test]
45    fn check_passes_for_current() {
46        let guard = FenceGuard::new(FenceEpoch::new(3));
47        assert!(guard.check(FenceEpoch::new(3)).is_ok());
48    }
49
50    #[test]
51    fn check_passes_for_newer() {
52        let guard = FenceGuard::new(FenceEpoch::new(3));
53        assert!(guard.check(FenceEpoch::new(5)).is_ok());
54    }
55
56    #[test]
57    fn check_fails_for_stale() {
58        let guard = FenceGuard::new(FenceEpoch::new(3));
59        let err = guard.check(FenceEpoch::new(1)).unwrap_err();
60        assert_eq!(err.expected, FenceEpoch::new(3));
61        assert_eq!(err.received, FenceEpoch::new(1));
62    }
63
64    #[test]
65    fn advance_bumps_and_returns_new() {
66        let mut guard = FenceGuard::new(FenceEpoch::zero());
67        let e1 = guard.advance();
68        assert_eq!(e1, FenceEpoch::new(1));
69
70        // Old epoch 0 is now stale
71        assert!(guard.check(FenceEpoch::zero()).is_err());
72        // New epoch passes
73        assert!(guard.check(e1).is_ok());
74
75        let e2 = guard.advance();
76        assert_eq!(e2, FenceEpoch::new(2));
77        assert!(guard.check(e1).is_err());
78        assert!(guard.check(e2).is_ok());
79    }
80}