Add support for LazyLock

This commit is contained in:
2025-01-17 22:31:40 +01:00
parent 40f835afd2
commit 25ae542ade
6 changed files with 152 additions and 13 deletions

View File

@@ -36,3 +36,6 @@ backtraces = []
# Feature names do not match crate names pending namespaced features. # Feature names do not match crate names pending namespaced features.
lockapi = ["lock_api"] lockapi = ["lock_api"]
parkinglot = ["parking_lot", "lockapi"] parkinglot = ["parking_lot", "lockapi"]
[build-dependencies]
autocfg = "1.4.0"

10
build.rs Normal file
View File

@@ -0,0 +1,10 @@
use autocfg::AutoCfg;
fn main() {
// To avoid bumping MSRV unnecessarily, we can sniff certain features. Reevaluate this on major
// releases.
let ac = AutoCfg::new().unwrap();
ac.emit_has_path("std::sync::LazyLock");
autocfg::rerun_path("build.rs");
}

View File

@@ -193,6 +193,14 @@ impl MutexId {
unreachable!("Tried to drop lock for mutex {:?} but it wasn't held", self) unreachable!("Tried to drop lock for mutex {:?} but it wasn't held", self)
}); });
} }
/// Execute the given closure while the guard is held.
pub fn with_held<T>(&self, f: impl FnOnce() -> T) -> T {
// Note: we MUST construct the RAII guard, we cannot simply mark held + mark released, as
// f() may panic and corrupt our state.
let _guard = self.get_borrowed();
f()
}
} }
impl Default for MutexId { impl Default for MutexId {

View File

@@ -138,16 +138,14 @@ pub mod tracing {
/// This method will panic if `f` panics, poisoning this `Once`. In addition, this function /// This method will panic if `f` panics, poisoning this `Once`. In addition, this function
/// panics when the lock acquisition order is determined to be inconsistent. /// panics when the lock acquisition order is determined to be inconsistent.
pub fn call_once(&self, f: impl FnOnce()) { pub fn call_once(&self, f: impl FnOnce()) {
let _borrow = self.id.get_borrowed(); self.id.with_held(|| self.inner.call_once(f));
self.inner.call_once(f);
} }
/// Performs the given initialization routine once and only once. /// Performs the given initialization routine once and only once.
/// ///
/// This method is identical to [`Once::call_once`] except it ignores poisoning. /// This method is identical to [`Once::call_once`] except it ignores poisoning.
pub fn call_once_force(&self, f: impl FnOnce(OnceState)) { pub fn call_once_force(&self, f: impl FnOnce(OnceState)) {
let _borrow = self.id.get_borrowed(); self.id.with_held(|| self.inner.call_once_force(f));
self.inner.call_once_force(f);
} }
} }
} }

View File

@@ -30,6 +30,12 @@ pub use tracing::{
Condvar, Mutex, MutexGuard, Once, OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard, Condvar, Mutex, MutexGuard, Once, OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard,
}; };
#[cfg(all(has_std__sync__LazyLock, debug_assertions))]
pub use tracing::LazyLock;
#[cfg(all(has_std__sync__LazyLock, not(debug_assertions)))]
pub use std::sync::LazyLock;
/// Dependency tracing versions of [`std::sync`]. /// Dependency tracing versions of [`std::sync`].
pub mod tracing { pub mod tracing {
use std::fmt; use std::fmt;
@@ -47,6 +53,12 @@ pub mod tracing {
use crate::BorrowedMutex; use crate::BorrowedMutex;
use crate::LazyMutexId; use crate::LazyMutexId;
#[cfg(has_std__sync__LazyLock)]
pub use lazy_lock::LazyLock;
#[cfg(has_std__sync__LazyLock)]
mod lazy_lock;
/// Wrapper for [`std::sync::Mutex`]. /// Wrapper for [`std::sync::Mutex`].
/// ///
/// Refer to the [crate-level][`crate`] documentation for the differences between this struct and /// Refer to the [crate-level][`crate`] documentation for the differences between this struct and
@@ -460,8 +472,7 @@ pub mod tracing {
where where
F: FnOnce(), F: FnOnce(),
{ {
let _guard = self.mutex_id.get_borrowed(); self.mutex_id.with_held(|| self.inner.call_once(f))
self.inner.call_once(f);
} }
/// Performs the same operation as [`call_once`][Once::call_once] except it ignores /// Performs the same operation as [`call_once`][Once::call_once] except it ignores
@@ -475,8 +486,7 @@ pub mod tracing {
where where
F: FnOnce(&OnceState), F: FnOnce(&OnceState),
{ {
let _guard = self.mutex_id.get_borrowed(); self.mutex_id.with_held(|| self.inner.call_once_force(f))
self.inner.call_once_force(f);
} }
/// Returns true if some `call_once` has completed successfully. /// Returns true if some `call_once` has completed successfully.
@@ -550,9 +560,7 @@ pub mod tracing {
/// As this method may block until initialization is complete, it participates in cycle /// As this method may block until initialization is complete, it participates in cycle
/// detection. /// detection.
pub fn set(&self, value: T) -> Result<(), T> { pub fn set(&self, value: T) -> Result<(), T> {
let _guard = self.id.get_borrowed(); self.id.with_held(|| self.inner.set(value))
self.inner.set(value)
} }
/// Gets the contents of the cell, initializing it with `f` if the cell was empty. /// Gets the contents of the cell, initializing it with `f` if the cell was empty.
@@ -562,8 +570,7 @@ pub mod tracing {
where where
F: FnOnce() -> T, F: FnOnce() -> T,
{ {
let _guard = self.id.get_borrowed(); self.id.with_held(|| self.inner.get_or_init(f))
self.inner.get_or_init(f)
} }
/// Takes the value out of this `OnceLock`, moving it back to an uninitialized state. /// Takes the value out of this `OnceLock`, moving it back to an uninitialized state.

View File

@@ -0,0 +1,113 @@
//! Wrapper implementation for LazyLock
//!
//! This lives in a separate module as LazyLock would otherwise raise our MSRV to 1.80. Reevaluate
//! this in the future.
use std::fmt;
use std::fmt::Debug;
use std::ops::Deref;
use crate::LazyMutexId;
/// Wrapper for [`std::sync::LazyLock`]
///
/// This wrapper participates in cycle detection like all other primitives in this crate. It should
/// only be possible to encounter cycles when acquiring mutexes in the initialisation function.
///
/// # Examples
///
/// ```
/// use tracing_mutex::stdsync::tracing::LazyLock;
///
/// static LOCK: LazyLock<i32> = LazyLock::new(|| {
/// println!("Hello, world!");
/// 42
/// });
///
/// // This should print "Hello, world!"
/// println!("{}", *LOCK);
/// // This should not.
/// println!("{}", *LOCK);
/// ```
pub struct LazyLock<T, F = fn() -> T> {
inner: std::sync::LazyLock<T, F>,
id: LazyMutexId,
}
impl<T, F: FnOnce() -> T> LazyLock<T, F> {
/// Creates a new lazy value with the given initializing function.
pub const fn new(f: F) -> LazyLock<T, F> {
Self {
id: LazyMutexId::new(),
inner: std::sync::LazyLock::new(f),
}
}
/// Force this lazy lock to be evaluated.
///
/// This is equivalent to dereferencing, but is more explicit.
pub fn force(this: &LazyLock<T, F>) -> &T {
&*this
}
}
impl<T, F: FnOnce() -> T> Deref for LazyLock<T, F> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.id.with_held(|| &*self.inner)
}
}
impl<T: Default> Default for LazyLock<T> {
/// Return a `LazyLock` that is initialized through [`Default`].
fn default() -> Self {
Self::new(Default::default)
}
}
impl<T: Debug, F> Debug for LazyLock<T, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Cannot implement this ourselves because the get() used is nightly, so delegate.
self.inner.fmt(f)
}
}
#[cfg(test)]
mod tests {
use crate::stdsync::Mutex;
use super::*;
#[test]
fn test_only_init_once() {
let mut init_counter = 0;
let lock = LazyLock::new(|| {
init_counter += 1;
42
});
assert_eq!(*lock, 42);
LazyLock::force(&lock);
// Ensure we can access the init counter
drop(lock);
assert_eq!(init_counter, 1);
}
#[test]
#[should_panic(expected = "Found cycle")]
fn test_panic_with_cycle() {
let mutex = Mutex::new(());
let lock = LazyLock::new(|| *mutex.lock().unwrap());
// Establish the relation from lock to mutex
LazyLock::force(&lock);
// Now do it the other way around, which should crash
let _guard = mutex.lock().unwrap();
LazyLock::force(&lock);
}
}