#![no_std]
#![deny(missing_docs)]
#![doc = include_str!("../README.md")]
use crate::poison::PoisonLockResult;
use cfg_if::cfg_if;
use core::ops::{Deref, DerefMut};
enum InitState<T, F> {
Uninit(F),
Init(T),
Poisoned,
}
mod poison;
cfg_if! {
if #[cfg(feature = "std")] {
extern crate std;
mod std_lock;
pub use std_lock::RawStdMutex;
}
}
macro_rules! declare_lazy_mut {
($default_mutex: path) => {
pub struct LazyMut<T, F = fn() -> T, M = $default_mutex> {
state: lock_api::Mutex<M, InitState<T, F>>,
poison_flag: poison::Flag,
}
};
}
cfg_if::cfg_if! {
if #[cfg(feature = "parking_lot")] {
declare_lazy_mut!(parking_lot::RawMutex);
} else if #[cfg(feature = "std")] {
declare_lazy_mut!(RawStdMutex);
} else if #[cfg(feature = "spin")] {
declare_lazy_mut!(spin::Mutex<()>);
} else {
#[doc(hidden)]
pub enum NoDefaultMutex {}
declare_lazy_mut!(NoDefaultMutex);
}
}
#[cold]
fn lazy_mut_poisoned_init() -> ! {
panic!("LazyMut instance has been poisoned during initialization")
}
#[clippy::has_significant_drop]
#[must_use = "if unused the LazyMut will immediately unlock"]
pub struct LazyMutGuard<'a, T, F, M: lock_api::RawMutex> {
lazy: &'a LazyMut<T, F, M>,
poison_guard: poison::Guard,
marker: core::marker::PhantomData<(&'a mut T, M::GuardMarker)>,
}
impl<T, F, M: lock_api::RawMutex> Deref for LazyMutGuard<'_, T, F, M> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe {
let InitState::Init(ref data) = *self.lazy.state.data_ptr()
else {
core::hint::unreachable_unchecked()
};
data
}
}
}
impl<T, F, M: lock_api::RawMutex> DerefMut for LazyMutGuard<'_, T, F, M> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe {
let InitState::Init(ref mut data) = *self.lazy.state.data_ptr()
else {
core::hint::unreachable_unchecked()
};
data
}
}
}
impl<T, F, M: lock_api::RawMutex> Drop for LazyMutGuard<'_, T, F, M> {
fn drop(&mut self) {
self.lazy.poison_flag.done(self.poison_guard)
}
}
impl<T, F, M: lock_api::RawMutex> core::panic::UnwindSafe for LazyMut<T, F, M> {}
impl<T, F: FnOnce() -> T, M: lock_api::RawMutex> LazyMut<T, F, M> {
fn force_mut(&self) -> PoisonLockResult<LazyMutGuard<'_, T, F, M>> {
let mut lock = self.state.lock();
let state = &mut *lock;
match state {
InitState::Init(_) => {}
InitState::Uninit(_) => unsafe { Self::really_init(state) },
InitState::Poisoned => lazy_mut_poisoned_init(),
}
debug_assert!(matches!(state, InitState::Init(_)));
poison::map_result(self.poison_flag.guard(), |poison_guard| LazyMutGuard {
lazy: self,
marker: core::marker::PhantomData,
poison_guard,
})
}
#[cold]
unsafe fn really_init(state: &mut InitState<T, F>) {
let InitState::Uninit(f) = core::mem::replace(state, InitState::Poisoned)
else {
unsafe { core::hint::unreachable_unchecked() }
};
let data = f();
unsafe { core::ptr::write(state, InitState::Init(data)) }
}
}
impl<T, F: FnOnce() -> T, M: lock_api::RawMutex> LazyMut<T, F, M> {
#[inline]
pub const fn new(f: F) -> Self {
LazyMut {
state: lock_api::Mutex::new(InitState::Uninit(f)),
poison_flag: poison::Flag::new(),
}
}
pub fn into_inner(self) -> Result<T, F> {
match self.state.into_inner() {
InitState::Init(data) => Ok(data),
InitState::Uninit(f) => Err(f),
InitState::Poisoned => lazy_mut_poisoned_init(),
}
}
pub fn get_mut(&self) -> LazyMutGuard<'_, T, F, M> {
self.force_mut().unwrap()
}
#[cfg(feature = "std")]
pub fn try_get_mut(&self) -> std::sync::LockResult<LazyMutGuard<'_, T, F, M>> {
self.force_mut()
}
pub fn is_poisoned(&self) -> bool {
matches!(&*self.state.lock(), InitState::Poisoned) || self.poison_flag.get()
}
pub fn clear_mutex_poison(&self) {
self.poison_flag.clear()
}
}
#[cfg(test)]
mod tests {
extern crate std;
use crate::LazyMut;
macro_rules! gen_test {
($name:ident $mutex_ty:ty) => {
#[test]
fn $name() {
let x = LazyMut::<u64, _, $mutex_ty>::new(|| 0_u64);
std::thread::scope(|s| {
for _ in 0..32 {
s.spawn(|| {
for i in 1..=10 {
let lock = &mut *x.get_mut();
*lock += 100;
assert!(*lock >= 100 * i);
}
});
}
});
assert_eq!(*x.get_mut(), 32 * 10 * 100);
}
};
}
#[cfg(feature = "std")]
gen_test!(std_test crate::RawStdMutex);
#[cfg(feature = "parking_lot")]
gen_test!(parking_lot_test parking_lot::RawMutex);
#[cfg(feature = "spin")]
gen_test!(spin_test spin::Mutex<()>);
}