1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! # lock-db
//!
//! Lock manager and deadlock detection for Rust databases — row/range locks,
//! multiple granularities, and wait-for cycle detection.
//!
//! A lock manager is the component that lets many transactions touch shared
//! data at once without corrupting it. Each transaction asks for a lock on a
//! resource in a [`LockMode`]; the manager grants it only when the mode is
//! compatible with what every other transaction already holds. That single
//! rule — the compatibility matrix — is what keeps concurrent reads and writes
//! correct.
//!
//! ## What is in this release
//!
//! This is v1.0.0 — the stable release. The public API is frozen until 2.0. The
//! crate provides multi-granularity locking, range locking, and wait-for
//! deadlock detection:
//!
//! - [`LockMode`] — the five standard MGL modes (IS, IX, S, SIX, X) and their
//! compatibility matrix, plus the lattice [join](LockMode::join) that drives
//! upgrades.
//! - [`LockManager`] — a sharded lock table with acquire, release, bulk release,
//! lattice upgrades, range locks, and the deadlock-aware
//! [`request`](LockManager::request).
//! - [`WaitForGraph`] — a wait-for graph with cycle detection and
//! [victim selection](VictimPolicy); the manager builds one to detect
//! deadlocks, and it is reusable on its own.
//! - [`KeyRange`] — an inclusive key interval, the unit a range lock protects
//! (phantom / predicate protection).
//! - [`TxnId`] and [`ResourceId`] — opaque identifiers the caller assigns.
//! - [`LockError`] — the small, exhaustive set of ways an operation can fail.
//!
//! [`try_acquire`](LockManager::try_acquire) is the non-blocking fast path: a
//! request that cannot be granted returns [`LockError::Conflict`] and is not
//! tracked. [`request`](LockManager::request) is the deadlock-aware path: a
//! request that cannot be granted is recorded in the wait-for graph and reports
//! [`Acquisition::Waiting`] or, if it closes a cycle,
//! [`Acquisition::Deadlock`]. lock-db detects deadlocks and names a victim; the
//! transaction layer above suspends, retries, and aborts.
//!
//! ## Hierarchical locking
//!
//! The intention modes exist to lock a hierarchy — database, table, page, row —
//! correctly and cheaply. The protocol is: before locking a resource in `S` or
//! `X`, hold an intention lock on each coarser resource above it (`IS` above an
//! `S`, `IX` above an `X`), acquiring coarse-to-fine and releasing fine-to-
//! coarse. lock-db enforces the compatibility matrix at each level; the caller
//! follows the protocol and maps each hierarchy node to a [`ResourceId`].
//!
//! ## Example
//!
//! ```
//! use lock_db::prelude::*;
//!
//! let lm = LockManager::new();
//! let row = ResourceId::new(1);
//! let (writer, reader) = (TxnId::new(1), TxnId::new(2));
//!
//! // The writer takes the row exclusively.
//! lm.try_acquire(writer, row, LockMode::Exclusive).unwrap();
//!
//! // A concurrent reader is refused while the write lock is held.
//! assert_eq!(lm.try_acquire(reader, row, LockMode::Shared), Err(LockError::Conflict));
//!
//! // Once the writer commits and releases, the reader gets in.
//! lm.release(writer, row).unwrap();
//! lm.try_acquire(reader, row, LockMode::Shared).unwrap();
//! ```
//!
//! Range locking, to keep another transaction from inserting into a span you
//! have read:
//!
//! ```
//! use lock_db::prelude::*;
//!
//! let lm = LockManager::new();
//! let index = ResourceId::new(10); // the key space being protected
//!
//! // Txn 1 read-locks the key range [100, 200].
//! lm.try_acquire_range(TxnId::new(1), index, KeyRange::new(100, 200).unwrap(), LockMode::Shared).unwrap();
//!
//! // Txn 2 cannot write key 150 inside that range.
//! let conflict = lm.try_acquire_range(TxnId::new(2), index, KeyRange::point(150), LockMode::Exclusive);
//! assert_eq!(conflict, Err(LockError::Conflict));
//!
//! // But a disjoint range is free.
//! lm.try_acquire_range(TxnId::new(2), index, KeyRange::new(201, 300).unwrap(), LockMode::Exclusive).unwrap();
//! ```
//!
//! Deadlock-aware acquisition, which records waits and reports cycles:
//!
//! ```
//! use lock_db::prelude::*;
//!
//! let lm = LockManager::new();
//! let (a, b) = (ResourceId::new(1), ResourceId::new(2));
//! let (t1, t2) = (TxnId::new(1), TxnId::new(2));
//!
//! lm.request(t1, a, LockMode::Exclusive); // T1 holds A
//! lm.request(t2, b, LockMode::Exclusive); // T2 holds B
//! lm.request(t1, b, LockMode::Exclusive); // T1 waits for T2
//!
//! // T2 waiting for A closes the cycle; abort the named victim to break it.
//! if let Acquisition::Deadlock(d) = lm.request(t2, a, LockMode::Exclusive) {
//! lm.release_all(d.victim);
//! }
//! ```
pub use crateLockError;
pub use crate;
pub use crateLockMode;
pub use crateKeyRange;
pub use crate;
pub use crate;
/// The crate's common imports.
///
/// Glob-import this to bring the lock manager, the mode enum, the identifiers,
/// and the error type into scope in one line:
///
/// ```
/// use lock_db::prelude::*;
///
/// let lm = LockManager::new();
/// lm.try_acquire(TxnId::new(1), ResourceId::new(1), LockMode::Shared).unwrap();
/// ```