Struct maitake::sync::RwLock

pub struct RwLock<T, Lock = Spinlock>
where Lock: RawMutex, T: ?Sized,
{ /* private fields */ }
Expand description

An asynchronous readers-writer lock.

This type of lock protects shared data by allowing either multiple concurrent readers (shared access), or a single writer (exclusive access) at a given point in time. If the shared data must be modified, write access must be acquired, preventing other threads from accessing the data while it is being written, but multiple threads can read the shared data when it is not being mutated.

This is in contrast to a Mutex, which only ever allows a single core/thread to access the shared data at any point in time. In some cases, such as when a large number of readers need to access the shared data without modifying it, using a RwLock can be more efficient than a Mutex.

Usage

The type parameter T represents the data that this lock protects. It is required that T satisfies Send to be shared across threads and Sync to allow concurrent access through readers. The RAII guards returned from the locking methods implement Deref (and DerefMut for the write methods) to allow access to the content of the lock.

The read method acquires read access to the lock, returning a RwLockReadGuard. If the lock is currently locked for write access, the read method will wait until the write access completes before allowing read access to the locked data.

The write method acquires write access to the lock, returning a RwLockWriteGuard, which implements DerefMut. If the lock is currently locked for reading or writing, the write method will wait until all current reads or the current write completes before allowing write access to the locked data.

Priority Policy

The priority policy of this lock is fair (or write-preferring), in order to ensure that readers cannot starve writers. Fairness is ensured using a first-in, first-out queue for the tasks awaiting the lock; if a task that wishes to acquire the write lock is at the head of the queue, read locks will not be given out until the write lock has been released. This is in contrast to the Rust standard library’s std::sync::RwLock, where the priority policy is dependent on the operating system’s implementation.

Overriding the blocking mutex

This type uses a blocking Mutex internally to synchronize access to its wait list. By default, this is a Spinlock. To use an alternative RawMutex implementation, use the new_with_raw_mutex constructor. See the documentation on overriding mutex implementations for more details.

Note that this type currently requires that the raw mutex implement RawMutex rather than mutex_traits::ScopedRawMutex!

Examples

use maitake_sync::RwLock;

let lock = RwLock::new(5);

// many reader locks can be held at once
{
    let r1 = lock.read().await;
    let r2 = lock.read().await;
    assert_eq!(*r1, 5);
    assert_eq!(*r2, 5);
} // read locks are dropped at this point

// only one write lock may be held, however
{
    let mut w = lock.write().await;
    *w += 1;
    assert_eq!(*w, 6);
} // write lock is dropped here

Implementations§

§

impl<T> RwLock<T>
where T: ?Sized,

pub async fn read_owned(self: &Arc<RwLock<T>>) -> OwnedRwLockReadGuard<T>

Locks this RwLock with shared read access, returning an owned RAII guard.

This method is identical to RwLock::read, execept that it requires the RwLock to be wrapped in an Arc, and returns an OwnedRwLockReadGuard that clones the Arc rather than borrowing the lock. Therefore, the returned guard is valid for the 'static lifetime.

If the lock is locked for write access, the calling task will yield and wait until there are no writers which hold the lock. There may be other readers inside the lock when the task resumes.

Note that under the priority policy of RwLock, read locks are not granted until prior write locks, to prevent starvation. Therefore deadlock may occur if a read lock is held by the current task, a write lock attempt is made, and then a subsequent read lock attempt is made by the current task.

Returns an RAII guard which will release this read access of the RwLock when dropped.

Cancellation

This method uses a queue to fairly distribute locks in the order they were requested. Cancelling a call to read results in the calling task losing its place in the queue.

Examples
use maitake_sync::RwLock;
use alloc::sync::Arc;

let lock = Arc::new(RwLock::new(1));
// hold the lock for reading in `main`.
let n = lock
    .try_read()
    .expect("read lock must be acquired, as the lock is unlocked");
assert_eq!(*n, 1);

task::spawn({
    let lock = lock.clone();
    async move {
        // While main has an active read lock, this task can acquire
        // one too.
        let n = lock.read_owned().await;
        assert_eq!(*n, 1);
    }
});

pub async fn write_owned(self: &Arc<RwLock<T>>) -> OwnedRwLockWriteGuard<T>

Locks this RwLock with exclusive write access,returning an owned RAII guard.

This method is identical to RwLock::write, execept that it requires the RwLock to be wrapped in an Arc, and returns an OwnedRwLockWriteGuard that clones the Arc rather than borrowing the lock. Therefore, the returned guard is valid for the 'static lifetime.

Returns

If other tasks are holding a read or write lock, the calling task will wait until the write lock or all read locks are released.

Returns an RAII guard which will release the write access of this RwLock when dropped.

Cancellation

This method uses a queue to fairly distribute locks in the order they were requested. Cancelling a call to write results in the calling task losing its place in the queue.

Examples
use maitake_sync::RwLock;
use alloc::sync::Arc;

let lock = Arc::new(RwLock::new(1));

task::spawn(async move {
    let mut guard = lock.write_owned().await;
    *guard += 1;
});

pub fn try_read_owned(self: &Arc<RwLock<T>>) -> Option<OwnedRwLockReadGuard<T>>

Attempts to acquire this RwLock for shared read access, without waiting, and returning an owned RAII guard.

This method is identical to RwLock::try_read, execept that it requires the RwLock to be wrapped in an Arc, and returns an OwnedRwLockReadGuard that clones the Arc rather than borrowing the lock. Therefore, the returned guard is valid for the 'static lifetime.

Returns

If the access couldn’t be acquired immediately, this method returns None rather than waiting.

Otherwise, an RAII guard is returned, which allows read access to the protected data and will release that access when dropped.

Examples
use maitake_sync::RwLock;
use alloc::sync::Arc;

let lock = Arc::new(RwLock::new(1));

let mut write_guard = lock
    .try_write()
    .expect("lock is unlocked, so write access should be acquired");
*write_guard += 1;

// because a write guard is held, we cannot acquire the read lock, so
// this will return `None`.
assert!(lock.try_read_owned().is_none());

pub fn try_write_owned( self: &Arc<RwLock<T>> ) -> Option<OwnedRwLockWriteGuard<T>>

Attempts to acquire this RwLock for exclusive write access, without waiting, and returning an owned RAII guard.

This method is identical to RwLock::try_write, execept that it requires the RwLock to be wrapped in an Arc, and returns an OwnedRwLockWriteGuard that clones the Arc rather than borrowing the lock. Therefore, the returned guard is valid for the 'static lifetime.

Returns

If the access couldn’t be acquired immediately, this method returns None rather than waiting.

Otherwise, an RAII guard is returned, which allows write access to the protected data and will release that access when dropped.

Examples
use maitake_sync::RwLock;
use alloc::sync::Arc;

let lock = Arc::new(RwLock::new(1));

let read_guard = lock
    .try_read()
    .expect("lock is unlocked, so read access should be acquired");
assert_eq!(*read_guard, 1);

// because a read guard is held, we cannot acquire the write lock, so
// this will return `None`.
assert!(lock.try_write_owned().is_none());
§

impl<T> RwLock<T>

pub const fn new(data: T) -> RwLock<T>

Returns a new RwLock protecting the provided data, in an unlocked state.

This constructor returns a RwLock that uses a Spinlock as the underlying blocking mutex implementation. To use an alternative RawMutex implementation, use the RwLock::new_with_raw_mutex constructor instead. See the documentation on overriding mutex implementations for more details.

Examples
use maitake_sync::RwLock;

let lock = RwLock::new(5);

Because this is a const fn, it may be used in static initializers:

use maitake_sync::RwLock;

static LOCK: RwLock<usize> = RwLock::new(5);
§

impl<T, Lock> RwLock<T, Lock>
where Lock: RawMutex,

pub const fn new_with_raw_mutex(data: T, lock: Lock) -> RwLock<T, Lock>

Returns a new RwLock protecting the provided data, in an unlocked state, using the provided RawMutex implementation.

This constructor allows a RwLock to be constructed with any type that implements RawMutex as the underlying raw blocking mutex implementation. See the documentation on overriding mutex implementations for more details.

pub fn into_inner(self) -> T

Consumes this RwLock, returning the guarded data.

§

impl<T, Lock> RwLock<T, Lock>
where Lock: RawMutex, T: ?Sized,

pub async fn read(&self) -> RwLockReadGuard<'_, T, Lock>

Locks this RwLock with shared read access, causing the current task to yield until the lock has been acquired.

If the lock is locked for write access, the calling task will yield and wait until there are no writers which hold the lock. There may be other readers inside the lock when the task resumes.

Note that under the priority policy of RwLock, read locks are not granted until prior write locks, to prevent starvation. Therefore deadlock may occur if a read lock is held by the current task, a write lock attempt is made, and then a subsequent read lock attempt is made by the current task.

Returns [an RAII guard] which will release this read access of the RwLock when dropped.

Cancellation

This method uses a queue to fairly distribute locks in the order they were requested. Cancelling a call to read results in the calling task losing its place in the queue.

Examples
use maitake_sync::RwLock;
use alloc::sync::Arc;

let lock = Arc::new(RwLock::new(1));

// hold the lock for reading in `main`.
let n = lock
    .try_read()
    .expect("read lock must be acquired, as the lock is unlocked");
assert_eq!(*n, 1);

task::spawn({
    let lock = lock.clone();
    async move {
        // While main has an active read lock, this task can acquire
        // one too.
        let n = lock.read().await;
        assert_eq!(*n, 1);
    }
});

[an RAII guard]:

pub async fn write(&self) -> RwLockWriteGuard<'_, T, Lock>

Locks this RwLock with exclusive write access, causing the current task to yield until the lock has been acquired.

If other tasks are holding a read or write lock, the calling task will wait until the write lock or all read locks are released.

Returns [an RAII guard] which will release the write access of this RwLock when dropped.

Cancellation

This method uses a queue to fairly distribute locks in the order they were requested. Cancelling a call to write results in the calling task losing its place in the queue.

Examples
use maitake_sync::RwLock;
use alloc::sync::Arc;

let lock = Arc::new(RwLock::new(1));

task::spawn(async move {
    let mut guard = lock.write().await;
    *guard += 1;
});

pub fn try_read(&self) -> Option<RwLockReadGuard<'_, T, Lock>>

Attempts to acquire this RwLock for shared read access, without waiting.

If the access couldn’t be acquired immediately, this method returns None rather than waiting.

Otherwise, an RAII guard is returned, which allows read access to the protected data and will release that access when dropped.

Examples
use maitake_sync::RwLock;

let lock = RwLock::new(1);

let mut write_guard = lock
    .try_write()
    .expect("lock is unlocked, so write access should be acquired");
*write_guard += 1;

// because a write guard is held, we cannot acquire the read lock, so
// this will return `None`.
assert!(lock.try_read().is_none());

pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, T, Lock>>

Attempts to acquire this RwLock for exclusive write access, without waiting.

If the access couldn’t be acquired immediately, this method returns None rather than waiting.

Otherwise, an RAII guard is returned, which allows write access to the protected data and will release that access when dropped.

Examples
use maitake_sync::RwLock;

let lock = RwLock::new(1);

let read_guard = lock
    .try_read()
    .expect("lock is unlocked, so read access should be acquired");
assert_eq!(*read_guard, 1);

// because a read guard is held, we cannot acquire the write lock, so
// this will return `None`.
assert!(lock.try_write().is_none());

pub fn get_mut(&mut self) -> &mut T

Returns a mutable reference to the underlying data.

Since this call borrows the RwLock mutably, no actual locking needs to take place – the mutable borrow statically guarantees no locks exist.

Examples
let mut lock = maitake_sync::RwLock::new(0);
*lock.get_mut() = 10;
assert_eq!(*lock.try_read().unwrap(), 10);

Trait Implementations§

§

impl<T, Lock> Debug for RwLock<T, Lock>
where T: Debug + ?Sized, Lock: RawMutex + Debug,

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl<T> Default for RwLock<T>
where T: Default,

§

fn default() -> RwLock<T>

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

impl<T, Lock> Send for RwLock<T, Lock>
where T: Send + ?Sized, Lock: RawMutex + Send,

§

impl<T, Lock> Sync for RwLock<T, Lock>
where T: Send + Sync + ?Sized, Lock: RawMutex + Sync,

Auto Trait Implementations§

§

impl<T, Lock = Spinlock> !RefUnwindSafe for RwLock<T, Lock>

§

impl<T: ?Sized, Lock> Unpin for RwLock<T, Lock>
where Lock: Unpin, T: Unpin,

§

impl<T, Lock = Spinlock> !UnwindSafe for RwLock<T, Lock>

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.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
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>,

§

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>,

§

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.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more