lock-db 1.0.0

Lock manager and deadlock detection for Rust databases - row/range locks, multiple granularities, and wait-for cycle detection.
Documentation
  • 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

[dependencies]
lock-db = "1"

Quick Start

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):

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:

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:

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.

  • LockMode — the five MGL modes and the compatibility matrix
  • LockManager — the sharded lock table (point locks, range locks, deadlock-aware request)
  • WaitForGraph — wait-for graph, cycle detection, and victim selection
  • KeyRange — an inclusive key interval for range locks
  • TxnId and ResourceId — opaque identifiers
  • LockError — failure modes

Examples

Runnable examples live in examples/. Run any of them with cargo run --example <name>:

Example Shows
quick_start Acquire, conflict, release on a single row.
two_phase_locking Growing-phase acquires, then release_all at commit.
shared_upgrade Read under a shared lock, then upgrade to exclusive.
hierarchy Intention locks over a database/table/page/row hierarchy.
range_locks Range locking for phantom protection.
deadlock Wait-for deadlock detection and victim abort.
concurrent 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 — transactions acquire and release locks here to enforce isolation
  • page-db — page-granularity locks coordinate with the paged store
  • 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 and 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.