mirror of
https://github.com/bertptrs/tracing-mutex.git
synced 2025-12-25 20:50: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/
|
||||
use std::cell::RefCell;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::Once;
|
||||
use std::sync::PoisonError;
|
||||
|
||||
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)]
|
||||
struct BorrowedMutex<'a>(&'a MutexId);
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ use std::ops::DerefMut;
|
||||
use std::sync::LockResult;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::MutexGuard;
|
||||
use std::sync::Once;
|
||||
use std::sync::OnceState;
|
||||
use std::sync::PoisonError;
|
||||
use std::sync::RwLock;
|
||||
use std::sync::RwLockReadGuard;
|
||||
@@ -27,6 +29,7 @@ use std::sync::TryLockError;
|
||||
use std::sync::TryLockResult;
|
||||
|
||||
use crate::BorrowedMutex;
|
||||
use crate::LazyMutexId;
|
||||
use crate::MutexId;
|
||||
|
||||
/// Debug-only tracing `Mutex`.
|
||||
@@ -41,7 +44,7 @@ pub type DebugMutex<T> = Mutex<T>;
|
||||
|
||||
/// 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
|
||||
/// detection in development but do not want to pay the performance penalty in release.
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -49,6 +52,16 @@ pub type DebugRwLock<T> = TracingRwLock<T>;
|
||||
#[cfg(not(debug_assertions))]
|
||||
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`].
|
||||
///
|
||||
/// 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)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
@@ -356,6 +423,28 @@ mod tests {
|
||||
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]
|
||||
#[should_panic(expected = "Mutex order graph should not have cycles")]
|
||||
fn test_detect_cycle() {
|
||||
|
||||
Reference in New Issue
Block a user