35 Commits

Author SHA1 Message Date
8f19921e9e Merge pull request #30 from bertptrs/remove-lazy-static 2023-08-27 11:26:58 +02:00
00420d6807 Implement wrapper for OnceLock 2023-08-26 00:58:54 +02:00
49b15bb6bd Bump MSRV to 1.70 2023-08-26 00:58:54 +02:00
29c9daf53e Replace dependency on lazy-static with OnceLock 2023-08-25 08:44:45 +02:00
bors[bot]
8feedb09d2 Merge #27
27: Add MSRV of 1.63 r=bertptrs a=bertptrs

Fixes #26.

Co-authored-by: Bert Peters <bert@bertptrs.nl>
2022-08-29 06:34:17 +00:00
de9888a102 Update documentation with MSRV 2022-08-29 08:32:17 +02:00
2d2e03eede Simplify lazy mutex ID drop 2022-08-29 08:26:12 +02:00
e9b577a0f5 Make stdsync wrappers const-constructible 2022-08-27 10:33:15 +02:00
5f6823394d Build and test with Rust 1.63 2022-08-27 10:26:42 +02:00
bors[bot]
61d19f866c Merge #25
25: Restructure modules r=bertptrs a=bertptrs

The `TracingFoo`, `DebugFoo` versions of every `Foo` resulted in quite verbose types everywhere. This PR restructures them to separate modules. The new modules map onto the old types as follows:

- `tracing_mutex::foo::TracingFoo` -> `tracing_mutex::foo::tracing::Foo`
- `tracing_mutex::foo::DebugFoo` -> `tracing_mutex::foo::Foo`

Co-authored-by: Bert Peters <bert@bertptrs.nl>
2022-08-27 08:18:29 +00:00
f78969ebf7 Update documentation 2022-08-27 10:08:51 +02:00
56b0604448 Restructure parking_lot wrappers 2022-08-27 10:06:31 +02:00
6e5516eaa7 Restructure std::sync wrappers 2022-08-27 10:01:51 +02:00
764d3df454 Add parking_lot to changelog 2022-08-24 10:28:51 +02:00
bors[bot]
e543860d8b Merge #24
24: Update parking_lot dependency to 0.12 r=bertptrs a=djkoloski

The changelog for parking_lot 0.12 can be found [here](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md#parking_lot-0120-parking_lot_core-090-lock_api-046-2022-01-28):
```
- The MSRV is bumped to 1.49.0.
- Disabled eventual fairness on wasm32-unknown-unknown. (#302)
- Added a rwlock method to report if lock is held exclusively. (#303)
- Use new asm! macro. (#304)
- Use windows-rs instead of winapi for faster builds. (#311)
- Moved hardware lock elision support to a separate Cargo feature. (#313)
- Removed used of deprecated spin_loop_hint. (#314)
```

Co-authored-by: David Koloski <dkoloski@google.com>
2022-08-24 08:20:55 +00:00
David Koloski
ed04552af3 Update parking_lot dependency to 0.12 2022-08-23 11:34:31 -04:00
bors[bot]
c5a506436c Merge #23
23: Ensure `BorrowedMutex` is `!Send` r=bertptrs a=bertptrs

This should prevent the bugs found in #22.

Co-authored-by: Bert Peters <bert@bertptrs.nl>
2022-06-23 20:02:10 +00:00
33cb6014a3 Ensure BorrowedMutex is !Send 2022-06-23 21:54:25 +02:00
5232bac582 Bump version 2022-05-23 08:59:47 +02:00
bors[bot]
6472f4b807 Merge #21
21: Prepare for release v0.2.1 r=bertptrs a=bertptrs



Co-authored-by: Bert Peters <bert@bertptrs.nl>
2022-05-23 06:55:45 +00:00
6afe7b1c48 Update README and CHANGELOG 2022-05-23 08:53:56 +02:00
9238ef53ee Update copyright 2022-05-23 08:37:26 +02:00
bors[bot]
c08addff7d Merge #17
17: Fix typos r=bertptrs a=quisar



Co-authored-by: Benjamin Lerman <qsr@chromium.org>
2022-05-23 06:33:21 +00:00
bors[bot]
c1ce9df8ad Merge #19
19: Add a wrapper for `std::sync::Condvar` r=bertptrs a=bertptrs

This wrapper does not do any tracing itself but supports the use of a tracing mutex guard instead of an `std::sync` one.

Co-authored-by: Bert Peters <bert@bertptrs.nl>
2022-05-17 19:50:02 +00:00
312eaa8649 Add a wrapper for std::sync::Condvar
This wrapper does not do any tracing itself but supports the use of a
tracing mutex guard instead of an `std::sync` one.
2022-05-17 21:45:25 +02:00
bors[bot]
1f7e6921aa Merge #18
18: Enable bors for nicer merging r=bertptrs a=bertptrs



Co-authored-by: Bert Peters <bert@bertptrs.nl>
2022-05-15 21:40:40 +00:00
f7048f265f Enable CI builds on staging/trying 2022-05-15 23:35:00 +02:00
64e56fdb86 Add minimal bors config 2022-05-15 23:35:00 +02:00
Benjamin Lerman
8e3278fdd2 Fix typos 2022-05-10 10:30:20 +02:00
9ea993e737 Add missing date 2022-05-07 18:15:50 +02:00
062850fc3e Merge pull request #16 from bertptrs/docsrs_feature_docs
Fix documentation builds for features
2022-05-07 17:56:09 +02:00
0d2622d5c6 Build documentation on CI 2022-05-07 17:52:32 +02:00
d1417e0b0c Tag module docs with their required features 2022-05-07 17:52:32 +02:00
fcc64e2cef Automatically build documentation for all features 2022-05-07 17:03:45 +02:00
fd0d05307c Update README and copyright year 2022-05-07 16:54:37 +02:00
11 changed files with 929 additions and 716 deletions

View File

@@ -2,17 +2,20 @@ on:
push: push:
branches: branches:
- master - master
- staging
- trying
pull_request: pull_request:
name: Continuous integration name: Continuous integration
jobs: jobs:
ci: tests:
name: Rust project name: Rust project
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
rust: rust:
- "1.70" # minimum stable rust version
- stable - stable
- beta - beta
- nightly - nightly
@@ -47,3 +50,22 @@ jobs:
with: with:
command: clippy command: clippy
args: --all-features --all-targets -- -D warnings args: --all-features --all-targets -- -D warnings
docs:
name: Documentation build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
- name: Build documentation
env:
# Build the docs like docs.rs builds it
RUSTDOCFLAGS: --cfg docsrs
run: cargo doc --all-features

View File

@@ -6,7 +6,35 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [Unreleased]
## [0.2.0] ### Added
- The minimum supported Rust version is now defined as 1.70. Previously it was undefined.
- Wrappers for `std::sync` primitives can now be `const` constructed.
- Add support for `std::sync::OnceLock`
### Breaking
- Update [`parking_lot`][parking_lot] dependency to `0.12`.
- Restructured the crate to reduce typename verbosity. For details, see: #25.
### Fixed
- Enforce that all internal mutex guards are `!Send`. They already should be according to other
reasons, but this adds extra security through the type system.
## [0.2.1] - 2022-05-23
### Added
- Build [docs.rs] documentation with all features enabled for completeness.
- Add support for `std::sync::Condvar`
### Fixed
- The `parkinglot` module is now correctly enabled by the `parkinglot` feature rather than the
`lockapi` feature.
## [0.2.0] - 2022-05-07
### Added ### Added
- Generic support for wrapping mutexes that implement the traits provided by the - Generic support for wrapping mutexes that implement the traits provided by the
@@ -55,11 +83,13 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Initial release. Initial release.
[Unreleased]: https://github.com/bertptrs/tracing-mutex/compare/v0.2.0...HEAD [Unreleased]: https://github.com/bertptrs/tracing-mutex/compare/v0.2.1...HEAD
[0.2.1]: https://github.com/bertptrs/tracing-mutex/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/bertptrs/tracing-mutex/compare/v0.1.2...v0.2.0 [0.2.0]: https://github.com/bertptrs/tracing-mutex/compare/v0.1.2...v0.2.0
[0.1.2]: https://github.com/bertptrs/tracing-mutex/compare/v0.1.1...v0.1.2 [0.1.2]: https://github.com/bertptrs/tracing-mutex/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/bertptrs/tracing-mutex/compare/v0.1.0...v0.1.1 [0.1.1]: https://github.com/bertptrs/tracing-mutex/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/bertptrs/tracing-mutex/releases/tag/v0.1.0 [0.1.0]: https://github.com/bertptrs/tracing-mutex/releases/tag/v0.1.0
[docs.rs]: https://docs.rs/tracing-mutex/latest/tracing_mutex/
[lock_api]: https://docs.rs/lock_api/ [lock_api]: https://docs.rs/lock_api/
[parking_lot]: https://docs.rs/parking_lot/ [parking_lot]: https://docs.rs/parking_lot/

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "tracing-mutex" name = "tracing-mutex"
version = "0.2.0" version = "0.2.1"
authors = ["Bert Peters <bert@bertptrs.nl>"] authors = ["Bert Peters <bert@bertptrs.nl>"]
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
@@ -10,11 +10,17 @@ keywords = ["mutex", "rwlock", "once", "thread"]
description = "Ensure deadlock-free mutexes by allocating in order, or else." description = "Ensure deadlock-free mutexes by allocating in order, or else."
readme = "README.md" readme = "README.md"
repository = "https://github.com/bertptrs/tracing-mutex" repository = "https://github.com/bertptrs/tracing-mutex"
rust-version = "1.70"
[package.metadata.docs.rs]
# Build docs for all features so the documentation is more complete
all-features = true
# Set custom cfg so we can enable docs.rs magic
rustdoc-args = ["--cfg", "docsrs"]
[dependencies] [dependencies]
lazy_static = "1"
lock_api = { version = "0.4", optional = true } lock_api = { version = "0.4", optional = true }
parking_lot = { version = "0.11", optional = true } parking_lot = { version = "0.12", optional = true }
[dev-dependencies] [dev-dependencies]
criterion = "0.3" criterion = "0.3"

View File

@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright [yyyy] [name of copyright owner] Copyright 2022 Bert Peters
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
Copyright © 2021 Bert Peters Copyright © 2022 Bert Peters
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the “Software”), to deal in the Software without restriction, associated documentation files (the “Software”), to deal in the Software without restriction,

View File

@@ -23,10 +23,12 @@ tree out of it, and panics if your dependencies would create a cycle. It provide
existing synchronization primitives with an identical API, and should be a drop-in replacement. existing synchronization primitives with an identical API, and should be a drop-in replacement.
Inspired by [this blogpost][whileydave], which references a similar behaviour implemented by Inspired by [this blogpost][whileydave], which references a similar behaviour implemented by
[Abseil][abseil-mutex] for their mutexes. [Abseil][abseil-mutex] for their mutexes. [This article goes into more depth on the exact
implementation.][article]
[whileydave]: https://whileydave.com/2020/12/19/dynamic-cycle-detection-for-lock-ordering/ [whileydave]: https://whileydave.com/2020/12/19/dynamic-cycle-detection-for-lock-ordering/
[abseil-mutex]: https://abseil.io/docs/cpp/guides/synchronization [abseil-mutex]: https://abseil.io/docs/cpp/guides/synchronization
[article]: https://bertptrs.nl/2022/06/23/deadlock-free-mutexes-and-directed-acyclic-graphs.html
## Usage ## Usage
@@ -34,7 +36,7 @@ Add this dependency to your `Cargo.lock` file like any other:
```toml ```toml
[dependencies] [dependencies]
tracing-mutex = "0.1" tracing-mutex = "0.2"
``` ```
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.
@@ -42,9 +44,9 @@ Replacements for the synchronization primitives in `std::sync` can be found in t
Support for other synchronization primitives is planned. Support for other synchronization primitives is planned.
```rust ```rust
use tracing_mutex::stdsync::TracingMutex; use tracing_mutex::stdsync::Mutex;
let some_mutex = TracingMutex::new(42); let some_mutex = Mutex::new(42);
*some_mutex.lock().unwrap() += 1; *some_mutex.lock().unwrap() += 1;
println!("{:?}", some_mutex); println!("{:?}", some_mutex);
``` ```
@@ -59,12 +61,26 @@ performance penalty in your production environment, this library also offers deb
when debug assertions are enabled, and to `Mutex` when they are not. Similar helper types are when debug assertions are enabled, and to `Mutex` when they are not. Similar helper types are
available for other synchronization primitives. available for other synchronization primitives.
The minimum supported Rust version is 1.70. Increasing this is not considered a breaking change, but
will be avoided within semver-compatible releases if possible.
### Features
- Dependency-tracking wrappers for all locking primitives
- Optional opt-out for release mode code
- Support for primitives from:
- `std::sync`
- `parking_lot`
- Any library that implements the `lock_api` traits
## Future improvements ## Future improvements
- Improve performance in lock tracing - Improve performance in lock tracing
- Optional logging to make debugging easier - Optional logging to make debugging easier
- Better and configurable error handling when detecting cyclic dependencies - Better and configurable error handling when detecting cyclic dependencies
- Support for other locking libraries, such as `parking_lot` - Support for other locking libraries
- Support for async locking libraries
- Support for `Send` mutex guards
**Note:** `parking_lot` has already began work on its own deadlock detection mechanism, which works **Note:** `parking_lot` has already began work on its own deadlock detection mechanism, which works
in a different way. Both can be complimentary. in a different way. Both can be complimentary.

View File

@@ -7,7 +7,7 @@ use criterion::BenchmarkId;
use criterion::Criterion; use criterion::Criterion;
use criterion::Throughput; use criterion::Throughput;
use rand::prelude::*; use rand::prelude::*;
use tracing_mutex::stdsync::TracingMutex; use tracing_mutex::stdsync::tracing::Mutex as TracingMutex;
const SAMPLE_SIZES: [usize; 5] = [10, 30, 100, 300, 1000]; const SAMPLE_SIZES: [usize; 5] = [10, 30, 100, 300, 1000];

6
bors.toml Normal file
View File

@@ -0,0 +1,6 @@
status = [
'Rust project (1.70)',
'Rust project (stable)',
'Rust project (beta)',
'Documentation build',
]

View File

@@ -41,44 +41,41 @@
//! //!
//! These operations have been reasonably optimized, but the performance penalty may yet be too much //! These operations have been reasonably optimized, but the performance penalty may yet be too much
//! for production use. In those cases, it may be beneficial to instead use debug-only versions //! for production use. In those cases, it may be beneficial to instead use debug-only versions
//! (such as [`stdsync::DebugMutex`]) which evaluate to a tracing mutex when debug assertions are //! (such as [`stdsync::Mutex`]) which evaluate to a tracing mutex when debug assertions are
//! enabled, and to the underlying mutex when they're not. //! enabled, and to the underlying mutex when they're not.
//! //!
//! [paper]: https://whileydave.com/publications/pk07_jea/ //! [paper]: https://whileydave.com/publications/pk07_jea/
#![cfg_attr(docsrs, feature(doc_cfg))]
use std::cell::RefCell; use std::cell::RefCell;
use std::cell::UnsafeCell;
use std::fmt; use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::ops::Deref; use std::ops::Deref;
use std::ops::DerefMut; use std::ops::DerefMut;
use std::ptr;
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::MutexGuard;
use std::sync::OnceLock;
use std::sync::PoisonError; use std::sync::PoisonError;
use lazy_static::lazy_static;
#[cfg(feature = "lockapi")] #[cfg(feature = "lockapi")]
#[cfg_attr(docsrs, doc(cfg(feature = "lockapi")))]
pub use lock_api; pub use lock_api;
#[cfg(feature = "parkinglot")] #[cfg(feature = "parkinglot")]
#[cfg_attr(docsrs, doc(cfg(feature = "parkinglot")))]
pub use parking_lot; pub use parking_lot;
use crate::graph::DiGraph; use crate::graph::DiGraph;
mod graph; mod graph;
#[cfg(feature = "lockapi")] #[cfg(feature = "lockapi")]
#[cfg_attr(docsrs, doc(cfg(feature = "lockapi")))]
pub mod lockapi; pub mod lockapi;
#[cfg(feature = "lockapi")] #[cfg(feature = "parkinglot")]
#[cfg_attr(docsrs, doc(cfg(feature = "parkinglot")))]
pub mod parkinglot; pub mod parkinglot;
pub mod stdsync; pub mod stdsync;
/// Counter for Mutex IDs. Atomic avoids the need for locking.
///
/// Should be part of the `MutexID` impl but static items are not yet a thing.
static ID_SEQUENCE: AtomicUsize = AtomicUsize::new(0);
thread_local! { thread_local! {
/// Stack to track which locks are held /// Stack to track which locks are held
/// ///
@@ -87,10 +84,6 @@ thread_local! {
static HELD_LOCKS: RefCell<Vec<usize>> = RefCell::new(Vec::new()); static HELD_LOCKS: RefCell<Vec<usize>> = RefCell::new(Vec::new());
} }
lazy_static! {
static ref DEPENDENCY_GRAPH: Mutex<DiGraph<usize>> = Default::default();
}
/// Dedicated ID type for Mutexes /// Dedicated ID type for Mutexes
/// ///
/// # Unstable /// # Unstable
@@ -109,6 +102,9 @@ impl MutexId {
/// This function may panic when there are no more mutex IDs available. The number of mutex ids /// This function may panic when there are no more mutex IDs available. The number of mutex ids
/// is `usize::MAX - 1` which should be plenty for most practical applications. /// is `usize::MAX - 1` which should be plenty for most practical applications.
pub fn new() -> Self { pub fn new() -> Self {
// Counter for Mutex IDs. Atomic avoids the need for locking.
static ID_SEQUENCE: AtomicUsize = AtomicUsize::new(0);
ID_SEQUENCE ID_SEQUENCE
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |id| id.checked_add(1)) .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |id| id.checked_add(1))
.map(Self) .map(Self)
@@ -129,7 +125,10 @@ impl MutexId {
/// This method panics if the new dependency would introduce a cycle. /// This method panics if the new dependency would introduce a cycle.
pub fn get_borrowed(&self) -> BorrowedMutex { pub fn get_borrowed(&self) -> BorrowedMutex {
self.mark_held(); self.mark_held();
BorrowedMutex(self) BorrowedMutex {
id: self,
_not_send: PhantomData,
}
} }
/// Mark this lock as held for the purposes of dependency tracking. /// Mark this lock as held for the purposes of dependency tracking.
@@ -199,17 +198,13 @@ impl Drop for MutexId {
/// ///
/// This type can be largely replaced once std::lazy gets stabilized. /// This type can be largely replaced once std::lazy gets stabilized.
struct LazyMutexId { struct LazyMutexId {
inner: UnsafeCell<MaybeUninit<MutexId>>, inner: OnceLock<MutexId>,
setter: Once,
_marker: PhantomData<MutexId>,
} }
impl LazyMutexId { impl LazyMutexId {
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
inner: UnsafeCell::new(MaybeUninit::uninit()), inner: OnceLock::new(),
setter: Once::new(),
_marker: PhantomData,
} }
} }
} }
@@ -226,51 +221,30 @@ impl Default for LazyMutexId {
} }
} }
/// Safety: the UnsafeCell is guaranteed to only be accessed mutably from a `Once`.
unsafe impl Sync for LazyMutexId {}
impl Deref for LazyMutexId { impl Deref for LazyMutexId {
type Target = MutexId; type Target = MutexId;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
self.setter.call_once(|| { self.inner.get_or_init(MutexId::new)
// Safety: this function is only called once, so only one mutable reference should exist
// at a time.
unsafe {
*self.inner.get() = MaybeUninit::new(MutexId::new());
}
});
// Safety: after the above Once runs, there are no longer any mutable references, so we can
// hand this out safely.
//
// Explanation of this monstrosity:
//
// - Get a pointer to the data from the UnsafeCell
// - Dereference that to get a reference to the underlying MaybeUninit
// - Use as_ptr on MaybeUninit to get a pointer to the initialized MutexID
// - Dereference the pointer to turn in into a reference as intended.
//
// This should get slightly nicer once `maybe_uninit_extra` is stabilized.
unsafe { &*((*self.inner.get()).as_ptr()) }
}
}
impl Drop for LazyMutexId {
fn drop(&mut self) {
if self.setter.is_completed() {
// We have a valid mutex ID and need to drop it
// Safety: we know that this pointer is valid because the initializer has successfully run.
let mutex_id = unsafe { ptr::read((*self.inner.get()).as_ptr()) };
drop(mutex_id);
}
} }
} }
/// Borrowed mutex ID
///
/// This type should be used as part of a mutex guard wrapper. It can be acquired through
/// [`MutexId::get_borrowed`] and will automatically mark the mutex as not borrowed when it is
/// dropped.
///
/// This type intentionally is [`!Send`](std::marker::Send) because the ownership tracking is based
/// on a thread-local stack which doesn't work if a guard gets released in a different thread from
/// where they're acquired.
#[derive(Debug)] #[derive(Debug)]
struct BorrowedMutex<'a>(&'a MutexId); struct BorrowedMutex<'a> {
/// Reference to the mutex we're borrowing from
id: &'a MutexId,
/// This value serves no purpose but to make the type [`!Send`](std::marker::Send)
_not_send: PhantomData<MutexGuard<'static, ()>>,
}
/// Drop a lock held by the current thread. /// Drop a lock held by the current thread.
/// ///
@@ -281,13 +255,16 @@ struct BorrowedMutex<'a>(&'a MutexId);
impl<'a> Drop for BorrowedMutex<'a> { impl<'a> Drop for BorrowedMutex<'a> {
fn drop(&mut self) { fn drop(&mut self) {
// Safety: the only way to get a BorrowedMutex is by locking the mutex. // Safety: the only way to get a BorrowedMutex is by locking the mutex.
unsafe { self.0.mark_released() }; unsafe { self.id.mark_released() };
} }
} }
/// Get a reference to the current dependency graph /// Get a reference to the current dependency graph
fn get_dependency_graph() -> impl DerefMut<Target = DiGraph<usize>> { fn get_dependency_graph() -> impl DerefMut<Target = DiGraph<usize>> {
static DEPENDENCY_GRAPH: OnceLock<Mutex<DiGraph<usize>>> = OnceLock::new();
DEPENDENCY_GRAPH DEPENDENCY_GRAPH
.get_or_init(Default::default)
.lock() .lock()
.unwrap_or_else(PoisonError::into_inner) .unwrap_or_else(PoisonError::into_inner)
} }

View File

@@ -1,19 +1,20 @@
//! Wrapper types and type aliases for tracing [`parking_lot`] mutexes. //! Wrapper types and type aliases for tracing [`parking_lot`] mutexes.
//! //!
//! This module provides type aliases that use the [`lockapi`][crate::lockapi] module to provide //! This module provides type aliases that use the [`lockapi`][crate::lockapi] module to provide
//! tracing variants of the `parking_lot` primitives. Each of the `TracingX` type aliases wraps an //! tracing variants of the `parking_lot` primitives. The [`tracing`] module contains type aliases
//! `X` in the `parkint_lot` api with dependency tracking, and a `DebugX` will refer to a `TracingX` //! that use dependency tracking, while the main `parking_lot` primitives are reexported as [`raw`].
//! when `debug_assertions` are enabled and to `X` when they're not. This can be used to aid //!
//! debugging in development while enjoying maximum performance in production. //! This main module imports from [`tracing`] when `debug_assertions` are enabled, and from [`raw`]
//! when they're not. Note that primitives for which no tracing wrapper exists are not imported into
//! the main module.
//! //!
//! # Usage //! # Usage
//! //!
//! ``` //! ```
//! # use std::sync::Arc; //! # use std::sync::Arc;
//! # use std::thread; //! # use std::thread;
//! # use lock_api::Mutex; //! use tracing_mutex::parkinglot::Mutex;
//! # use tracing_mutex::parkinglot::TracingMutex; //! let mutex = Arc::new(Mutex::new(0));
//! let mutex = Arc::new(TracingMutex::new(0));
//! //!
//! let handles: Vec<_> = (0..10).map(|_| { //! let handles: Vec<_> = (0..10).map(|_| {
//! let mutex = Arc::clone(&mutex); //! let mutex = Arc::clone(&mutex);
@@ -37,141 +38,89 @@
//! In addition, the mutex guards returned by the tracing wrappers are `!Send`, regardless of //! In addition, the mutex guards returned by the tracing wrappers are `!Send`, regardless of
//! whether `parking_lot` is configured to have `Send` mutex guards. This is a limitation of the //! whether `parking_lot` is configured to have `Send` mutex guards. This is a limitation of the
//! current bookkeeping system. //! current bookkeeping system.
use parking_lot::Once;
use parking_lot::OnceState; pub use parking_lot as raw;
#[cfg(debug_assertions)]
pub use tracing::{
FairMutex, FairMutexGuard, MappedFairMutexGuard, MappedMutexGuard, MappedReentrantMutexGuard,
MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, MutexGuard, Once, OnceState,
ReentrantMutex, ReentrantMutexGuard, RwLock, RwLockReadGuard, RwLockUpgradableReadGuard,
RwLockWriteGuard,
};
#[cfg(not(debug_assertions))]
pub use parking_lot::{
FairMutex, FairMutexGuard, MappedFairMutexGuard, MappedMutexGuard, MappedReentrantMutexGuard,
MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, MutexGuard, Once, OnceState,
ReentrantMutex, ReentrantMutexGuard, RwLock, RwLockReadGuard, RwLockUpgradableReadGuard,
RwLockWriteGuard,
};
/// Dependency tracing wrappers for [`parking_lot`].
pub mod tracing {
pub use parking_lot::OnceState;
use crate::lockapi::TracingWrapper; use crate::lockapi::TracingWrapper;
use crate::LazyMutexId; use crate::LazyMutexId;
macro_rules! debug_variant { type RawFairMutex = TracingWrapper<parking_lot::RawFairMutex>;
($debug_name:ident, $tracing_name:ident, $normal_name:ty) => { type RawMutex = TracingWrapper<parking_lot::RawMutex>;
type $tracing_name = TracingWrapper<$normal_name>; type RawRwLock = TracingWrapper<parking_lot::RawRwLock>;
#[cfg(debug_assertions)]
type $debug_name = TracingWrapper<$normal_name>;
#[cfg(not(debug_assertions))]
type $debug_name = $normal_name;
};
}
debug_variant!(
DebugRawFairMutex,
TracingRawFairMutex,
parking_lot::RawFairMutex
);
debug_variant!(DebugRawMutex, TracingRawMutex, parking_lot::RawMutex);
debug_variant!(DebugRawRwLock, TracingRawRwLock, parking_lot::RawRwLock);
/// Dependency tracking fair mutex. See: [`parking_lot::FairMutex`]. /// Dependency tracking fair mutex. See: [`parking_lot::FairMutex`].
pub type TracingFairMutex<T> = lock_api::Mutex<TracingRawFairMutex, T>; pub type FairMutex<T> = lock_api::Mutex<RawFairMutex, T>;
/// Mutex guard for [`TracingFairMutex`]. /// Mutex guard for [`FairMutex`].
pub type TracingFairMutexGuard<'a, T> = lock_api::MutexGuard<'a, TracingRawFairMutex, T>; pub type FairMutexGuard<'a, T> = lock_api::MutexGuard<'a, RawFairMutex, T>;
/// RAII guard for `TracingFairMutexGuard::map`. /// RAII guard for [`FairMutexGuard::map`].
pub type TracingMappedFairMutexGuard<'a, T> = pub type MappedFairMutexGuard<'a, T> = lock_api::MappedMutexGuard<'a, RawFairMutex, T>;
lock_api::MappedMutexGuard<'a, TracingRawFairMutex, T>;
/// Debug-only dependency tracking fair mutex.
///
/// If debug assertions are enabled this resolves to [`TracingFairMutex`] and to
/// [`parking_lot::FairMutex`] otherwise.
pub type DebugFairMutex<T> = lock_api::Mutex<DebugRawFairMutex, T>;
/// Mutex guard for [`DebugFairMutex`].
pub type DebugFairMutexGuard<'a, T> = lock_api::MutexGuard<'a, DebugRawFairMutex, T>;
/// RAII guard for `DebugFairMutexGuard::map`.
pub type DebugMappedFairMutexGuard<'a, T> = lock_api::MappedMutexGuard<'a, DebugRawFairMutex, T>;
/// Dependency tracking mutex. See: [`parking_lot::Mutex`]. /// Dependency tracking mutex. See: [`parking_lot::Mutex`].
pub type TracingMutex<T> = lock_api::Mutex<TracingRawMutex, T>; pub type Mutex<T> = lock_api::Mutex<RawMutex, T>;
/// Mutex guard for [`TracingMutex`]. /// Mutex guard for [`Mutex`].
pub type TracingMutexGuard<'a, T> = lock_api::MutexGuard<'a, TracingRawMutex, T>; pub type MutexGuard<'a, T> = lock_api::MutexGuard<'a, RawMutex, T>;
/// RAII guard for `TracingMutexGuard::map`. /// RAII guard for [`MutexGuard::map`].
pub type TracingMappedMutexGuard<'a, T> = lock_api::MappedMutexGuard<'a, TracingRawMutex, T>; pub type MappedMutexGuard<'a, T> = lock_api::MappedMutexGuard<'a, RawMutex, T>;
/// Debug-only dependency tracking mutex.
///
/// If debug assertions are enabled this resolves to [`TracingMutex`] and to [`parking_lot::Mutex`]
/// otherwise.
pub type DebugMutex<T> = lock_api::Mutex<DebugRawMutex, T>;
/// Mutex guard for [`DebugMutex`].
pub type DebugMutexGuard<'a, T> = lock_api::MutexGuard<'a, DebugRawMutex, T>;
/// RAII guard for `TracingMutexGuard::map`.
pub type DebugMappedMutexGuard<'a, T> = lock_api::MappedMutexGuard<'a, DebugRawMutex, T>;
/// Dependency tracking reentrant mutex. See: [`parking_lot::ReentrantMutex`]. /// Dependency tracking reentrant mutex. See: [`parking_lot::ReentrantMutex`].
/// ///
/// **Note:** due to the way dependencies are tracked, this mutex can only be acquired directly /// **Note:** due to the way dependencies are tracked, this mutex can only be acquired directly
/// after itself. Acquiring any other mutex in between introduces a dependency cycle, and will /// after itself. Acquiring any other mutex in between introduces a dependency cycle, and will
/// therefore be rejected. /// therefore be rejected.
pub type TracingReentrantMutex<T> = pub type ReentrantMutex<T> = lock_api::ReentrantMutex<RawMutex, parking_lot::RawThreadId, T>;
lock_api::ReentrantMutex<TracingWrapper<parking_lot::RawMutex>, parking_lot::RawThreadId, T>; /// Mutex guard for [`ReentrantMutex`].
/// Mutex guard for [`TracingReentrantMutex`]. pub type ReentrantMutexGuard<'a, T> =
pub type TracingReentrantMutexGuard<'a, T> = lock_api::ReentrantMutexGuard< lock_api::ReentrantMutexGuard<'a, RawMutex, parking_lot::RawThreadId, T>;
'a, /// RAII guard for `ReentrantMutexGuard::map`.
TracingWrapper<parking_lot::RawMutex>, pub type MappedReentrantMutexGuard<'a, T> =
parking_lot::RawThreadId, lock_api::MappedReentrantMutexGuard<'a, RawMutex, parking_lot::RawThreadId, T>;
T,
>;
/// RAII guard for `TracingReentrantMutexGuard::map`.
pub type TracingMappedReentrantMutexGuard<'a, T> =
lock_api::MappedReentrantMutexGuard<'a, TracingRawMutex, parking_lot::RawThreadId, T>;
/// Debug-only dependency tracking reentrant mutex.
///
/// If debug assertions are enabled this resolves to [`TracingReentrantMutex`] and to
/// [`parking_lot::ReentrantMutex`] otherwise.
pub type DebugReentrantMutex<T> =
lock_api::ReentrantMutex<DebugRawMutex, parking_lot::RawThreadId, T>;
/// Mutex guard for [`DebugReentrantMutex`].
pub type DebugReentrantMutexGuard<'a, T> =
lock_api::ReentrantMutexGuard<'a, DebugRawMutex, parking_lot::RawThreadId, T>;
/// RAII guard for `DebugReentrantMutexGuard::map`.
pub type DebugMappedReentrantMutexGuard<'a, T> =
lock_api::MappedReentrantMutexGuard<'a, DebugRawMutex, parking_lot::RawThreadId, T>;
/// Dependency tracking RwLock. See: [`parking_lot::RwLock`]. /// Dependency tracking RwLock. See: [`parking_lot::RwLock`].
pub type TracingRwLock<T> = lock_api::RwLock<TracingRawRwLock, T>; pub type RwLock<T> = lock_api::RwLock<RawRwLock, T>;
/// Read guard for [`TracingRwLock`]. /// Read guard for [`RwLock`].
pub type TracingRwLockReadGuard<'a, T> = lock_api::RwLockReadGuard<'a, TracingRawRwLock, T>; pub type RwLockReadGuard<'a, T> = lock_api::RwLockReadGuard<'a, RawRwLock, T>;
/// Upgradable Read guard for [`TracingRwLock`]. /// Upgradable Read guard for [`RwLock`].
pub type TracingRwLockUpgradableReadGuard<'a, T> = pub type RwLockUpgradableReadGuard<'a, T> =
lock_api::RwLockUpgradableReadGuard<'a, TracingRawRwLock, T>; lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>;
/// Write guard for [`TracingRwLock`]. /// Write guard for [`RwLock`].
pub type TracingRwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, TracingRawRwLock, T>; pub type RwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, RawRwLock, T>;
/// RAII guard for `TracingRwLockReadGuard::map`. /// RAII guard for `RwLockReadGuard::map`.
pub type TracingMappedRwLockReadGuard<'a, T> = pub type MappedRwLockReadGuard<'a, T> = lock_api::MappedRwLockReadGuard<'a, RawRwLock, T>;
lock_api::MappedRwLockReadGuard<'a, TracingRawRwLock, T>; /// RAII guard for `RwLockWriteGuard::map`.
/// RAII guard for `TracingRwLockWriteGuard::map`. pub type MappedRwLockWriteGuard<'a, T> = lock_api::MappedRwLockWriteGuard<'a, RawRwLock, T>;
pub type TracingMappedRwLockWriteGuard<'a, T> =
lock_api::MappedRwLockWriteGuard<'a, TracingRawRwLock, T>;
/// Debug-only dependency tracking RwLock.
///
/// If debug assertions are enabled this resolved to [`TracingRwLock`] and to
/// [`parking_lot::RwLock`] otherwise.
pub type DebugRwLock<T> = lock_api::RwLock<DebugRawRwLock, T>;
/// Read guard for [`TracingRwLock`].
pub type DebugRwLockReadGuard<'a, T> = lock_api::RwLockReadGuard<'a, DebugRawRwLock, T>;
/// Upgradable Read guard for [`TracingRwLock`].
pub type DebugRwLockUpgradableReadGuard<'a, T> =
lock_api::RwLockUpgradableReadGuard<'a, DebugRawRwLock, T>;
/// Write guard for [`TracingRwLock`].
pub type DebugRwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, DebugRawRwLock, T>;
/// RAII guard for `DebugRwLockReadGuard::map`.
pub type DebugMappedRwLockReadGuard<'a, T> = lock_api::MappedRwLockReadGuard<'a, DebugRawRwLock, T>;
/// RAII guard for `DebugRwLockWriteGuard::map`.
pub type DebugMappedRwLockWriteGuard<'a, T> =
lock_api::MappedRwLockWriteGuard<'a, DebugRawRwLock, T>;
/// A dependency-tracking wrapper for [`parking_lot::Once`]. /// A dependency-tracking wrapper for [`parking_lot::Once`].
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct TracingOnce { pub struct Once {
inner: Once, inner: parking_lot::Once,
id: LazyMutexId, id: LazyMutexId,
} }
impl TracingOnce { impl Once {
/// Create a new `TracingOnce` value. /// Create a new `Once` value.
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
inner: Once::new(), inner: parking_lot::Once::new(),
id: LazyMutexId::new(), id: LazyMutexId::new(),
} }
} }
@@ -181,8 +130,7 @@ impl TracingOnce {
self.inner.state() self.inner.state()
} }
/// /// This call is considered as "locking this `Once`" and it participates in dependency
/// This call is considered as "locking this `TracingOnce`" and it participates in dependency
/// tracking as such. /// tracking as such.
/// ///
/// # Panics /// # Panics
@@ -194,34 +142,26 @@ impl TracingOnce {
self.inner.call_once(f); self.inner.call_once(f);
} }
/// Performs the given initialization routeine once and only once. /// Performs the given initialization routine once and only once.
/// ///
/// This method is identical to [`TracingOnce::call_once`] except it ignores poisining. /// 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(); let _borrow = self.id.get_borrowed();
self.inner.call_once_force(f); self.inner.call_once_force(f);
} }
} }
}
/// Debug-only `Once`.
///
/// If debug assertions are enabled this resolves to [`TracingOnce`] and to [`parking_lot::Once`]
/// otherwise.
#[cfg(debug_assertions)]
pub type DebugOnce = TracingOnce;
#[cfg(not(debug_assertions))]
pub type DebugOnce = Once;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
use super::*; use super::tracing;
#[test] #[test]
fn test_mutex_usage() { fn test_mutex_usage() {
let mutex = Arc::new(TracingMutex::new(())); let mutex = Arc::new(tracing::Mutex::new(()));
let local_lock = mutex.lock(); let local_lock = mutex.lock();
drop(local_lock); drop(local_lock);
@@ -236,9 +176,9 @@ mod tests {
#[should_panic] #[should_panic]
fn test_mutex_conflict() { fn test_mutex_conflict() {
let mutexes = [ let mutexes = [
TracingMutex::new(()), tracing::Mutex::new(()),
TracingMutex::new(()), tracing::Mutex::new(()),
TracingMutex::new(()), tracing::Mutex::new(()),
]; ];
for i in 0..3 { for i in 0..3 {
@@ -249,7 +189,7 @@ mod tests {
#[test] #[test]
fn test_rwlock_usage() { fn test_rwlock_usage() {
let lock = Arc::new(TracingRwLock::new(())); let lock = Arc::new(tracing::RwLock::new(()));
let lock2 = Arc::clone(&lock); let lock2 = Arc::clone(&lock);
let _read_lock = lock.read(); let _read_lock = lock.read();
@@ -264,19 +204,19 @@ mod tests {
#[test] #[test]
fn test_rwlock_upgradable_read_usage() { fn test_rwlock_upgradable_read_usage() {
let lock = TracingRwLock::new(()); let lock = tracing::RwLock::new(());
// Should be able to acquire an upgradable read lock. // Should be able to acquire an upgradable read lock.
let upgradable_guard: TracingRwLockUpgradableReadGuard<'_, _> = lock.upgradable_read(); let upgradable_guard: tracing::RwLockUpgradableReadGuard<'_, _> = lock.upgradable_read();
// Should be able to upgrade the guard. // Should be able to upgrade the guard.
let _write_guard: TracingRwLockWriteGuard<'_, _> = let _write_guard: tracing::RwLockWriteGuard<'_, _> =
TracingRwLockUpgradableReadGuard::upgrade(upgradable_guard); tracing::RwLockUpgradableReadGuard::upgrade(upgradable_guard);
} }
#[test] #[test]
fn test_once_usage() { fn test_once_usage() {
let once = Arc::new(TracingOnce::new()); let once = Arc::new(tracing::Once::new());
let once_clone = once.clone(); let once_clone = once.clone();
assert!(!once_clone.state().done()); assert!(!once_clone.state().done());

View File

@@ -1,102 +1,69 @@
//! Tracing mutex wrappers for locks found in `std::sync`. //! Tracing mutex wrappers for locks found in `std::sync`.
//! //!
//! This module provides wrappers for `std::sync` primitives with exactly the same API and //! This module provides wrappers for `std::sync` primitives with exactly the same API and
//! functionality as their counterparts, with the exception that their acquisition order is //! functionality as their counterparts, with the exception that their acquisition order is tracked.
//! tracked. //!
//! Dedicated wrappers that provide the dependency tracing can be found in the [`tracing`] module.
//! The original primitives are available from [`std::sync`], imported as [`raw`] for convenience.
//!
//! If debug assertions are enabled, this module imports the primitives from [`tracing`], otherwise
//! it will import from [`raw`].
//! //!
//! ```rust //! ```rust
//! # use tracing_mutex::stdsync::TracingMutex; //! # use tracing_mutex::stdsync::tracing::Mutex;
//! # use tracing_mutex::stdsync::TracingRwLock; //! # use tracing_mutex::stdsync::tracing::RwLock;
//! let mutex = TracingMutex::new(()); //! let mutex = Mutex::new(());
//! mutex.lock().unwrap(); //! mutex.lock().unwrap();
//! //!
//! let rwlock = TracingRwLock::new(()); //! let rwlock = RwLock::new(());
//! rwlock.read().unwrap(); //! rwlock.read().unwrap();
//! ``` //! ```
pub use std::sync as raw;
#[cfg(not(debug_assertions))]
pub use std::sync::{
Condvar, Mutex, MutexGuard, Once, OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard,
};
#[cfg(debug_assertions)]
pub use tracing::{
Condvar, Mutex, MutexGuard, Once, OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard,
};
/// Dependency tracing versions of [`std::sync`].
pub mod tracing {
use std::fmt; use std::fmt;
use std::ops::Deref; use std::ops::Deref;
use std::ops::DerefMut; use std::ops::DerefMut;
use std::sync;
use std::sync::LockResult; use std::sync::LockResult;
use std::sync::Mutex;
use std::sync::MutexGuard;
use std::sync::Once;
use std::sync::OnceState; use std::sync::OnceState;
use std::sync::PoisonError; use std::sync::PoisonError;
use std::sync::RwLock;
use std::sync::RwLockReadGuard;
use std::sync::RwLockWriteGuard;
use std::sync::TryLockError; use std::sync::TryLockError;
use std::sync::TryLockResult; use std::sync::TryLockResult;
use std::sync::WaitTimeoutResult;
use std::time::Duration;
use crate::BorrowedMutex; use crate::BorrowedMutex;
use crate::LazyMutexId; use crate::LazyMutexId;
use crate::MutexId;
/// Debug-only tracing `Mutex`.
///
/// Type alias that resolves to [`TracingMutex`] when debug assertions are enabled and to
/// [`std::sync::Mutex`] 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 DebugMutex<T> = TracingMutex<T>;
#[cfg(not(debug_assertions))]
pub type DebugMutex<T> = Mutex<T>;
/// Mutex guard for [`DebugMutex`].
#[cfg(debug_assertions)]
pub type DebugMutexGuard<'a, T> = TracingMutexGuard<'a, T>;
#[cfg(not(debug_assertions))]
pub type DebugMutexGuard<'a, T> = MutexGuard<'a, T>;
/// Debug-only tracing `RwLock`.
///
/// 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)]
pub type DebugRwLock<T> = TracingRwLock<T>;
#[cfg(not(debug_assertions))]
pub type DebugRwLock<T> = RwLock<T>;
/// Read guard for [`DebugRwLock`].
#[cfg(debug_assertions)]
pub type DebugReadGuard<'a, T> = TracingReadGuard<'a, T>;
#[cfg(not(debug_assertions))]
pub type DebugReadGuard<'a, T> = RwLockReadGuard<'a, T>;
/// Write guard for [`DebugRwLock`].
#[cfg(debug_assertions)]
pub type DebugWriteGuard<'a, T> = TracingWriteGuard<'a, T>;
#[cfg(not(debug_assertions))]
pub type DebugWriteGuard<'a, T> = RwLockWriteGuard<'a, 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`] documentation for the differences between this struct and
/// the one it wraps. /// the one it wraps.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct TracingMutex<T> { pub struct Mutex<T> {
inner: Mutex<T>, inner: sync::Mutex<T>,
id: MutexId, id: LazyMutexId,
} }
/// Wrapper for [`std::sync::MutexGuard`]. /// Wrapper for [`std::sync::MutexGuard`].
/// ///
/// Refer to the [crate-level][`crate`] documentaiton for the differences between this struct and /// Refer to the [crate-level][`crate`] documentation for the differences between this struct and
/// the one it wraps. /// the one it wraps.
#[derive(Debug)] #[derive(Debug)]
pub struct TracingMutexGuard<'a, T> { pub struct MutexGuard<'a, T> {
inner: MutexGuard<'a, T>, inner: sync::MutexGuard<'a, T>,
_mutex: BorrowedMutex<'a>, _mutex: BorrowedMutex<'a>,
} }
@@ -123,12 +90,12 @@ where
} }
} }
impl<T> TracingMutex<T> { impl<T> Mutex<T> {
/// Create a new tracing mutex with the provided value. /// Create a new tracing mutex with the provided value.
pub fn new(t: T) -> Self { pub const fn new(t: T) -> Self {
Self { Self {
inner: Mutex::new(t), inner: sync::Mutex::new(t),
id: MutexId::new(), id: LazyMutexId::new(),
} }
} }
@@ -139,11 +106,11 @@ impl<T> TracingMutex<T> {
/// This method participates in lock dependency tracking. If acquiring this lock introduces a /// This method participates in lock dependency tracking. If acquiring this lock introduces a
/// dependency cycle, this method will panic. /// dependency cycle, this method will panic.
#[track_caller] #[track_caller]
pub fn lock(&self) -> LockResult<TracingMutexGuard<T>> { pub fn lock(&self) -> LockResult<MutexGuard<T>> {
let mutex = self.id.get_borrowed(); let mutex = self.id.get_borrowed();
let result = self.inner.lock(); let result = self.inner.lock();
let mapper = |guard| TracingMutexGuard { let mapper = |guard| MutexGuard {
_mutex: mutex, _mutex: mutex,
inner: guard, inner: guard,
}; };
@@ -158,11 +125,11 @@ impl<T> TracingMutex<T> {
/// This method participates in lock dependency tracking. If acquiring this lock introduces a /// This method participates in lock dependency tracking. If acquiring this lock introduces a
/// dependency cycle, this method will panic. /// dependency cycle, this method will panic.
#[track_caller] #[track_caller]
pub fn try_lock(&self) -> TryLockResult<TracingMutexGuard<T>> { pub fn try_lock(&self) -> TryLockResult<MutexGuard<T>> {
let mutex = self.id.get_borrowed(); let mutex = self.id.get_borrowed();
let result = self.inner.try_lock(); let result = self.inner.try_lock();
let mapper = |guard| TracingMutexGuard { let mapper = |guard| MutexGuard {
_mutex: mutex, _mutex: mutex,
inner: guard, inner: guard,
}; };
@@ -188,13 +155,13 @@ impl<T> TracingMutex<T> {
} }
} }
impl<T> From<T> for TracingMutex<T> { impl<T> From<T> for Mutex<T> {
fn from(t: T) -> Self { fn from(t: T) -> Self {
Self::new(t) Self::new(t)
} }
} }
impl<'a, T> Deref for TracingMutexGuard<'a, T> { impl<'a, T> Deref for MutexGuard<'a, T> {
type Target = T; type Target = T;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@@ -202,28 +169,140 @@ impl<'a, T> Deref for TracingMutexGuard<'a, T> {
} }
} }
impl<'a, T> DerefMut for TracingMutexGuard<'a, T> { impl<'a, T> DerefMut for MutexGuard<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner &mut self.inner
} }
} }
impl<'a, T: fmt::Display> fmt::Display for TracingMutexGuard<'a, T> { impl<'a, T: fmt::Display> fmt::Display for MutexGuard<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f) self.inner.fmt(f)
} }
} }
/// Wrapper around [`std::sync::Condvar`].
///
/// Allows `TracingMutexGuard` to be used with a `Condvar`. Unlike other structs in this module,
/// this wrapper does not add any additional dependency tracking or other overhead on top of the
/// primitive it wraps. All dependency tracking happens through the mutexes itself.
///
/// # Panics
///
/// This struct does not add any panics over the base implementation of `Condvar`, but panics due to
/// dependency tracking may poison associated mutexes.
///
/// # Examples
///
/// ```
/// use std::sync::Arc;
/// use std::thread;
///
/// use tracing_mutex::stdsync::tracing::{Condvar, Mutex};
///
/// let pair = Arc::new((Mutex::new(false), Condvar::new()));
/// let pair2 = Arc::clone(&pair);
///
/// // Spawn a thread that will unlock the condvar
/// thread::spawn(move || {
/// let (lock, condvar) = &*pair2;
/// *lock.lock().unwrap() = true;
/// condvar.notify_one();
/// });
///
/// // Wait until the thread unlocks the condvar
/// let (lock, condvar) = &*pair;
/// let guard = lock.lock().unwrap();
/// let guard = condvar.wait_while(guard, |started| !*started).unwrap();
///
/// // Guard should read true now
/// assert!(*guard);
/// ```
#[derive(Debug, Default)]
pub struct Condvar(sync::Condvar);
impl Condvar {
/// Creates a new condition variable which is ready to be waited on and notified.
pub const fn new() -> Self {
Self(sync::Condvar::new())
}
/// Wrapper for [`std::sync::Condvar::wait`].
pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> LockResult<MutexGuard<'a, T>> {
let MutexGuard { _mutex, inner } = guard;
map_lockresult(self.0.wait(inner), |inner| MutexGuard { _mutex, inner })
}
/// Wrapper for [`std::sync::Condvar::wait_while`].
pub fn wait_while<'a, T, F>(
&self,
guard: MutexGuard<'a, T>,
condition: F,
) -> LockResult<MutexGuard<'a, T>>
where
F: FnMut(&mut T) -> bool,
{
let MutexGuard { _mutex, inner } = guard;
map_lockresult(self.0.wait_while(inner, condition), |inner| MutexGuard {
_mutex,
inner,
})
}
/// Wrapper for [`std::sync::Condvar::wait_timeout`].
pub fn wait_timeout<'a, T>(
&self,
guard: MutexGuard<'a, T>,
dur: Duration,
) -> LockResult<(MutexGuard<'a, T>, WaitTimeoutResult)> {
let MutexGuard { _mutex, inner } = guard;
map_lockresult(self.0.wait_timeout(inner, dur), |(inner, result)| {
(MutexGuard { _mutex, inner }, result)
})
}
/// Wrapper for [`std::sync::Condvar::wait_timeout_while`].
pub fn wait_timeout_while<'a, T, F>(
&self,
guard: MutexGuard<'a, T>,
dur: Duration,
condition: F,
) -> LockResult<(MutexGuard<'a, T>, WaitTimeoutResult)>
where
F: FnMut(&mut T) -> bool,
{
let MutexGuard { _mutex, inner } = guard;
map_lockresult(
self.0.wait_timeout_while(inner, dur, condition),
|(inner, result)| (MutexGuard { _mutex, inner }, result),
)
}
/// Wrapper for [`std::sync::Condvar::notify_one`].
pub fn notify_one(&self) {
self.0.notify_one();
}
/// Wrapper for [`std::sync::Condvar::notify_all`].
pub fn notify_all(&self) {
self.0.notify_all();
}
}
/// Wrapper for [`std::sync::RwLock`]. /// Wrapper for [`std::sync::RwLock`].
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct TracingRwLock<T> { pub struct RwLock<T> {
inner: RwLock<T>, inner: sync::RwLock<T>,
id: MutexId, id: LazyMutexId,
} }
/// Hybrid wrapper for both [`std::sync::RwLockReadGuard`] and [`std::sync::RwLockWriteGuard`]. /// Hybrid wrapper for both [`std::sync::RwLockReadGuard`] and [`std::sync::RwLockWriteGuard`].
/// ///
/// Please refer to [`TracingReadGuard`] and [`TracingWriteGuard`] for usable types. /// Please refer to [`RwLockReadGuard`] and [`RwLockWriteGuard`] for usable types.
#[derive(Debug)] #[derive(Debug)]
pub struct TracingRwLockGuard<'a, L> { pub struct TracingRwLockGuard<'a, L> {
inner: L, inner: L,
@@ -231,15 +310,15 @@ pub struct TracingRwLockGuard<'a, L> {
} }
/// Wrapper around [`std::sync::RwLockReadGuard`]. /// Wrapper around [`std::sync::RwLockReadGuard`].
pub type TracingReadGuard<'a, T> = TracingRwLockGuard<'a, RwLockReadGuard<'a, T>>; pub type RwLockReadGuard<'a, T> = TracingRwLockGuard<'a, sync::RwLockReadGuard<'a, T>>;
/// Wrapper around [`std::sync::RwLockWriteGuard`]. /// Wrapper around [`std::sync::RwLockWriteGuard`].
pub type TracingWriteGuard<'a, T> = TracingRwLockGuard<'a, RwLockWriteGuard<'a, T>>; pub type RwLockWriteGuard<'a, T> = TracingRwLockGuard<'a, sync::RwLockWriteGuard<'a, T>>;
impl<T> TracingRwLock<T> { impl<T> RwLock<T> {
pub fn new(t: T) -> Self { pub const fn new(t: T) -> Self {
Self { Self {
inner: RwLock::new(t), inner: sync::RwLock::new(t),
id: MutexId::new(), id: LazyMutexId::new(),
} }
} }
@@ -250,7 +329,7 @@ impl<T> TracingRwLock<T> {
/// This method participates in lock dependency tracking. If acquiring this lock introduces a /// This method participates in lock dependency tracking. If acquiring this lock introduces a
/// dependency cycle, this method will panic. /// dependency cycle, this method will panic.
#[track_caller] #[track_caller]
pub fn read(&self) -> LockResult<TracingReadGuard<T>> { pub fn read(&self) -> LockResult<RwLockReadGuard<T>> {
let mutex = self.id.get_borrowed(); let mutex = self.id.get_borrowed();
let result = self.inner.read(); let result = self.inner.read();
@@ -267,7 +346,7 @@ impl<T> TracingRwLock<T> {
/// This method participates in lock dependency tracking. If acquiring this lock introduces a /// This method participates in lock dependency tracking. If acquiring this lock introduces a
/// dependency cycle, this method will panic. /// dependency cycle, this method will panic.
#[track_caller] #[track_caller]
pub fn write(&self) -> LockResult<TracingWriteGuard<T>> { pub fn write(&self) -> LockResult<RwLockWriteGuard<T>> {
let mutex = self.id.get_borrowed(); let mutex = self.id.get_borrowed();
let result = self.inner.write(); let result = self.inner.write();
@@ -284,7 +363,7 @@ impl<T> TracingRwLock<T> {
/// This method participates in lock dependency tracking. If acquiring this lock introduces a /// This method participates in lock dependency tracking. If acquiring this lock introduces a
/// dependency cycle, this method will panic. /// dependency cycle, this method will panic.
#[track_caller] #[track_caller]
pub fn try_read(&self) -> TryLockResult<TracingReadGuard<T>> { pub fn try_read(&self) -> TryLockResult<RwLockReadGuard<T>> {
let mutex = self.id.get_borrowed(); let mutex = self.id.get_borrowed();
let result = self.inner.try_read(); let result = self.inner.try_read();
@@ -301,7 +380,7 @@ impl<T> TracingRwLock<T> {
/// This method participates in lock dependency tracking. If acquiring this lock introduces a /// This method participates in lock dependency tracking. If acquiring this lock introduces a
/// dependency cycle, this method will panic. /// dependency cycle, this method will panic.
#[track_caller] #[track_caller]
pub fn try_write(&self) -> TryLockResult<TracingWriteGuard<T>> { pub fn try_write(&self) -> TryLockResult<RwLockWriteGuard<T>> {
let mutex = self.id.get_borrowed(); let mutex = self.id.get_borrowed();
let result = self.inner.try_write(); let result = self.inner.try_write();
@@ -324,7 +403,7 @@ impl<T> TracingRwLock<T> {
} }
} }
impl<T> From<T> for TracingRwLock<T> { impl<T> From<T> for RwLock<T> {
fn from(t: T) -> Self { fn from(t: T) -> Self {
Self::new(t) Self::new(t)
} }
@@ -352,19 +431,19 @@ where
/// Wrapper around [`std::sync::Once`]. /// Wrapper around [`std::sync::Once`].
/// ///
/// 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
/// the one it wraps. /// and the one it wraps.
#[derive(Debug)] #[derive(Debug)]
pub struct TracingOnce { pub struct Once {
inner: Once, inner: sync::Once,
mutex_id: LazyMutexId, mutex_id: LazyMutexId,
} }
impl TracingOnce { impl Once {
/// Create a new `Once` value. /// Create a new `Once` value.
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
inner: Once::new(), inner: sync::Once::new(),
mutex_id: LazyMutexId::new(), mutex_id: LazyMutexId::new(),
} }
} }
@@ -383,7 +462,7 @@ impl TracingOnce {
self.inner.call_once(f); self.inner.call_once(f);
} }
/// Performs the same operation as [`call_once`][TracingOnce::call_once] except it ignores /// Performs the same operation as [`call_once`][Once::call_once] except it ignores
/// poisoning. /// poisoning.
/// ///
/// # Panics /// # Panics
@@ -404,6 +483,142 @@ impl TracingOnce {
} }
} }
/// Wrapper for [`std::sync::OnceLock`]
///
/// The exact locking behaviour of [`std::sync::OnceLock`] is currently undefined, but may
/// deadlock in the event of reentrant initialization attempts. This wrapper participates in
/// cycle detection as normal and will therefore panic in the event of reentrancy.
///
/// Most of this primitive's methods do not involve locking and as such are simply passed
/// through to the inner implementation.
///
/// # Examples
///
/// ```
/// use tracing_mutex::stdsync::tracing::OnceLock;
///
/// static LOCK: OnceLock<i32> = OnceLock::new();
/// assert!(LOCK.get().is_none());
///
/// std::thread::spawn(|| {
/// let value: &i32 = LOCK.get_or_init(|| 42);
/// assert_eq!(value, &42);
/// }).join().unwrap();
///
/// let value: Option<&i32> = LOCK.get();
/// assert_eq!(value, Some(&42));
/// ```
#[derive(Debug)]
pub struct OnceLock<T> {
id: LazyMutexId,
inner: sync::OnceLock<T>,
}
// N.B. this impl inlines everything that directly calls the inner implementation as there
// should be 0 overhead to doing so.
impl<T> OnceLock<T> {
/// Creates a new empty cell
pub const fn new() -> Self {
Self {
id: LazyMutexId::new(),
inner: sync::OnceLock::new(),
}
}
/// Gets a reference to the underlying value.
///
/// This method does not attempt to lock and therefore does not participate in cycle
/// detection.
#[inline]
pub fn get(&self) -> Option<&T> {
self.inner.get()
}
/// Gets a mutable reference to the underlying value.
///
/// This method does not attempt to lock and therefore does not participate in cycle
/// detection.
#[inline]
pub fn get_mut(&mut self) -> Option<&mut T> {
self.inner.get_mut()
}
/// Sets the contents of this cell to the underlying value
///
/// As this method may block until initialization is complete, it participates in cycle
/// detection.
pub fn set(&self, value: T) -> Result<(), T> {
let _guard = self.id.get_borrowed();
self.inner.set(value)
}
/// Gets the contents of the cell, initializing it with `f` if the cell was empty.
///
/// This method participates in cycle detection. Reentrancy is considered a cycle.
pub fn get_or_init<F>(&self, f: F) -> &T
where
F: FnOnce() -> T,
{
let _guard = self.id.get_borrowed();
self.inner.get_or_init(f)
}
/// Takes the value out of this `OnceLock`, moving it back to an uninitialized state.
///
/// This method does not attempt to lock and therefore does not participate in cycle
/// detection.
#[inline]
pub fn take(&mut self) -> Option<T> {
self.inner.take()
}
/// Consumes the `OnceLock`, returning the wrapped value. Returns None if the cell was
/// empty.
///
/// This method does not attempt to lock and therefore does not participate in cycle
/// detection.
#[inline]
pub fn into_inner(mut self) -> Option<T> {
self.take()
}
}
impl<T> Default for OnceLock<T> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<T: PartialEq> PartialEq for OnceLock<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl<T: Eq> Eq for OnceLock<T> {}
impl<T: Clone> Clone for OnceLock<T> {
fn clone(&self) -> Self {
Self {
id: LazyMutexId::new(),
inner: self.inner.clone(),
}
}
}
impl<T> From<T> for OnceLock<T> {
#[inline]
fn from(value: T) -> Self {
Self {
id: LazyMutexId::new(),
inner: sync::OnceLock::from(value),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
@@ -413,7 +628,7 @@ mod tests {
#[test] #[test]
fn test_mutex_usage() { fn test_mutex_usage() {
let mutex = Arc::new(TracingMutex::new(0)); let mutex = Arc::new(Mutex::new(0));
assert_eq!(*mutex.lock().unwrap(), 0); assert_eq!(*mutex.lock().unwrap(), 0);
*mutex.lock().unwrap() = 1; *mutex.lock().unwrap() = 1;
@@ -435,7 +650,7 @@ mod tests {
#[test] #[test]
fn test_rwlock_usage() { fn test_rwlock_usage() {
let rwlock = Arc::new(TracingRwLock::new(0)); let rwlock = Arc::new(RwLock::new(0));
assert_eq!(*rwlock.read().unwrap(), 0); assert_eq!(*rwlock.read().unwrap(), 0);
assert_eq!(*rwlock.write().unwrap(), 0); assert_eq!(*rwlock.write().unwrap(), 0);
@@ -462,7 +677,7 @@ mod tests {
#[test] #[test]
fn test_once_usage() { fn test_once_usage() {
let once = Arc::new(TracingOnce::new()); let once = Arc::new(Once::new());
let once_clone = once.clone(); let once_clone = once.clone();
assert!(!once.is_completed()); assert!(!once.is_completed());
@@ -483,8 +698,8 @@ mod tests {
#[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() {
let a = TracingMutex::new(()); let a = Mutex::new(());
let b = TracingMutex::new(()); let b = Mutex::new(());
let hold_a = a.lock().unwrap(); let hold_a = a.lock().unwrap();
let _ = b.lock(); let _ = b.lock();
@@ -495,3 +710,4 @@ mod tests {
let _ = a.lock(); let _ = a.lock();
} }
} }
}