mirror of
https://github.com/bertptrs/tracing-mutex.git
synced 2025-12-27 13:30:32 +01:00
Implement a tracing wrapper for std::sync::Once.
This commit is contained in:
48
src/lib.rs
48
src/lib.rs
@@ -46,11 +46,14 @@
|
|||||||
//!
|
//!
|
||||||
//! [paper]: https://whileydave.com/publications/pk07_jea/
|
//! [paper]: https://whileydave.com/publications/pk07_jea/
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::cell::UnsafeCell;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::ops::Deref;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
use std::sync::Once;
|
||||||
use std::sync::PoisonError;
|
use std::sync::PoisonError;
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
@@ -155,6 +158,51 @@ impl Drop for MutexId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `const`-compatible version of [`crate::MutexId`].
|
||||||
|
///
|
||||||
|
/// This struct can be used similarly to the normal mutex ID, but to be const-compatible its ID is
|
||||||
|
/// generated on first use. This allows it to be used as the mutex ID for mutexes with a `const`
|
||||||
|
/// constructor.
|
||||||
|
struct LazyMutexId {
|
||||||
|
inner: UnsafeCell<Option<MutexId>>,
|
||||||
|
setter: Once,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LazyMutexId {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: UnsafeCell::new(None),
|
||||||
|
setter: Once::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for LazyMutexId {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{:?}", self.deref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Sync for LazyMutexId {}
|
||||||
|
|
||||||
|
impl Deref for LazyMutexId {
|
||||||
|
type Target = MutexId;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.setter.call_once(|| {
|
||||||
|
// Safety: this function is only called once, so only one mutable reference should exist
|
||||||
|
// at a time.
|
||||||
|
unsafe {
|
||||||
|
*self.inner.get() = Some(MutexId::new());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Safety: after the above Once runs, there are no longer any mutable references, so we can
|
||||||
|
// hand this out safely.
|
||||||
|
unsafe { (*self.inner.get()).as_ref().unwrap() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct BorrowedMutex<'a>(&'a MutexId);
|
struct BorrowedMutex<'a>(&'a MutexId);
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ use std::ops::DerefMut;
|
|||||||
use std::sync::LockResult;
|
use std::sync::LockResult;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::sync::MutexGuard;
|
use std::sync::MutexGuard;
|
||||||
|
use std::sync::Once;
|
||||||
|
use std::sync::OnceState;
|
||||||
use std::sync::PoisonError;
|
use std::sync::PoisonError;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use std::sync::RwLockReadGuard;
|
use std::sync::RwLockReadGuard;
|
||||||
@@ -27,6 +29,7 @@ use std::sync::TryLockError;
|
|||||||
use std::sync::TryLockResult;
|
use std::sync::TryLockResult;
|
||||||
|
|
||||||
use crate::BorrowedMutex;
|
use crate::BorrowedMutex;
|
||||||
|
use crate::LazyMutexId;
|
||||||
use crate::MutexId;
|
use crate::MutexId;
|
||||||
|
|
||||||
/// Debug-only tracing `Mutex`.
|
/// Debug-only tracing `Mutex`.
|
||||||
@@ -41,7 +44,7 @@ pub type DebugMutex<T> = Mutex<T>;
|
|||||||
|
|
||||||
/// Debug-only tracing `RwLock`.
|
/// Debug-only tracing `RwLock`.
|
||||||
///
|
///
|
||||||
/// Type alias that resolves to [`RwLock`] when debug assertions are enabled and to
|
/// Type alias that resolves to [`TracingRwLock`] when debug assertions are enabled and to
|
||||||
/// [`std::sync::RwLock`] when they're not. Use this if you want to have the benefits of cycle
|
/// [`std::sync::RwLock`] when they're not. Use this if you want to have the benefits of cycle
|
||||||
/// detection in development but do not want to pay the performance penalty in release.
|
/// detection in development but do not want to pay the performance penalty in release.
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
@@ -49,6 +52,16 @@ pub type DebugRwLock<T> = TracingRwLock<T>;
|
|||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
pub type DebugRwLock<T> = RwLock<T>;
|
pub type DebugRwLock<T> = RwLock<T>;
|
||||||
|
|
||||||
|
/// Debug-only tracing `Once`.
|
||||||
|
///
|
||||||
|
/// Type alias that resolves to [`TracingOnce`] when debug assertions are enabled and to
|
||||||
|
/// [`std::sync::Once`] when they're not. Use this if you want to have the benefits of cycle
|
||||||
|
/// detection in development but do not want to pay the performance penalty in release.
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
pub type DebugOnce = TracingOnce;
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
pub type DebugOnce = Once;
|
||||||
|
|
||||||
/// Wrapper for [`std::sync::Mutex`].
|
/// Wrapper for [`std::sync::Mutex`].
|
||||||
///
|
///
|
||||||
/// Refer to the [crate-level][`crate`] documentaiton for the differences between this struct and
|
/// Refer to the [crate-level][`crate`] documentaiton for the differences between this struct and
|
||||||
@@ -307,6 +320,60 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wrapper around [`std::sync::Once`].
|
||||||
|
///
|
||||||
|
/// Refer to the [crate-level][`crate`] documentaiton for the differences between this struct and
|
||||||
|
/// the one it wraps.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TracingOnce {
|
||||||
|
inner: Once,
|
||||||
|
mutex_id: LazyMutexId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TracingOnce {
|
||||||
|
/// Create a new `Once` value.
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Once::new(),
|
||||||
|
mutex_id: LazyMutexId::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for [`std::sync::Once::call_once`].
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// In addition to the panics that `Once` can cause, this method will panic if calling it
|
||||||
|
/// introduces a cycle in the lock dependency graph.
|
||||||
|
pub fn call_once<F>(&self, f: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(),
|
||||||
|
{
|
||||||
|
let _guard = self.mutex_id.get_borrowed();
|
||||||
|
self.inner.call_once(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs the same operation as [`call_once`][TracingOnce::call_once] except it ignores
|
||||||
|
/// poisoning.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This method participates in lock dependency tracking. If acquiring this lock introduces a
|
||||||
|
/// dependency cycle, this method will panic.
|
||||||
|
pub fn call_once_force<F>(&self, f: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(&OnceState),
|
||||||
|
{
|
||||||
|
let _guard = self.mutex_id.get_borrowed();
|
||||||
|
self.inner.call_once_force(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if some `call_once` has completed successfully.
|
||||||
|
pub fn is_completed(&self) -> bool {
|
||||||
|
self.inner.is_completed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -356,6 +423,28 @@ mod tests {
|
|||||||
handle.join().unwrap();
|
handle.join().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_once_usage() {
|
||||||
|
let _graph_lock = GRAPH_MUTEX.lock();
|
||||||
|
|
||||||
|
let once = Arc::new(TracingOnce::new());
|
||||||
|
let once_clone = once.clone();
|
||||||
|
|
||||||
|
assert!(!once.is_completed());
|
||||||
|
|
||||||
|
let handle = thread::spawn(move || {
|
||||||
|
assert!(!once_clone.is_completed());
|
||||||
|
|
||||||
|
once_clone.call_once(|| {});
|
||||||
|
|
||||||
|
assert!(once_clone.is_completed());
|
||||||
|
});
|
||||||
|
|
||||||
|
handle.join().unwrap();
|
||||||
|
|
||||||
|
assert!(once.is_completed());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "Mutex order graph should not have cycles")]
|
#[should_panic(expected = "Mutex order graph should not have cycles")]
|
||||||
fn test_detect_cycle() {
|
fn test_detect_cycle() {
|
||||||
|
|||||||
Reference in New Issue
Block a user