lockbell 0.1.2

An RwLock that rings back when contention clears.
Documentation
use std::{
    panic::{self, AssertUnwindSafe},
    sync::{
        Arc,
        atomic::{AtomicBool, Ordering::Relaxed},
    },
};

use lockbell::RwLockBell;
use parking_lot::Mutex;

#[test]
fn test_callback_fires_on_single_read_guard_drop() {
    let lock = Arc::new(RwLockBell::new(0u64));
    let called = Arc::new(AtomicBool::new(false));
    let called2 = called.clone();

    let r = lock.read();
    assert!(
        lock.try_write_or(move || called2.store(true, Relaxed))
            .is_none()
    );

    assert!(
        !called.load(Relaxed),
        "callback must not fire while read guard is held"
    );
    drop(r);
    assert!(
        called.load(Relaxed),
        "callback must fire when the read guard drops"
    );
}

#[test]
fn test_callback_fires_on_try_read_guard_drop() {
    let lock = Arc::new(RwLockBell::new(0u64));
    let called = Arc::new(AtomicBool::new(false));
    let called2 = called.clone();

    let r = lock.try_read().expect("lock is free");
    assert!(
        lock.try_write_or(move || called2.store(true, Relaxed))
            .is_none()
    );

    assert!(!called.load(Relaxed));
    drop(r);
    assert!(called.load(Relaxed));
}

#[test]
fn test_callback_fires_on_last_read_guard_drop() {
    let lock = Arc::new(RwLockBell::new(0u64));
    let called = Arc::new(AtomicBool::new(false));
    let called2 = called.clone();

    let r1 = lock.read();
    let r2 = lock.read();
    let r3 = lock.read();

    assert!(
        lock.try_write_or(move || called2.store(true, Relaxed))
            .is_none()
    );

    drop(r1);
    assert!(!called.load(Relaxed), "should not fire after first drop");
    drop(r2);
    assert!(!called.load(Relaxed), "should not fire after second drop");
    drop(r3);
    assert!(called.load(Relaxed), "must fire after last drop");
}

#[test]
fn test_multiple_callbacks_fired_by_last_read_guard() {
    let lock = Arc::new(RwLockBell::new(0u64));
    let order = Arc::new(Mutex::new(Vec::<u64>::new()));

    let r = lock.read();

    for i in 0..5u64 {
        let order = order.clone();
        assert!(lock.try_write_or(move || order.lock().push(i)).is_none());
    }

    drop(r);
    assert_eq!(*order.lock(), vec![0, 1, 2, 3, 4]);
}

#[test]
fn test_callback_no_lock_held_read_path() {
    let lock = Arc::new(RwLockBell::new(0u64));
    let lock2 = lock.clone();

    let r = lock.read();
    assert!(
        lock.try_write_or(move || {
            let mut w = lock2.write();
            *w = 99;
        })
        .is_none()
    );

    drop(r);
    assert_eq!(*lock.read(), 99);
}

#[test]
fn test_callback_from_read_guard_drain_can_re_register() {
    let lock = Arc::new(RwLockBell::new(0u64));
    let second_called = Arc::new(AtomicBool::new(false));
    let second_called2 = second_called.clone();
    let lock2 = lock.clone();

    let r = lock.read();
    assert!(
        lock.try_write_or(move || {
            let _guard = lock2
                .try_write_or(|| {})
                .expect("lock is free during callback, try_write_or must succeed");
            second_called2.store(true, Relaxed);
        })
        .is_none()
    );

    drop(r);
    assert!(second_called.load(Relaxed));
}

#[test]
fn test_callback_panic_read_guard_path() {
    let lock = Arc::new(RwLockBell::new(0u64));
    let second_called = Arc::new(AtomicBool::new(false));
    let second_called2 = second_called.clone();

    let r = lock.read();
    assert!(
        lock.try_write_or(|| panic!("intentional panic from read guard callback"))
            .is_none()
    );
    assert!(
        lock.try_write_or(move || second_called2.store(true, Relaxed))
            .is_none()
    );

    let result = panic::catch_unwind(AssertUnwindSafe(|| drop(r)));
    assert!(result.is_err(), "panic from callback must propagate");
    assert!(
        second_called.load(Relaxed),
        "second callback must still run"
    );
}

#[test]
fn test_callback_fires_immediately_not_deferred() {
    let lock = Arc::new(RwLockBell::new(0u64));
    let called = Arc::new(AtomicBool::new(false));
    let called2 = called.clone();

    let r = lock.read();
    assert!(
        lock.try_write_or(move || called2.store(true, Relaxed))
            .is_none()
    );

    drop(r);
    assert!(
        called.load(Relaxed),
        "callback must fire synchronously in the drop of the last read guard"
    );
}