Skip to main content

Crate borrow_mutex

Crate borrow_mutex 

Source
Expand description

crates.io libs.rs documentation rust-version license

§BorrowMutex

BorrowMutex is an async Mutex which does not require wrapping the target structure. Instead, a &mut T can be lended to the mutex at any time.

This lets any other side borrow the &mut T. The mutable ref is borrow-able only while the lender awaits, and the lending side can await until someone wants to borrow. The semantics enforce at most one side has a mutable reference at any given time.

This lets us share any mutable object between distinct async contexts without Arc<Mutex> over the object in question and without relying on any kind of internal mutability.

The most common use case is having a state handled entirely in its own async context, but occasionally having to be accessed from the outside - another async context.

Since the shared data doesn’t have to be wrapped inside an Arc, it doesn’t have to be allocated on the heap. In fact, BorrowMutex does not perform any allocations whatsoever. The tests/borrow_basic.rs presents a simple example where everything is stored on the stack.

§Example

ⓘ
let mutex = BorrowMutex::<16, i32>::new();

let lender = async {
    let mut value = 0;
    mutex.wait_to_lend().await;
    mutex.lend(&mut value).unwrap().await;
};

let borrower = async {
    *mutex.try_borrow().await.unwrap() += 1;
};

A more realistic example would be to futures::select! on the lender side on mutex.wait_to_lend() and some conn.incoming_message().

§Benchmarks

Measured locally with:

cargo bench --bench mutex -- --sample-size 10 --warm-up-time 1 --measurement-time 1

Criterion median time per batch; lower is better. Each user performs 1,000 operations per batch. The uncontended BorrowMutex workload polls wait_to_lend().now_or_never() without a borrower; the other mutexes lock and update a value. The APIs and synchronization mechanisms are not identical, so compare the times with that distinction in mind.

WorkloadBorrowMutexsmol::lock::Mutextokio::sync::Mutex
Uncontended (1 user)5.329 µs6.091 µs21.683 µs
Contended (2 users)217.25 µs197.68 µs219.33 µs
Contended (8 users)1.2501 ms1.1821 ms0.90964 ms
Contended (32 users)5.3907 ms4.7255 ms3.5714 ms

Uncontended time per 1,000 operations (one █ ≈ 1 µs; shorter is faster):

BorrowMutex          █████                           5.33 µs
smol::lock::Mutex    ██████                          6.09 µs
tokio::sync::Mutex   ██████████████████████          21.68 µs

The barrier and repeated-lock workload is adapted from the methodology of ytakano/async_bench. The contention interpretation was also informed by khonsulabs/async-locking-benchmarks.

Both futures should print interchangeably. See tests/borrow_basic.rs for a full working example.

§Safety

Calling std::mem::forget() on a LendGuard may lead to undefined behavior. Dropping it while the value is borrowed is protected by aborting the process. See BorrowMutex::lend for the full contract.

Structs§

BorrowGuardArmed
RAII scoped lock guard. This is an armed variant which provides access to the lended value via its Deref and DerefMut implementations.
BorrowGuardUnarmed
RAII scoped lock guard. This is an unarmed variant which is still waiting for a value to be lended.
BorrowMutex
Async Mutex which does not require wrapping the target structure. A &mut T can be lended to the mutex at any given time.
LendGuard
RAII scoped lock guard for the lending side. This is created by BorrowMutex::lend(), and has to be polled until the associated borrower drops.
LendWaiter
A Future returned by BorrowMutex::wait_to_lend() which resolves as soon as any borrow request is pending.

Enums§

Error
Possible borrow errors