LockPool

Trait LockPool 

Source
pub trait LockPool<K>: Default
where K: Eq + PartialEq + Hash + Clone + Debug,
{ type Guard<'a>: Guard<K> where Self: 'a; type OwnedGuard: Guard<K>; // Required methods fn num_locked_or_poisoned(&self) -> usize; fn lock( &self, key: K, ) -> Result<Self::Guard<'_>, PoisonError<K, Self::Guard<'_>>>; fn lock_owned( self: &Arc<Self>, key: K, ) -> Result<Self::OwnedGuard, PoisonError<K, Self::OwnedGuard>>; fn try_lock( &self, key: K, ) -> Result<Self::Guard<'_>, TryLockError<K, Self::Guard<'_>>>; fn try_lock_owned( self: &Arc<Self>, key: K, ) -> Result<Self::OwnedGuard, TryLockError<K, Self::OwnedGuard>>; fn unpoison(&self, key: K) -> Result<(), UnpoisonError>; // Provided method fn new() -> Self { ... } }
Expand description

A pool of locks where individual locks can be locked/unlocked by key. It initially considers all keys as “unlocked”, but they can be locked and if a second thread tries to acquire a lock for the same key, they will have to wait.

This trait is implemented by [TokioLockPool] and [SyncLockPool]. [SyncLockPool] is a little faster but its locks cannot be held across await points in asynchronous code. [TokioLockPool] can be used in both synchronous and asynchronous code and offers methods for each.

use lockpool::{LockPool, SyncLockPool};

let pool = SyncLockPool::new();
let guard1 = pool.lock(4)?;
let guard2 = pool.lock(5)?;

// This next line would cause a deadlock or panic because `4` is already locked on this thread
// let guard3 = pool.lock(4)?;

// After dropping the corresponding guard, we can lock it again
std::mem::drop(guard1);
let guard3 = pool.lock(4)?;

You can use an arbitrary type to index locks by, as long as that type implements PartialEq + Eq + Hash + Clone + Debug.

use lockpool::{LockPool, SyncLockPool};

#[derive(PartialEq, Eq, Hash, Clone, Debug)]
struct CustomLockKey(u32);

let pool = SyncLockPool::new();
let guard = pool.lock(CustomLockKey(4))?;

Under the hood, a LockPool is a HashMap of Mutexes, with some logic making sure there aren’t any race conditions when accessing the hash map.

Required Associated Types§

Source

type Guard<'a>: Guard<K> where Self: 'a

A handle to a held lock. The guard cannot be held across .await points. The guard internally borrows the LockPool, so the LockPool will not be dropped while a guard exists. The lock is automatically released whenever the guard is dropped, at which point LockPool::lock or LockPool::lock_owned with the same key will succeed yet again.

Source

type OwnedGuard: Guard<K>

An owned handle to a held lock. This guard is only available from a LockPool that is wrapped in an Arc. It is identical to LockPool::Guard, except that rather than borrowing the LockPool, it clones the Arc, incrementing the reference count. This means that unlike LockPool::Guard, it will have the 'static lifetime. The lock is automatically released whenever the guard is dropped, at which point LockPool::lock or LockPool::lock_owned with the same key will succeed yet again.

Required Methods§

Source

fn num_locked_or_poisoned(&self) -> usize

Return the number of locked locks

Corner case: Poisoned locks count as locked even if they’re currently not locked

Source

fn lock( &self, key: K, ) -> Result<Self::Guard<'_>, PoisonError<K, Self::Guard<'_>>>

Lock a lock by key.

If the lock with this key is currently locked by a different thread, then the current thread blocks until it becomes available. Upon returning, the thread is the only thread with the lock held. A RAII guard is returned to allow scoped unlock of the lock. When the guard goes out of scope, the lock will be unlocked.

The exact behavior on locking a lock in the thread which already holds the lock is left unspecified. However, this function will not return on the second call (it might panic or deadlock, for example).

§Errors

If another user of this lock panicked while holding the lock, then this call will return an error once the lock is acquired.

§Panics
  • This function might panic when called if the lock is already held by the current thread.
  • If this is called through [TokioLockPool], then this function will also panic when called from an async context. See documentation of [TokioLockPool] for details.
§Examples
use lockpool::{LockPool, SyncLockPool};

let pool = SyncLockPool::new();
let guard1 = pool.lock(4)?;
let guard2 = pool.lock(5)?;

// This next line would cause a deadlock or panic because `4` is already locked on this thread
// let guard3 = pool.lock(4)?;

// After dropping the corresponding guard, we can lock it again
std::mem::drop(guard1);
let guard3 = pool.lock(4)?;
Source

fn lock_owned( self: &Arc<Self>, key: K, ) -> Result<Self::OwnedGuard, PoisonError<K, Self::OwnedGuard>>

Lock a lock by key.

This is similar to LockPool::lock, but it works on an Arc<LockPool> instead of a LockPool and returns a Guard that binds its lifetime to the LockPool in that Arc. Such a Guard can be more easily moved around or cloned.

§Errors

If another user of this lock panicked while holding the lock, then this call will return an error once the lock is acquired.

§Panics
  • This function might panic when called if the lock is already held by the current thread.
  • If this is called through [TokioLockPool], then this function will also panic when called from an async context. See documentation of [TokioLockPool] for details.
§Examples
use lockpool::{LockPool, SyncLockPool};
use std::sync::Arc;

let pool = Arc::new(SyncLockPool::new());
let guard1 = pool.lock_owned(4)?;
let guard2 = pool.lock_owned(5)?;

// This next line would cause a deadlock or panic because `4` is already locked on this thread
// let guard3 = pool.lock_owned(4)?;

// After dropping the corresponding guard, we can lock it again
std::mem::drop(guard1);
let guard3 = pool.lock_owned(4)?;
Source

fn try_lock( &self, key: K, ) -> Result<Self::Guard<'_>, TryLockError<K, Self::Guard<'_>>>

Attempts to acquire the lock with the given key.

If the lock could not be acquired at this time, then Err is returned. Otherwise, a RAII guard is returned. The lock will be unlocked when the guard is dropped.

This function does not block.

§Errors
  • If another user of this lock panicked while holding the lock, then this call will return TryLockError::Poisoned.
  • If the lock could not be acquired because it is already locked, then this call will return TryLockError::WouldBlock.
§Examples
use lockpool::{TryLockError, LockPool, SyncLockPool};

let pool = SyncLockPool::new();
let guard1 = pool.lock(4)?;
let guard2 = pool.lock(5)?;

// This next line would cause a deadlock or panic because `4` is already locked on this thread
let guard3 = pool.try_lock(4);
assert!(matches!(guard3.unwrap_err(), TryLockError::WouldBlock));

// After dropping the corresponding guard, we can lock it again
std::mem::drop(guard1);
let guard3 = pool.lock(4)?;
Source

fn try_lock_owned( self: &Arc<Self>, key: K, ) -> Result<Self::OwnedGuard, TryLockError<K, Self::OwnedGuard>>

Attempts to acquire the lock with the given key.

This is similar to LockPool::try_lock, but it works on an Arc<LockPool> instead of a LockPool and returns a Guard that binds its lifetime to the LockPool in that Arc. Such a Guard can be more easily moved around or cloned.

This function does not block.

§Errors
  • If another user of this lock panicked while holding the lock, then this call will return TryLockError::Poisoned.
  • If the lock could not be acquired because it is already locked, then this call will return TryLockError::WouldBlock.
§Examples
use lockpool::{TryLockError, LockPool, SyncLockPool};
use std::sync::Arc;

let pool = Arc::new(SyncLockPool::new());
let guard1 = pool.lock(4)?;
let guard2 = pool.lock(5)?;

// This next line would cause a deadlock or panic because `4` is already locked on this thread
let guard3 = pool.try_lock_owned(4);
assert!(matches!(guard3.unwrap_err(), TryLockError::WouldBlock));

// After dropping the corresponding guard, we can lock it again
std::mem::drop(guard1);
let guard3 = pool.lock(4)?;
Source

fn unpoison(&self, key: K) -> Result<(), UnpoisonError>

Unpoisons a poisoned lock.

Generally, once a thread panics while a lock is held, that lock is poisoned forever and all future attempts at locking it will return a PoisonError. This is since the resources protected by the lock are likely in an invalid state if the thread panicked while having the lock.

However, if you need an escape hatch, this function is it. Using it, you can unpoison a lock so that it can be locked again. This only works if currently no other thread is waiting for the lock.

§Errors:

Provided Methods§

Source

fn new() -> Self

Create a new lock pool where no lock is locked

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§