Skip to main content

LockManager

Struct LockManager 

Source
pub struct LockManager { /* private fields */ }
Available on crate feature 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

Source

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());
Source

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);
Source

pub fn shards(&self) -> usize

Returns the number of shards in the table.

Always a power of two.

Source

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:

  • txn already holds a lock on res that covers mode (re-acquisition is idempotent, and asking for a weaker mode than you hold is a no-op);
  • txn already holds res in some mode and the join of that mode with mode is compatible with every other holder (an in-place upgrade — for example shared to exclusive when sole holder, or shared plus intention-exclusive to SIX); or
  • txn holds nothing on res and mode is 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));
Source

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));
Source

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 empty
Source

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);
Source

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));
Source

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),
);
Source

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));
Source

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

Trait Implementations§

Source§

impl Default for LockManager

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.