armdb 0.14.3

sharded bitcask key-value storage optimized for NVMe
Documentation
//! Backend-neutral synchronization helpers.
//!
//! A transaction closure can panic after its shard guards have been acquired.
//! The transaction cleanup releases those guards during unwinding, but
//! `std::sync::Mutex` records the escaped panic as poison. The standard adapter
//! recovers that guard on later acquisition so the database can finish its
//! cleanup and continue serving work. `parking_lot` has no poisoning, while
//! loom deliberately remains fail-fast at this boundary.

#[cfg(feature = "loom")]
pub(crate) use loom::sync::{
    Condvar, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard,
};

#[cfg(all(feature = "parking_lot", not(feature = "loom")))]
pub(crate) use parking_lot::{
    Condvar, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard,
};

#[cfg(not(any(feature = "parking_lot", feature = "loom")))]
pub(crate) use std::sync::{Condvar, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};

#[cfg(feature = "loom")]
#[inline(always)]
pub(crate) fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
    mutex.lock().expect("loom mutex poisoned")
}

#[cfg(feature = "loom")]
#[inline(always)]
pub(crate) fn read<T>(lock: &RwLock<T>) -> RwLockReadGuard<'_, T> {
    lock.read().expect("loom rwlock poisoned")
}

#[cfg(all(feature = "parking_lot", not(feature = "loom")))]
#[allow(dead_code)]
#[inline(always)]
pub(crate) fn read<T>(lock: &RwLock<T>) -> RwLockReadGuard<'_, T> {
    lock.read()
}

#[cfg(not(any(feature = "parking_lot", feature = "loom")))]
#[inline(always)]
pub(crate) fn read<T>(lock: &RwLock<T>) -> RwLockReadGuard<'_, T> {
    lock.read().unwrap_or_else(|poisoned| poisoned.into_inner())
}

#[cfg(feature = "loom")]
#[inline(always)]
pub(crate) fn write<T>(lock: &RwLock<T>) -> RwLockWriteGuard<'_, T> {
    lock.write().expect("loom rwlock poisoned")
}

#[cfg(all(feature = "parking_lot", not(feature = "loom")))]
#[allow(dead_code)]
#[inline(always)]
pub(crate) fn write<T>(lock: &RwLock<T>) -> RwLockWriteGuard<'_, T> {
    lock.write()
}

#[cfg(not(any(feature = "parking_lot", feature = "loom")))]
#[inline(always)]
pub(crate) fn write<T>(lock: &RwLock<T>) -> RwLockWriteGuard<'_, T> {
    lock.write()
        .unwrap_or_else(|poisoned| poisoned.into_inner())
}

#[cfg(all(feature = "parking_lot", not(feature = "loom")))]
#[inline(always)]
pub(crate) fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
    mutex.lock()
}

#[cfg(not(any(feature = "parking_lot", feature = "loom")))]
#[inline(always)]
pub(crate) fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
    mutex.lock().unwrap_or_else(|e| e.into_inner())
}

/// Try to take `mutex` without blocking.
///
/// A caught transaction panic can leave a std mutex poisoned after its shard
/// guard is released. The standard adapter returns that guard so test-only
/// liveness checks can continue; `parking_lot` has no poison and loom remains
/// fail-fast.
///
/// Only tests probe a lock without blocking today, hence `#[cfg(test)]`.
#[cfg(all(test, feature = "parking_lot", not(feature = "loom")))]
#[inline(always)]
pub(crate) fn try_lock<T>(mutex: &Mutex<T>) -> Option<MutexGuard<'_, T>> {
    mutex.try_lock()
}

#[cfg(all(test, feature = "loom"))]
#[inline(always)]
pub(crate) fn try_lock<T>(mutex: &Mutex<T>) -> Option<MutexGuard<'_, T>> {
    match mutex.try_lock() {
        Ok(guard) => Some(guard),
        Err(std::sync::TryLockError::WouldBlock) => None,
        Err(std::sync::TryLockError::Poisoned(error)) => {
            panic!("loom mutex poisoned: {error}")
        }
    }
}

#[cfg(all(test, not(any(feature = "parking_lot", feature = "loom"))))]
#[inline(always)]
pub(crate) fn try_lock<T>(mutex: &Mutex<T>) -> Option<MutexGuard<'_, T>> {
    match mutex.try_lock() {
        Ok(guard) => Some(guard),
        Err(std::sync::TryLockError::Poisoned(error)) => Some(error.into_inner()),
        Err(std::sync::TryLockError::WouldBlock) => None,
    }
}

#[cfg(all(feature = "parking_lot", not(feature = "loom")))]
#[inline(always)]
pub(crate) fn cv_wait<'a, T>(cv: &Condvar, mut guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
    cv.wait(&mut guard);
    guard
}

#[cfg(feature = "loom")]
#[inline(always)]
pub(crate) fn cv_wait<'a, T>(cv: &Condvar, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
    cv.wait(guard).expect("loom mutex poisoned")
}

#[cfg(not(any(feature = "parking_lot", feature = "loom")))]
#[inline(always)]
pub(crate) fn cv_wait<'a, T>(cv: &Condvar, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
    cv.wait(guard).unwrap_or_else(|e| e.into_inner())
}

/// Wait on `cv` for at most `timeout`, returning the reacquired guard.
///
/// `mutex` must be the one `guard` came from; it is only used under `loom`,
/// whose `Condvar` has no timed wait — there the guard is dropped, the thread
/// yields, and the lock is retaken. That is a spurious wakeup, which every
/// caller of a condition variable must already tolerate.
#[cfg(all(feature = "parking_lot", not(feature = "loom")))]
#[inline(always)]
pub(crate) fn cv_wait_timeout<'a, T>(
    cv: &Condvar,
    _mutex: &'a Mutex<T>,
    mut guard: MutexGuard<'a, T>,
    timeout: std::time::Duration,
) -> MutexGuard<'a, T> {
    cv.wait_for(&mut guard, timeout);
    guard
}

#[cfg(feature = "loom")]
#[inline(always)]
pub(crate) fn cv_wait_timeout<'a, T>(
    _cv: &Condvar,
    mutex: &'a Mutex<T>,
    guard: MutexGuard<'a, T>,
    _timeout: std::time::Duration,
) -> MutexGuard<'a, T> {
    drop(guard);
    loom::thread::yield_now();
    lock(mutex)
}

#[cfg(not(any(feature = "parking_lot", feature = "loom")))]
#[inline(always)]
pub(crate) fn cv_wait_timeout<'a, T>(
    cv: &Condvar,
    _mutex: &'a Mutex<T>,
    guard: MutexGuard<'a, T>,
    timeout: std::time::Duration,
) -> MutexGuard<'a, T> {
    cv.wait_timeout(guard, timeout)
        .unwrap_or_else(|e| e.into_inner())
        .0
}