mirror of
https://github.com/bertptrs/tracing-mutex.git
synced 2025-12-25 12:40:31 +01:00
@@ -6,6 +6,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- On Rust 1.80 or newer, a wrapper for `std::sync::LazyLock` is now available. The MSRV has not been
|
||||||
|
changed; older versions simply don't get this wrapper.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Reworked CI to better test continued support for the minimum supported Rust version
|
- Reworked CI to better test continued support for the minimum supported Rust version
|
||||||
|
|||||||
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -25,9 +25,9 @@ checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
@@ -619,6 +619,7 @@ dependencies = [
|
|||||||
name = "tracing-mutex"
|
name = "tracing-mutex"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
"criterion",
|
"criterion",
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ Add this dependency to your `Cargo.lock` file like any other:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tracing-mutex = "0.2"
|
tracing-mutex = "0.3"
|
||||||
```
|
```
|
||||||
|
|
||||||
Then use the locks provided by this library instead of the ones you would use otherwise.
|
Then use the locks provided by this library instead of the ones you would use otherwise.
|
||||||
|
|||||||
10
build.rs
Normal file
10
build.rs
Normal 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");
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
117
src/stdsync/tracing/lazy_lock.rs
Normal file
117
src/stdsync/tracing/lazy_lock.rs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
//! 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> {
|
||||||
|
// MSRV violation is fine, this is gated behind a cfg! check
|
||||||
|
#[allow(clippy::incompatible_msrv)]
|
||||||
|
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(),
|
||||||
|
// MSRV violation is fine, this is gated behind a cfg! check
|
||||||
|
#[allow(clippy::incompatible_msrv)]
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user