shared-lock 0.1.0

A lock that protects multiple objects at once.
Documentation
use {
    crate::{Guard, Lock, execution_unit::execution_unit_id},
    parking_lot::lock_api::RawMutex,
    std::{
        mem,
        sync::{Barrier, atomic::Ordering::Relaxed},
        thread,
        time::{Duration, Instant},
    },
};

fn assert_default(lock: &Lock) {
    assert_eq!(lock.shared.execution_unit_id.load(Relaxed), 0);
    assert_eq!(lock.shared.tickets.get(), 0);
    assert_eq!(lock.shared.raw_mutex.is_locked(), false);
}

fn run_in_thread<T: Send>(f: impl FnOnce() -> T + Send) -> T {
    thread::scope(|s| s.spawn(|| f()).join().unwrap())
}

#[test]
fn default() {
    let lock = Lock::default();
    assert_default(&lock);
}

#[test]
fn force_unlock() {
    let lock = Lock::default();
    let guard = lock.lock();
    mem::forget(guard);
    assert_eq!(lock.is_locked(), true);
    unsafe {
        lock.force_unlock();
    }
    assert_eq!(lock.is_locked(), false);
    assert_default(&lock);
}

#[test]
fn force_unlock_fair() {
    let lock = Lock::default();
    let guard = lock.lock();
    mem::forget(guard);
    assert_eq!(lock.is_locked(), true);
    unsafe {
        lock.force_unlock_fair();
    }
    assert_eq!(lock.is_locked(), false);
    assert_default(&lock);
}

#[test]
fn is_locked() {
    let lock = Lock::default();
    let guard = lock.lock();
    assert_eq!(lock.is_locked(), true);
    drop(guard);
    assert_eq!(lock.is_locked(), false);
}

#[test]
fn is_locked_by() {
    let lock1 = Lock::default();
    let lock2 = Lock::default();
    let guard1 = &lock1.lock();
    assert_eq!(lock1.is_locked_by(guard1), true);
    assert_eq!(lock2.is_locked_by(guard1), false);
    let guard2 = &lock2.lock();
    assert_eq!(lock1.is_locked_by(guard2), false);
    assert_eq!(lock2.is_locked_by(guard2), true);
    let guard3 = &lock1.lock();
    assert_eq!(lock1.is_locked_by(guard3), true);
    assert_eq!(lock2.is_locked_by(guard3), false);
    assert_eq!(lock1.is_locked_by(guard1), true);
    assert_eq!(lock2.is_locked_by(guard1), false);
}

#[test]
fn is_locked_by_current_thread() {
    let lock = Lock::default();
    assert_eq!(lock.is_locked_by_current_thread(), false);
    run_in_thread(|| {
        assert_eq!(lock.is_locked_by_current_thread(), false);
    });
    {
        let _guard = lock.lock();
        assert_eq!(lock.is_locked_by_current_thread(), true);
        run_in_thread(|| {
            assert_eq!(lock.is_locked_by_current_thread(), false);
        });
    }
    assert_eq!(lock.is_locked_by_current_thread(), false);
    run_in_thread(|| {
        assert_eq!(lock.is_locked_by_current_thread(), false);
    });
    let barrier1 = Barrier::new(2);
    let barrier2 = Barrier::new(2);
    thread::scope(|s| {
        let handle = s.spawn(|| {
            let _guard = lock.lock();
            assert_eq!(lock.is_locked_by_current_thread(), true);
            barrier1.wait();
            barrier2.wait();
        });
        barrier1.wait();
        assert_eq!(lock.is_locked(), true);
        assert_eq!(lock.is_locked_by_current_thread(), false);
        barrier2.wait();
        handle.join().unwrap();
    });
}

#[test]
fn lock() {
    let lock = Lock::default();
    run_in_thread(|| {
        assert!(lock.try_lock().is_some());
    });
    let guard1 = lock.lock();
    assert_eq!(
        lock.shared.execution_unit_id.load(Relaxed),
        execution_unit_id()
    );
    assert_eq!(lock.shared.tickets.get(), 1);
    run_in_thread(|| {
        assert!(lock.try_lock().is_none());
    });
    let guard2 = lock.lock();
    assert_eq!(
        lock.shared.execution_unit_id.load(Relaxed),
        execution_unit_id()
    );
    assert_eq!(lock.shared.tickets.get(), 2);
    run_in_thread(|| {
        assert!(lock.try_lock().is_none());
    });
    drop(guard2);
    assert_eq!(
        lock.shared.execution_unit_id.load(Relaxed),
        execution_unit_id()
    );
    assert_eq!(lock.shared.tickets.get(), 1);
    run_in_thread(|| {
        assert!(lock.try_lock().is_none());
    });
    drop(guard1);
    assert_eq!(lock.shared.execution_unit_id.load(Relaxed), 0);
    assert_eq!(lock.shared.tickets.get(), 0);
    run_in_thread(|| {
        assert!(lock.try_lock().is_some());
    });
}

#[test]
fn make_guard_unchecked() {
    let lock = Lock::default();
    let guard = lock.lock();
    mem::forget(guard);
    assert_eq!(
        lock.shared.execution_unit_id.load(Relaxed),
        execution_unit_id(),
    );
    assert_eq!(lock.shared.tickets.get(), 1);
    let guard = unsafe { lock.make_guard_unchecked() };
    assert_eq!(
        lock.shared.execution_unit_id.load(Relaxed),
        execution_unit_id(),
    );
    assert_eq!(lock.shared.tickets.get(), 1);
    drop(guard);
    assert_default(&lock);
}

#[test]
fn try_lock() {
    let lock = Lock::default();
    assert!(lock.try_lock().is_some());
    run_in_thread(|| {
        assert!(lock.try_lock().is_some());
    });
    let guard = lock.lock();
    assert!(lock.try_lock().is_some());
    run_in_thread(|| {
        assert!(lock.try_lock().is_none());
    });
    drop(guard);
    assert!(lock.try_lock().is_some());
    run_in_thread(|| {
        assert!(lock.try_lock().is_some());
    });
}

#[test]
fn try_lock_for() {
    let lock = Lock::default();
    let duration = Duration::from_millis(100);
    assert!(lock.try_lock_for(duration).is_some());
    run_in_thread(|| {
        assert!(lock.try_lock_for(duration).is_some());
    });
    let guard = lock.lock();
    assert!(lock.try_lock_for(duration).is_some());
    run_in_thread(|| {
        let start = Instant::now();
        assert!(lock.try_lock_for(duration).is_none());
        let elapsed = start.elapsed();
        assert!(elapsed >= duration);
    });
    drop(guard);
    assert!(lock.try_lock_for(duration).is_some());
    run_in_thread(|| {
        assert!(lock.try_lock_for(duration).is_some());
    });
}

#[test]
fn try_lock_until() {
    let lock = Lock::default();
    let duration = Duration::from_millis(100);
    assert!(lock.try_lock_until(Instant::now() + duration).is_some());
    run_in_thread(|| {
        assert!(lock.try_lock_until(Instant::now() + duration).is_some());
    });
    let guard = lock.lock();
    assert!(lock.try_lock_until(Instant::now() + duration).is_some());
    run_in_thread(|| {
        let start = Instant::now();
        assert!(lock.try_lock_until(start + duration).is_none());
        let elapsed = start.elapsed();
        assert!(elapsed >= duration);
    });
    drop(guard);
    assert!(lock.try_lock_until(Instant::now() + duration).is_some());
    run_in_thread(|| {
        assert!(lock.try_lock_until(Instant::now() + duration).is_some());
    });
}

fn unlock_test(mut unlocker: impl FnMut(Guard<'_>)) {
    let lock = Lock::default();
    run_in_thread(|| {
        assert!(lock.try_lock().is_some());
    });
    let guard = lock.lock();
    run_in_thread(|| {
        assert!(lock.try_lock().is_none());
    });
    unlocker(guard);
    assert_default(&lock);
    run_in_thread(|| {
        assert!(lock.try_lock().is_some());
    });
    let guard1 = lock.lock();
    run_in_thread(|| {
        assert!(lock.try_lock().is_none());
    });
    let guard2 = lock.lock();
    run_in_thread(|| {
        assert!(lock.try_lock().is_none());
    });
    unlocker(guard1);
    run_in_thread(|| {
        assert!(lock.try_lock().is_none());
    });
    unlocker(guard2);
    run_in_thread(|| {
        assert!(lock.try_lock().is_some());
    });
}

#[test]
fn drop_guard() {
    unlock_test(|guard| drop(guard));
}

#[test]
fn unlock_fair() {
    unlock_test(|guard| guard.unlock_fair());
}

trait Unlocked {
    fn unlocked<T>(guard: &mut Guard<'_>, f: impl FnOnce() -> T) -> T;
}

struct UnfairUnlocked;

impl Unlocked for UnfairUnlocked {
    fn unlocked<T>(guard: &mut Guard<'_>, f: impl FnOnce() -> T) -> T {
        guard.unlocked(f)
    }
}

struct FairUnlocked;

impl Unlocked for FairUnlocked {
    fn unlocked<T>(guard: &mut Guard<'_>, f: impl FnOnce() -> T) -> T {
        guard.unlocked_fair(f)
    }
}

fn unlocked_test<T: Unlocked>() {
    let lock = Lock::default();
    let assert_good = || {
        run_in_thread(|| {
            assert!(lock.try_lock().is_some());
        });
    };
    let assert_bad = || {
        run_in_thread(|| {
            assert!(lock.try_lock().is_none());
        });
    };
    assert_good();
    let mut guard = lock.lock();
    assert_bad();
    T::unlocked(&mut guard, || {
        assert_good();
    });
    assert_bad();
    drop(guard);
    assert_good();
    let mut guard1 = lock.lock();
    let mut guard2 = lock.lock();
    assert_bad();
    T::unlocked(&mut guard1, || {
        assert_bad();
    });
    T::unlocked(&mut guard1, || {
        assert_bad();
        T::unlocked(&mut guard2, || {
            assert_good();
        });
        assert_bad();
    });
    drop(guard1);
    drop(guard2);
    assert_good();
}

#[test]
fn unlocked() {
    unlocked_test::<UnfairUnlocked>();
}

#[test]
fn unlocked_fair() {
    unlocked_test::<FairUnlocked>();
}

#[test]
fn eq() {
    let lock1 = Lock::default();
    let lock2 = Lock::default();
    assert_eq!(lock1, lock1);
    assert_ne!(lock1, lock2);
    assert_ne!(lock1, lock2);
    assert_eq!(lock2, lock2);
}