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.3.0 milestone. It provides multi-granularity and range
16//! locking on top of the lock-table core:
17//!
18//! - [`LockMode`] — the five standard MGL modes (IS, IX, S, SIX, X) and their
19//! compatibility matrix, plus the lattice [join](LockMode::join) that drives
20//! upgrades.
21//! - [`LockManager`] — a sharded, non-blocking lock table with acquire,
22//! release, bulk release, lattice upgrades, and range locks.
23//! - [`KeyRange`] — an inclusive key interval, the unit a range lock protects
24//! (phantom / predicate protection).
25//! - [`TxnId`] and [`ResourceId`] — opaque identifiers the caller assigns.
26//! - [`LockError`] — the small, exhaustive set of ways an operation can fail.
27//!
28//! Acquisition is non-blocking: a request that cannot be granted returns
29//! [`LockError::Conflict`] instead of waiting. Blocking acquisition with wait
30//! queues and wait-for deadlock detection land in later 0.x releases (see
31//! `dev/ROADMAP.md`).
32//!
33//! ## Hierarchical locking
34//!
35//! The intention modes exist to lock a hierarchy — database, table, page, row —
36//! correctly and cheaply. The protocol is: before locking a resource in `S` or
37//! `X`, hold an intention lock on each coarser resource above it (`IS` above an
38//! `S`, `IX` above an `X`), acquiring coarse-to-fine and releasing fine-to-
39//! coarse. lock-db enforces the compatibility matrix at each level; the caller
40//! follows the protocol and maps each hierarchy node to a [`ResourceId`].
41//!
42//! ## Example
43//!
44//! ```
45//! use lock_db::prelude::*;
46//!
47//! let lm = LockManager::new();
48//! let row = ResourceId::new(1);
49//! let (writer, reader) = (TxnId::new(1), TxnId::new(2));
50//!
51//! // The writer takes the row exclusively.
52//! lm.try_acquire(writer, row, LockMode::Exclusive).unwrap();
53//!
54//! // A concurrent reader is refused while the write lock is held.
55//! assert_eq!(lm.try_acquire(reader, row, LockMode::Shared), Err(LockError::Conflict));
56//!
57//! // Once the writer commits and releases, the reader gets in.
58//! lm.release(writer, row).unwrap();
59//! lm.try_acquire(reader, row, LockMode::Shared).unwrap();
60//! ```
61//!
62//! Range locking, to keep another transaction from inserting into a span you
63//! have read:
64//!
65//! ```
66//! use lock_db::prelude::*;
67//!
68//! let lm = LockManager::new();
69//! let index = ResourceId::new(10); // the key space being protected
70//!
71//! // Txn 1 read-locks the key range [100, 200].
72//! lm.try_acquire_range(TxnId::new(1), index, KeyRange::new(100, 200).unwrap(), LockMode::Shared).unwrap();
73//!
74//! // Txn 2 cannot write key 150 inside that range.
75//! let conflict = lm.try_acquire_range(TxnId::new(2), index, KeyRange::point(150), LockMode::Exclusive);
76//! assert_eq!(conflict, Err(LockError::Conflict));
77//!
78//! // But a disjoint range is free.
79//! lm.try_acquire_range(TxnId::new(2), index, KeyRange::new(201, 300).unwrap(), LockMode::Exclusive).unwrap();
80//! ```
81
82#![cfg_attr(not(feature = "std"), no_std)]
83#![cfg_attr(docsrs, feature(doc_cfg))]
84#![deny(missing_docs)]
85#![deny(unused_must_use)]
86#![deny(clippy::unwrap_used)]
87#![deny(clippy::expect_used)]
88#![deny(clippy::todo)]
89#![deny(clippy::unimplemented)]
90#![deny(clippy::dbg_macro)]
91#![deny(clippy::print_stdout)]
92#![deny(clippy::print_stderr)]
93#![forbid(unsafe_code)]
94
95mod error;
96mod id;
97mod mode;
98mod range;
99
100#[cfg(feature = "std")]
101mod manager;
102
103pub use crate::error::LockError;
104pub use crate::id::{ResourceId, TxnId};
105pub use crate::mode::LockMode;
106pub use crate::range::KeyRange;
107
108#[cfg(feature = "std")]
109pub use crate::manager::LockManager;
110
111/// The crate's common imports.
112///
113/// Glob-import this to bring the lock manager, the mode enum, the identifiers,
114/// and the error type into scope in one line:
115///
116/// ```
117/// use lock_db::prelude::*;
118///
119/// let lm = LockManager::new();
120/// lm.try_acquire(TxnId::new(1), ResourceId::new(1), LockMode::Shared).unwrap();
121/// ```
122pub mod prelude {
123 pub use crate::error::LockError;
124 pub use crate::id::{ResourceId, TxnId};
125 pub use crate::mode::LockMode;
126 pub use crate::range::KeyRange;
127
128 #[cfg(feature = "std")]
129 pub use crate::manager::LockManager;
130}