keepcalm 0.6.0

Simple shared types for multi-threaded programs
Documentation
use std::{sync::Arc, time::Duration};

use criterion::{criterion_group, criterion_main, Criterion};
use keepcalm::{make_spawner, Shared, Spawner};

static SPAWNER: Spawner = make_spawner!(tokio::task::spawn_blocking);

async fn acquire_lock_tokio(lock: &tokio::sync::Mutex<usize>) -> usize {
    *lock.lock().await
}

async fn acquire_lock_and_sleep_tokio(lock: Arc<tokio::sync::Mutex<usize>>) {
    let lock2 = lock.clone();
    let f = tokio::spawn(async move {
        let l = lock2.lock().await;
        tokio::time::sleep(Duration::from_micros(10)).await;
        drop(l);
    });
    _ = *lock.lock().await;
    _ = f.await;
}

async fn acquire_rwlock_tokio(lock: &tokio::sync::RwLock<usize>) -> usize {
    *lock.read().await
}

async fn acquire_lock_keepcalm(lock: &Shared<usize>) -> usize {
    *lock.read_async(&SPAWNER).await
}

async fn acquire_lock_and_sleep_keepcalm(lock: Shared<usize>) {
    let lock2 = lock.clone();
    let f = tokio::spawn(async move {
        // This benchmark deliberately holds the lock across `.await`, so it needs the opt-in
        // `Send` guard. Ordinary `read()` guards are `!Send` and cannot cross the spawn boundary.
        let l = lock2.read_send();
        tokio::time::sleep(Duration::from_micros(10)).await;
        drop(l);
    });
    _ = *lock.read_async(&SPAWNER).await;
    _ = f.await;
}

async fn acquire_lock_keepcalm_noasync(lock: &Shared<usize>) -> usize {
    *lock.read()
}

fn async_benchmark(c: &mut Criterion) {
    c.bench_function("tokio mutex", |b| {
        let lock = tokio::sync::Mutex::new(1);
        let runtime = tokio::runtime::Runtime::new().unwrap();
        b.to_async(runtime).iter(|| acquire_lock_tokio(&lock));
    });

    c.bench_function("tokio mutex (contended)", |b| {
        let lock = Arc::new(tokio::sync::Mutex::new(1));
        let runtime = tokio::runtime::Runtime::new().unwrap();
        b.to_async(runtime)
            .iter(|| acquire_lock_and_sleep_tokio(lock.clone()));
    });

    c.bench_function("tokio rwlock", |b| {
        let lock = tokio::sync::RwLock::new(1);
        let runtime = tokio::runtime::Runtime::new().unwrap();
        b.to_async(runtime).iter(|| acquire_rwlock_tokio(&lock));
    });

    c.bench_function("keepcalm mutex", |b| {
        let lock = Shared::new_mutex(1);
        let runtime = tokio::runtime::Runtime::new().unwrap();
        b.to_async(runtime).iter(|| acquire_lock_keepcalm(&lock));
    });

    c.bench_function("keepcalm mutex (contended)", |b| {
        let lock = Shared::new_mutex(1);
        let runtime = tokio::runtime::Runtime::new().unwrap();
        b.to_async(runtime)
            .iter(|| acquire_lock_and_sleep_keepcalm(lock.clone()));
    });

    c.bench_function("keepcalm rwlock", |b| {
        let lock = Shared::new(1);
        let runtime = tokio::runtime::Runtime::new().unwrap();
        b.to_async(runtime).iter(|| acquire_lock_keepcalm(&lock));
    });

    c.bench_function("keepcalm rwlock (no async)", |b| {
        let lock = Shared::new(1);
        let runtime = tokio::runtime::Runtime::new().unwrap();
        b.to_async(runtime)
            .iter(|| acquire_lock_keepcalm_noasync(&lock));
    });
}

fn sync_benchmark(c: &mut Criterion) {
    c.bench_function("parking_lot mutex (raw)", |b| {
        let lock = parking_lot::const_mutex(1);
        b.iter(|| lock.lock());
    });

    c.bench_function("keepcalm mutex (raw)", |b| {
        let lock = Shared::new(1);
        b.iter(|| lock.read());
    });
}

criterion_group!(benches, async_benchmark, sync_benchmark);
criterion_main!(benches);