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}