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 shared, wants it exclusive, and is the only holder (an in-place upgrade); or
  • no other transaction holds res in a mode incompatible with mode.

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.

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

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.