https://github.com/jamesgober/lock-db
Lock manager and deadlock detection for Rust databases - row/range locks, multiple granularities, and wait-for cycle detection.
https://github.com/jamesgober/lock-db
concurrency database deadlock-detection lock-manager locking reps rust transactions
Last synced: 2 days ago
JSON representation
Lock manager and deadlock detection for Rust databases - row/range locks, multiple granularities, and wait-for cycle detection.
- Host: GitHub
- URL: https://github.com/jamesgober/lock-db
- Owner: jamesgober
- License: apache-2.0
- Created: 2026-06-05T13:59:42.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-06-06T01:11:19.000Z (about 1 month ago)
- Last Synced: 2026-06-06T02:19:12.522Z (about 1 month ago)
- Topics: concurrency, database, deadlock-detection, lock-manager, locking, reps, rust, transactions
- Language: Rust
- Size: 91.8 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE-APACHE
Awesome Lists containing this project
README
lock-db
LOCK MANAGER & DEADLOCK DETECTION
lock-db is the lock manager for a transactional database: the component that lets many transactions touch shared data at once without corrupting it, and that notices when they have deadlocked and breaks the tie.
It provides row and range locks across multiple granularities (database, table, page, row) with the standard lock modes and a compatibility matrix, and it builds a wait-for graph to detect deadlock cycles and select a victim to abort.
MSRV is 1.85+ (Rust 2024 edition). Row/range locks. Hierarchical granularity. Wait-for deadlock detection.
Status: stable. v1.0.0 — the public API is frozen until 2.0. The feature set is complete (the five MGL modes, hierarchical and range locks, and wait-for deadlock detection), verified by property, loom, and adversarial-contention / deadlock-storm stress suites, and green across Linux, macOS, and Windows.
What it does
- **Lock modes** — the five standard multi-granularity modes — intention-shared (IS), intention-exclusive (IX), shared (S), shared-intention-exclusive (SIX), and exclusive (X) — with a `const` compatibility matrix at the core of every grant decision
- **Hierarchical granularities** — lock a database / table / page / row hierarchy correctly with intention locks; the manager enforces the matrix at every level
- **Range locks** — lock a contiguous span of keys (`KeyRange`) for predicate / phantom protection, with overlap-based conflict detection
- **Deadlock detection** — a wait-for graph with cycle detection and victim selection; the deadlock-aware `request` records waits and reports cycles, and `WaitForGraph` is reusable on its own
- **Sharded lock table** — the resource space is partitioned across independent shards so acquisitions on unrelated resources never contend on the same mutex
- **Acquire / release** — non-blocking `try_acquire`, single and bulk release, re-entrant acquisition, and lattice upgrades (e.g. S + IX → SIX)
## Installation
```toml
[dependencies]
lock-db = "1"
```
## Quick Start
```rust
use lock_db::prelude::*;
// One manager, shared across all worker threads behind an `Arc`.
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-lock a span of keys to keep another transaction from inserting into it
(phantom protection):
```rust
use lock_db::prelude::*;
let lm = LockManager::new();
let index = ResourceId::new(10); // the key space being protected
// Txn 1 read-locks keys [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, but a disjoint range is free.
assert!(lm.try_acquire_range(TxnId::new(2), index, KeyRange::point(150), LockMode::Exclusive).is_err());
lm.try_acquire_range(TxnId::new(2), index, KeyRange::new(201, 300).unwrap(), LockMode::Exclusive).unwrap();
```
A transaction drops its whole lock set — point and range — in one call at commit
or abort:
```rust
use lock_db::prelude::*;
let lm = LockManager::new();
let txn = TxnId::new(1);
for id in 0..3 {
lm.try_acquire(txn, ResourceId::new(id), LockMode::Exclusive).unwrap();
}
assert_eq!(lm.release_all(txn), 3);
```
The deadlock-aware `request` records waits and reports cycles, naming a victim to
abort:
```rust
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 victim to break it.
if let Acquisition::Deadlock(d) = lm.request(t2, a, LockMode::Exclusive) {
lm.release_all(d.victim);
}
```
## API Overview
For the complete reference with method tables and examples, see [`docs/API.md`](./docs/API.md).
- [`LockMode`](./docs/API.md#lockmode) — the five MGL modes and the compatibility matrix
- [`LockManager`](./docs/API.md#lockmanager) — the sharded lock table (point locks, range locks, deadlock-aware `request`)
- [`WaitForGraph`](./docs/API.md#waitforgraph) — wait-for graph, cycle detection, and victim selection
- [`KeyRange`](./docs/API.md#keyrange) — an inclusive key interval for range locks
- [`TxnId` and `ResourceId`](./docs/API.md#identifiers) — opaque identifiers
- [`LockError`](./docs/API.md#lockerror) — failure modes
## Examples
Runnable examples live in [`examples/`](./examples). Run any of them with
`cargo run --example `:
| Example | Shows |
|---------|-------|
| [`quick_start`](./examples/quick_start.rs) | Acquire, conflict, release on a single row. |
| [`two_phase_locking`](./examples/two_phase_locking.rs) | Growing-phase acquires, then `release_all` at commit. |
| [`shared_upgrade`](./examples/shared_upgrade.rs) | Read under a shared lock, then upgrade to exclusive. |
| [`hierarchy`](./examples/hierarchy.rs) | Intention locks over a database/table/page/row hierarchy. |
| [`range_locks`](./examples/range_locks.rs) | Range locking for phantom protection. |
| [`deadlock`](./examples/deadlock.rs) | Wait-for deadlock detection and victim abort. |
| [`concurrent`](./examples/concurrent.rs) | Many threads contending on one row, with a mutual-exclusion check. |
## Where It Fits
`lock-db` is the concurrency-control layer. It is used by:
- [`txn-db`](https://github.com/jamesgober/txn-db) — transactions acquire and release locks here to enforce isolation
- [`page-db`](https://github.com/jamesgober/page-db) — page-granularity locks coordinate with the paged store
- [`index-db`](https://github.com/jamesgober/index-db) — range locks protect B+tree key ranges against phantoms
- storage engines — any engine needing pessimistic concurrency control
It has no first-party dependencies, so it builds and tests standalone today.
## Cross-Platform Support
Linux (x86_64, aarch64), macOS (x86_64, Apple Silicon), and Windows (x86_64) are first-class and verified by the CI matrix.
## Contributing
See [`CONTRIBUTING.md`](./CONTRIBUTING.md) and [`dev/DIRECTIVES.md`](./dev/DIRECTIVES.md). Before a PR: `cargo fmt --all`, `cargo clippy --all-targets --all-features -- -D warnings`, and `cargo test --all-features` must be clean.
License
Licensed under either of
-
Apache License, Version 2.0 — LICENSE-APACHE
-
MIT License — LICENSE-MIT
at your option.
COPYRIGHT © 2026 JAMES GOBER.