pub struct LockManager { /* private fields */ }std only.Expand description
A sharded lock table mapping resources to the transactions that hold them.
LockManager is the primary entry point of the crate. It is Send + Sync
and is meant to be shared behind an std::sync::Arc across all worker
threads; every method takes &self, so no outer lock is needed.
§Examples
use lock_db::{LockManager, LockMode, ResourceId, TxnId};
let lm = LockManager::new();
let row = ResourceId::new(100);
let (t1, t2) = (TxnId::new(1), TxnId::new(2));
// Two transactions read the same row concurrently.
lm.try_acquire(t1, row, LockMode::Shared).unwrap();
lm.try_acquire(t2, row, LockMode::Shared).unwrap();
assert_eq!(lm.holder_count(row), 2);
// Neither can take it exclusively while the other reads.
assert!(lm.try_acquire(t1, row, LockMode::Exclusive).is_err());
// After both release, an exclusive lock is free to take.
lm.release(t1, row).unwrap();
lm.release(t2, row).unwrap();
lm.try_acquire(t1, row, LockMode::Exclusive).unwrap();Implementations§
Source§impl LockManager
impl LockManager
Sourcepub fn new() -> Self
pub fn new() -> Self
Creates a lock manager with a shard count chosen for the current machine.
The count scales with the number of available CPUs (rounded up to a power
of two) so that contention on any single shard mutex stays low on
multi-core systems. Use with_shards to pin an
exact count, for example in tests or on memory-constrained targets.
§Examples
use lock_db::LockManager;
let lm = LockManager::new();
assert!(lm.shards().is_power_of_two());Sourcepub fn with_shards(shards: usize) -> Self
pub fn with_shards(shards: usize) -> Self
Creates a lock manager with an explicit shard count.
shards is rounded up to the next power of two (and a request of 0 is
treated as 1), which lets the shard lookup use a shift instead of a
remainder. More shards reduce contention but cost a mutex and two small
maps each; fewer shards save memory at the cost of more collisions.
§Examples
use lock_db::LockManager;
// Rounded up to the next power of two.
assert_eq!(LockManager::with_shards(5).shards(), 8);
assert_eq!(LockManager::with_shards(0).shards(), 1);Sourcepub fn shards(&self) -> usize
pub fn shards(&self) -> usize
Returns the number of shards in the table.
Always a power of two.
Sourcepub fn try_acquire(
&self,
txn: TxnId,
res: ResourceId,
mode: LockMode,
) -> Result<(), LockError>
pub fn try_acquire( &self, txn: TxnId, res: ResourceId, mode: LockMode, ) -> Result<(), LockError>
Tries to acquire mode on res for txn without blocking.
The request is granted immediately and Ok(()) is returned when:
txnalready holds a lock onresthat coversmode(re-acquisition is idempotent, and asking for a weaker mode than you hold is a no-op);txnalready holdsresin some mode and the join of that mode withmodeis compatible with every other holder (an in-place upgrade — for example shared to exclusive when sole holder, or shared plus intention-exclusive to SIX); ortxnholds nothing onresandmodeis compatible with every current holder.
Otherwise nothing is changed and LockError::Conflict is returned. The
caller decides whether to retry, wait, or abort; this method never blocks
the calling thread.
§Errors
Returns LockError::Conflict if the lock cannot be granted right now.
§Examples
use lock_db::{LockError, LockManager, LockMode, ResourceId, TxnId};
let lm = LockManager::new();
let key = ResourceId::new(7);
let t = TxnId::new(1);
// Upgrade a shared lock to exclusive while sole holder.
lm.try_acquire(t, key, LockMode::Shared).unwrap();
lm.try_acquire(t, key, LockMode::Exclusive).unwrap();
assert_eq!(lm.mode_held(t, key), Some(LockMode::Exclusive));
// A second reader now conflicts with the upgraded exclusive lock.
let r = lm.try_acquire(TxnId::new(2), key, LockMode::Shared);
assert_eq!(r, Err(LockError::Conflict));Sourcepub fn release(&self, txn: TxnId, res: ResourceId) -> Result<(), LockError>
pub fn release(&self, txn: TxnId, res: ResourceId) -> Result<(), LockError>
Releases the lock txn holds on res.
§Errors
Returns LockError::NotHeld if txn holds no lock on res, which
usually means a double release or a bookkeeping mismatch in the caller.
§Examples
use lock_db::{LockError, LockManager, LockMode, ResourceId, TxnId};
let lm = LockManager::new();
let key = ResourceId::new(3);
let t = TxnId::new(1);
lm.try_acquire(t, key, LockMode::Exclusive).unwrap();
lm.release(t, key).unwrap();
assert_eq!(lm.release(t, key), Err(LockError::NotHeld));Sourcepub fn release_all(&self, txn: TxnId) -> usize
pub fn release_all(&self, txn: TxnId) -> usize
Releases every lock held by txn across the whole table — both point
locks and range locks.
This is the call a transaction layer makes at commit or abort to drop a transaction’s entire lock set at once. It returns the number of locks released, and is proportional to that number rather than to the size of the table.
§Examples
use lock_db::{KeyRange, LockManager, LockMode, ResourceId, TxnId};
let lm = LockManager::new();
let t = TxnId::new(1);
for id in 0..5 {
lm.try_acquire(t, ResourceId::new(id), LockMode::Exclusive).unwrap();
}
lm.try_acquire_range(t, ResourceId::new(99), KeyRange::point(1), LockMode::Shared).unwrap();
assert_eq!(lm.release_all(t), 6); // 5 point locks + 1 range lock
assert_eq!(lm.release_all(t), 0); // idempotent once emptySourcepub fn holder_count(&self, res: ResourceId) -> usize
pub fn holder_count(&self, res: ResourceId) -> usize
Returns the number of transactions currently holding res.
Mostly useful for diagnostics and tests; in steady state this is 0,
1 for an exclusive lock, or the reader count for a shared lock.
§Examples
use lock_db::{LockManager, LockMode, ResourceId, TxnId};
let lm = LockManager::new();
let key = ResourceId::new(1);
assert_eq!(lm.holder_count(key), 0);
lm.try_acquire(TxnId::new(1), key, LockMode::Shared).unwrap();
assert_eq!(lm.holder_count(key), 1);Sourcepub fn mode_held(&self, txn: TxnId, res: ResourceId) -> Option<LockMode>
pub fn mode_held(&self, txn: TxnId, res: ResourceId) -> Option<LockMode>
Returns the mode in which txn holds res, or None if it holds no
lock on it.
§Examples
use lock_db::{LockManager, LockMode, ResourceId, TxnId};
let lm = LockManager::new();
let key = ResourceId::new(1);
let t = TxnId::new(1);
assert_eq!(lm.mode_held(t, key), None);
lm.try_acquire(t, key, LockMode::Shared).unwrap();
assert_eq!(lm.mode_held(t, key), Some(LockMode::Shared));Sourcepub fn try_acquire_range(
&self,
txn: TxnId,
space: ResourceId,
range: KeyRange,
mode: LockMode,
) -> Result<(), LockError>
pub fn try_acquire_range( &self, txn: TxnId, space: ResourceId, range: KeyRange, mode: LockMode, ) -> Result<(), LockError>
Tries to acquire mode over the key range range in key space space,
for txn, without blocking.
A range lock protects a contiguous span of keys — use it to stop another
transaction from inserting into, or writing within, a range you have
read (phantom and predicate protection). space identifies the key space
the range lives in, typically an index; ranges in different spaces never
conflict.
The request is granted unless some other transaction already holds an
overlapping range in space in an
incompatible mode. The same transaction may
hold several ranges in a space, including overlapping ones; range locks
are not merged or upgraded.
§Errors
Returns LockError::Conflict if an overlapping, incompatible range is
held by another transaction.
§Examples
use lock_db::{KeyRange, LockError, LockManager, LockMode, ResourceId, TxnId};
let lm = LockManager::new();
let index = ResourceId::new(1);
// A read lock over [100, 200].
lm.try_acquire_range(TxnId::new(1), index, KeyRange::new(100, 200).unwrap(), LockMode::Shared).unwrap();
// Another reader may share the overlapping range...
lm.try_acquire_range(TxnId::new(2), index, KeyRange::new(150, 250).unwrap(), LockMode::Shared).unwrap();
// ...but a writer inside it conflicts.
assert_eq!(
lm.try_acquire_range(TxnId::new(3), index, KeyRange::point(150), LockMode::Exclusive),
Err(LockError::Conflict),
);Sourcepub fn release_range(
&self,
txn: TxnId,
space: ResourceId,
range: KeyRange,
) -> Result<(), LockError>
pub fn release_range( &self, txn: TxnId, space: ResourceId, range: KeyRange, ) -> Result<(), LockError>
Releases a range lock txn holds over range in space.
Matches on the transaction and the exact range. If the transaction holds several locks on the identical range (in different modes), one is released per call.
§Errors
Returns LockError::NotHeld if txn holds no lock on that exact range
in space.
§Examples
use lock_db::{KeyRange, LockError, LockManager, LockMode, ResourceId, TxnId};
let lm = LockManager::new();
let index = ResourceId::new(1);
let r = KeyRange::new(1, 10).unwrap();
let t = TxnId::new(1);
lm.try_acquire_range(t, index, r, LockMode::Exclusive).unwrap();
lm.release_range(t, index, r).unwrap();
assert_eq!(lm.release_range(t, index, r), Err(LockError::NotHeld));Sourcepub fn range_count(&self, space: ResourceId) -> usize
pub fn range_count(&self, space: ResourceId) -> usize
Returns the number of range locks currently held in space.
Counts every holder, across all transactions and modes. Mostly useful for diagnostics and tests.
§Examples
use lock_db::{KeyRange, LockManager, LockMode, ResourceId, TxnId};
let lm = LockManager::new();
let index = ResourceId::new(1);
assert_eq!(lm.range_count(index), 0);
lm.try_acquire_range(TxnId::new(1), index, KeyRange::point(1), LockMode::Shared).unwrap();
assert_eq!(lm.range_count(index), 1);