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