Skip to main content

lock_db/
mode.rs

1//! Lock modes and the compatibility matrix.
2//!
3//! The compatibility matrix is the correctness core of a lock manager: it
4//! decides, for any two transactions contending for the same resource, whether
5//! both requests can be held at once. Get this wrong and the manager hands out
6//! conflicting locks; everything above it then corrupts data. For that reason
7//! the matrix lives in one small, `const`, exhaustively tested function rather
8//! than being scattered across the acquire path.
9//!
10//! This milestone (v0.2.0) ships the two fundamental modes, shared and
11//! exclusive. The hierarchical intention modes (IS, IX, SIX) arrive with
12//! multi-granularity locking in a later release and extend this same matrix.
13
14/// The mode in which a transaction holds, or wants to hold, a lock.
15///
16/// # Examples
17///
18/// ```
19/// use lock_db::LockMode;
20///
21/// // Two readers coexist; a writer excludes everyone.
22/// assert!(LockMode::Shared.compatible_with(LockMode::Shared));
23/// assert!(!LockMode::Shared.compatible_with(LockMode::Exclusive));
24/// assert!(!LockMode::Exclusive.compatible_with(LockMode::Exclusive));
25/// ```
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub enum LockMode {
29    /// A read lock. Any number of transactions may hold a resource `Shared` at
30    /// the same time, but none may hold it `Exclusive` while they do.
31    Shared,
32
33    /// A write lock. Held by at most one transaction, and only when no other
34    /// transaction holds the resource in any mode.
35    Exclusive,
36}
37
38impl LockMode {
39    /// Returns `true` if a lock in `self` mode and a lock in `other` mode may be
40    /// held on the same resource by two different transactions at once.
41    ///
42    /// This is the symmetric compatibility relation: `a.compatible_with(b)`
43    /// always equals `b.compatible_with(a)`. The only compatible pair is
44    /// shared/shared.
45    ///
46    /// # Examples
47    ///
48    /// ```
49    /// use lock_db::LockMode;
50    ///
51    /// for a in [LockMode::Shared, LockMode::Exclusive] {
52    ///     for b in [LockMode::Shared, LockMode::Exclusive] {
53    ///         // Symmetry holds for every pair.
54    ///         assert_eq!(a.compatible_with(b), b.compatible_with(a));
55    ///     }
56    /// }
57    /// ```
58    #[inline]
59    #[must_use]
60    pub const fn compatible_with(self, other: LockMode) -> bool {
61        matches!((self, other), (LockMode::Shared, LockMode::Shared))
62    }
63
64    /// Returns `true` if holding `self` already grants everything `other` would.
65    ///
66    /// A transaction that already holds a resource exclusively does not need to
67    /// re-acquire it to read; a transaction holding it shared still needs an
68    /// upgrade to write. This drives the idempotent and upgrade paths of
69    /// [`LockManager::try_acquire`](crate::LockManager::try_acquire).
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// use lock_db::LockMode;
75    ///
76    /// assert!(LockMode::Exclusive.covers(LockMode::Shared));
77    /// assert!(LockMode::Exclusive.covers(LockMode::Exclusive));
78    /// assert!(LockMode::Shared.covers(LockMode::Shared));
79    /// assert!(!LockMode::Shared.covers(LockMode::Exclusive));
80    /// ```
81    #[inline]
82    #[must_use]
83    pub const fn covers(self, other: LockMode) -> bool {
84        matches!(
85            (self, other),
86            (LockMode::Exclusive, _) | (LockMode::Shared, LockMode::Shared)
87        )
88    }
89
90    /// Returns `true` for [`LockMode::Exclusive`].
91    ///
92    /// # Examples
93    ///
94    /// ```
95    /// use lock_db::LockMode;
96    ///
97    /// assert!(LockMode::Exclusive.is_exclusive());
98    /// assert!(!LockMode::Shared.is_exclusive());
99    /// ```
100    #[inline]
101    #[must_use]
102    pub const fn is_exclusive(self) -> bool {
103        matches!(self, LockMode::Exclusive)
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::LockMode::{Exclusive, Shared};
110
111    #[test]
112    fn test_compatible_matrix_only_shared_shared_is_true() {
113        assert!(Shared.compatible_with(Shared));
114        assert!(!Shared.compatible_with(Exclusive));
115        assert!(!Exclusive.compatible_with(Shared));
116        assert!(!Exclusive.compatible_with(Exclusive));
117    }
118
119    #[test]
120    fn test_compatible_is_symmetric() {
121        for a in [Shared, Exclusive] {
122            for b in [Shared, Exclusive] {
123                assert_eq!(a.compatible_with(b), b.compatible_with(a));
124            }
125        }
126    }
127
128    #[test]
129    fn test_covers_reflexive() {
130        for m in [Shared, Exclusive] {
131            assert!(m.covers(m));
132        }
133    }
134
135    #[test]
136    fn test_covers_exclusive_covers_everything() {
137        assert!(Exclusive.covers(Shared));
138        assert!(Exclusive.covers(Exclusive));
139    }
140
141    #[test]
142    fn test_covers_shared_does_not_cover_exclusive() {
143        assert!(!Shared.covers(Exclusive));
144    }
145
146    #[test]
147    fn test_is_exclusive() {
148        assert!(Exclusive.is_exclusive());
149        assert!(!Shared.is_exclusive());
150    }
151
152    #[test]
153    fn test_ordering_shared_below_exclusive() {
154        assert!(Shared < Exclusive);
155    }
156}