Crate async_condvar_fair[][src]

Expand description

async-condvar-fair

Condition variables for async Rust. Features:

  • Fair. notify_one always wakes up the longest-waiting waiter.

  • Novel Baton facility to help avoid dropped notifications which can otherwise arise through async task cancellation.

  • Works with any async runtime.

  • Can work with any kind of mutex (sync or async).

  • 100% safe code. (Uses parking_lot internally.)

  • Can handle multiple different mutexes interacting with multiple different condvars, if you like that kind of thing.

Main entrypoint

The main entrypoint to this library is Condvar. See its documentation for details of the constructors and methods available.

Example

use std::collections::VecDeque;
use async_condvar_fair::{Condvar, BatonExt};
use parking_lot::Mutex;

struct Shared {
    cv: Condvar,
    queue: Mutex<VecDeque<String>>,
}

impl Shared {
    pub async fn procssor(&self) {
        let mut guard = self.queue.lock();
        let mut baton = None;
        loop {
            while let Some(entry) = guard.pop_front() {
                println!("processing {:?}", &entry);
                if entry.is_empty() { break }
            }
            baton.dispose();

            let got = self.cv.wait_baton(guard).await;
            guard = got.0;
            baton = got.1;
        }
    }
}

Mutexes - choosing a mutex, sync vs async

Note that it can make perfect sense to have an async condvar, but protect the shared state with a sync mutex. If the operations which occur while holding the mutex are fast (and, in particular, do not perform IO), this is usually the best choice.

parking_lot::Mutex is a good choice for such a sync mutex, and is conveniently supported by async-condvar-fair.

If you use a sync mutex, you probably don’t intend to be awaiting with the mutex held, so you can probably use plain wait (rather than wait_baton).

If the operations you do while holding the mutex are slow, you should use the async mutex supplied with your async runtime. In this case, if you are using notify_one you should consider using wait_baton to avoid task cancellation causing lost notifications: see Baton.

Mutexes - how to pass the mutex to wait_baton et al

Condvar::wait_baton and wait can in principle work with any mutex. But they need to know how to relock the mutex.

For the most convenient mutexes, like parking_lot::Mutex, can just pass the guard to wait. Condvar will use the guard to unlock the mutex, and then to relock it again during wakeup.

But for many mutexes, this is not possible, because the guard type does not provide a way to get back to the unlocked mutex reference. This is the case, for example, for std::sync::Mutex.

For these inconvenient mutexes, you can pass a tuple to wait_baton or wait, or use wait_no_relock and relock the mutex yourself.

featuremutex typepass to wait / wait_baton
always enabledparking_lot::MutexMutexGuard
always enabledparking_lot::FairMutexFairMutexGuard
always enabledstd::sync::Mutex(MutexGuard, &Mutex)
tokio[tokio::sync::Mutex](MutexGuard, &Mutex)
tokioArc<[tokio::sync::Mutex]>(OwnedMutexGuard, Arc<Mutex>)
tokio[tokio::sync::RwLock](RwLockReadGuard, &Mutex)
tokio[tokio::sync::RwLock](RwLockWriteGuard, &Mutex)
tokioArc<[tokio::sync::RwLock]>(OwnedRwLockReadGuard, Arc<RwLock>)
tokioArc<[tokio::sync::RwLock]>(OwnedRwLockWriteGuard, Arc<RwLock>)
smol[smol::lock::Mutex]MutexGuard
smol[smol::lock::RwLock](RwLockReadGuard, &RwLock)
smol[smol::lock::RwLock](RwLockWriteGuard, &RwLock)
smolArc<[smol::lock::Mutex]>MutexGuardArc

This is all achieved through provided implementations of the RelockMutexGuard trait.

If you want to use a mutex type without builtin support in async-condvar-wait, use the RelockMutexGuard! macro to define a suitable impl, define that impl by hand, or use wait_no_relock and relock the mutex yourself each time.

Example of passing a tuple of guard and mutex

use std::sync::Mutex;
struct Shared {
    cv: Condvar,
    queue: Mutex<VecDeque<String>>,
}
loop {
    while let Some(entry) = guard.pop_front() {
        println!("processing {:?}", &entry);
        if entry.is_empty() { break }
    }
    baton.dispose();

    let got = self.cv.wait_baton((guard, &self.queue)).await;
    guard = got.0;
    baton = got.1;
}

Macros

Implements RelockMutexGuard (needed for any mutexes used with a condvar)

Structs

Obligation to do work, or notify someone else to do it

Condition variable (for async)

Future for waiting, from wait_baton

Traits

Extension trait for Option<Baton> to provide dispose and pass

Lock guards that can be unlocked and relocked