Skip to content

Latest commit

 

History

100 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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.

Workload BorrowMutex smol::lock::Mutex tokio::sync::Mutex
Uncontended (1 user) 5.329 µs 6.091 µs 21.683 µs
Contended (2 users) 217.25 µs 197.68 µs 219.33 µs
Contended (8 users) 1.2501 ms 1.1821 ms 0.90964 ms
Contended (32 users) 5.3907 ms 4.7255 ms 3.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.

About

Async Rust Mutex which does not require wrapping the target structure

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages