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 holdsresshared, wants it exclusive, and is the only holder (an in-place upgrade); or- no other transaction holds
resin a mode incompatible withmode.
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.
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::{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();
}
assert_eq!(lm.release_all(t), 5);
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));