26 Commits

Author SHA1 Message Date
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 765 additions and 658 deletions

View File

@@ -2,12 +2,14 @@ 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:
@@ -47,3 +49,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,29 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [Unreleased]
## [0.2.0] ### 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 +77,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"
@@ -11,10 +11,16 @@ 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"
[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" 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

@@ -34,7 +34,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 +42,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 +59,23 @@ 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.
### 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];

5
bors.toml Normal file
View File

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

View File

@@ -41,10 +41,11 @@
//! //!
//! 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::cell::UnsafeCell;
use std::fmt; use std::fmt;
@@ -56,21 +57,26 @@ 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::MutexGuard;
use std::sync::Once; use std::sync::Once;
use std::sync::PoisonError; use std::sync::PoisonError;
use lazy_static::lazy_static; 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;
@@ -129,7 +135,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.
@@ -269,8 +278,22 @@ impl Drop for LazyMutexId {
} }
} }
/// 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,7 +304,7 @@ 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() };
} }
} }

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;
use crate::lockapi::TracingWrapper; pub use parking_lot as raw;
use crate::LazyMutexId;
macro_rules! debug_variant { #[cfg(debug_assertions)]
($debug_name:ident, $tracing_name:ident, $normal_name:ty) => { pub use tracing::{
type $tracing_name = TracingWrapper<$normal_name>; FairMutex, FairMutexGuard, MappedFairMutexGuard, MappedMutexGuard, MappedReentrantMutexGuard,
MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, MutexGuard, Once, OnceState,
ReentrantMutex, ReentrantMutexGuard, RwLock, RwLockReadGuard, RwLockUpgradableReadGuard,
RwLockWriteGuard,
};
#[cfg(debug_assertions)] #[cfg(not(debug_assertions))]
type $debug_name = TracingWrapper<$normal_name>; pub use parking_lot::{
#[cfg(not(debug_assertions))] FairMutex, FairMutexGuard, MappedFairMutexGuard, MappedMutexGuard, MappedReentrantMutexGuard,
type $debug_name = $normal_name; MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, MutexGuard, Once, OnceState,
}; ReentrantMutex, ReentrantMutexGuard, RwLock, RwLockReadGuard, RwLockUpgradableReadGuard,
} RwLockWriteGuard,
};
debug_variant!( /// Dependency tracing wrappers for [`parking_lot`].
DebugRawFairMutex, pub mod tracing {
TracingRawFairMutex, pub use parking_lot::OnceState;
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`]. use crate::lockapi::TracingWrapper;
pub type TracingFairMutex<T> = lock_api::Mutex<TracingRawFairMutex, T>; use crate::LazyMutexId;
/// Mutex guard for [`TracingFairMutex`].
pub type TracingFairMutexGuard<'a, T> = lock_api::MutexGuard<'a, TracingRawFairMutex, T>;
/// RAII guard for `TracingFairMutexGuard::map`.
pub type TracingMappedFairMutexGuard<'a, 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`]. type RawFairMutex = TracingWrapper<parking_lot::RawFairMutex>;
pub type TracingMutex<T> = lock_api::Mutex<TracingRawMutex, T>; type RawMutex = TracingWrapper<parking_lot::RawMutex>;
/// Mutex guard for [`TracingMutex`]. type RawRwLock = TracingWrapper<parking_lot::RawRwLock>;
pub type TracingMutexGuard<'a, T> = lock_api::MutexGuard<'a, TracingRawMutex, T>;
/// RAII guard for `TracingMutexGuard::map`.
pub type TracingMappedMutexGuard<'a, T> = lock_api::MappedMutexGuard<'a, TracingRawMutex, 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 fair mutex. See: [`parking_lot::FairMutex`].
/// pub type FairMutex<T> = lock_api::Mutex<RawFairMutex, T>;
/// **Note:** due to the way dependencies are tracked, this mutex can only be acquired directly /// Mutex guard for [`FairMutex`].
/// after itself. Acquiring any other mutex in between introduces a dependency cycle, and will pub type FairMutexGuard<'a, T> = lock_api::MutexGuard<'a, RawFairMutex, T>;
/// therefore be rejected. /// RAII guard for [`FairMutexGuard::map`].
pub type TracingReentrantMutex<T> = pub type MappedFairMutexGuard<'a, T> = lock_api::MappedMutexGuard<'a, RawFairMutex, T>;
lock_api::ReentrantMutex<TracingWrapper<parking_lot::RawMutex>, parking_lot::RawThreadId, T>;
/// Mutex guard for [`TracingReentrantMutex`].
pub type TracingReentrantMutexGuard<'a, T> = lock_api::ReentrantMutexGuard<
'a,
TracingWrapper<parking_lot::RawMutex>,
parking_lot::RawThreadId,
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. /// Dependency tracking mutex. See: [`parking_lot::Mutex`].
/// pub type Mutex<T> = lock_api::Mutex<RawMutex, T>;
/// If debug assertions are enabled this resolves to [`TracingReentrantMutex`] and to /// Mutex guard for [`Mutex`].
/// [`parking_lot::ReentrantMutex`] otherwise. pub type MutexGuard<'a, T> = lock_api::MutexGuard<'a, RawMutex, T>;
pub type DebugReentrantMutex<T> = /// RAII guard for [`MutexGuard::map`].
lock_api::ReentrantMutex<DebugRawMutex, parking_lot::RawThreadId, T>; pub type MappedMutexGuard<'a, T> = lock_api::MappedMutexGuard<'a, RawMutex, 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 reentrant mutex. See: [`parking_lot::ReentrantMutex`].
pub type TracingRwLock<T> = lock_api::RwLock<TracingRawRwLock, T>; ///
/// Read guard for [`TracingRwLock`]. /// **Note:** due to the way dependencies are tracked, this mutex can only be acquired directly
pub type TracingRwLockReadGuard<'a, T> = lock_api::RwLockReadGuard<'a, TracingRawRwLock, T>; /// after itself. Acquiring any other mutex in between introduces a dependency cycle, and will
/// Upgradable Read guard for [`TracingRwLock`]. /// therefore be rejected.
pub type TracingRwLockUpgradableReadGuard<'a, T> = pub type ReentrantMutex<T> = lock_api::ReentrantMutex<RawMutex, parking_lot::RawThreadId, T>;
lock_api::RwLockUpgradableReadGuard<'a, TracingRawRwLock, T>; /// Mutex guard for [`ReentrantMutex`].
/// Write guard for [`TracingRwLock`]. pub type ReentrantMutexGuard<'a, T> =
pub type TracingRwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, TracingRawRwLock, T>; lock_api::ReentrantMutexGuard<'a, RawMutex, parking_lot::RawThreadId, T>;
/// RAII guard for `TracingRwLockReadGuard::map`. /// RAII guard for `ReentrantMutexGuard::map`.
pub type TracingMappedRwLockReadGuard<'a, T> = pub type MappedReentrantMutexGuard<'a, T> =
lock_api::MappedRwLockReadGuard<'a, TracingRawRwLock, T>; lock_api::MappedReentrantMutexGuard<'a, RawMutex, parking_lot::RawThreadId, T>;
/// RAII guard for `TracingRwLockWriteGuard::map`.
pub type TracingMappedRwLockWriteGuard<'a, T> =
lock_api::MappedRwLockWriteGuard<'a, TracingRawRwLock, T>;
/// Debug-only dependency tracking RwLock. /// Dependency tracking RwLock. See: [`parking_lot::RwLock`].
/// pub type RwLock<T> = lock_api::RwLock<RawRwLock, T>;
/// If debug assertions are enabled this resolved to [`TracingRwLock`] and to /// Read guard for [`RwLock`].
/// [`parking_lot::RwLock`] otherwise. pub type RwLockReadGuard<'a, T> = lock_api::RwLockReadGuard<'a, RawRwLock, T>;
pub type DebugRwLock<T> = lock_api::RwLock<DebugRawRwLock, T>; /// Upgradable Read guard for [`RwLock`].
/// Read guard for [`TracingRwLock`]. pub type RwLockUpgradableReadGuard<'a, T> =
pub type DebugRwLockReadGuard<'a, T> = lock_api::RwLockReadGuard<'a, DebugRawRwLock, T>; lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>;
/// Upgradable Read guard for [`TracingRwLock`]. /// Write guard for [`RwLock`].
pub type DebugRwLockUpgradableReadGuard<'a, T> = pub type RwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, RawRwLock, T>;
lock_api::RwLockUpgradableReadGuard<'a, DebugRawRwLock, T>; /// RAII guard for `RwLockReadGuard::map`.
/// Write guard for [`TracingRwLock`]. pub type MappedRwLockReadGuard<'a, T> = lock_api::MappedRwLockReadGuard<'a, RawRwLock, T>;
pub type DebugRwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, DebugRawRwLock, T>; /// RAII guard for `RwLockWriteGuard::map`.
/// RAII guard for `DebugRwLockReadGuard::map`. pub type MappedRwLockWriteGuard<'a, T> = lock_api::MappedRwLockWriteGuard<'a, RawRwLock, T>;
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,119 +1,83 @@
//! 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();
//! ``` //! ```
use std::fmt; pub use std::sync as raw;
use std::ops::Deref;
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;
use std::sync::RwLockWriteGuard;
use std::sync::TryLockError;
use std::sync::TryLockResult;
use crate::BorrowedMutex;
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))] #[cfg(not(debug_assertions))]
pub type DebugMutex<T> = Mutex<T>; pub use std::sync::{Condvar, Mutex, MutexGuard, Once, RwLock, RwLockReadGuard, RwLockWriteGuard};
/// Mutex guard for [`DebugMutex`].
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
pub type DebugMutexGuard<'a, T> = TracingMutexGuard<'a, T>; pub use tracing::{Condvar, Mutex, MutexGuard, Once, RwLock, RwLockReadGuard, RwLockWriteGuard};
#[cfg(not(debug_assertions))]
pub type DebugMutexGuard<'a, T> = MutexGuard<'a, T>;
/// Debug-only tracing `RwLock`. /// Dependency tracing versions of [`std::sync`].
/// pub mod tracing {
/// Type alias that resolves to [`TracingRwLock`] when debug assertions are enabled and to use std::fmt;
/// [`std::sync::RwLock`] when they're not. Use this if you want to have the benefits of cycle use std::ops::Deref;
/// detection in development but do not want to pay the performance penalty in release. use std::ops::DerefMut;
#[cfg(debug_assertions)] use std::sync;
pub type DebugRwLock<T> = TracingRwLock<T>; use std::sync::LockResult;
#[cfg(not(debug_assertions))] use std::sync::OnceState;
pub type DebugRwLock<T> = RwLock<T>; use std::sync::PoisonError;
use std::sync::TryLockError;
use std::sync::TryLockResult;
use std::sync::WaitTimeoutResult;
use std::time::Duration;
/// Read guard for [`DebugRwLock`]. use crate::BorrowedMutex;
#[cfg(debug_assertions)] use crate::LazyMutexId;
pub type DebugReadGuard<'a, T> = TracingReadGuard<'a, T>; use crate::MutexId;
#[cfg(not(debug_assertions))]
pub type DebugReadGuard<'a, T> = RwLockReadGuard<'a, T>;
/// Write guard for [`DebugRwLock`]. /// Wrapper for [`std::sync::Mutex`].
#[cfg(debug_assertions)] ///
pub type DebugWriteGuard<'a, T> = TracingWriteGuard<'a, T>; /// Refer to the [crate-level][`crate`] documentation for the differences between this struct and
#[cfg(not(debug_assertions))] /// the one it wraps.
pub type DebugWriteGuard<'a, T> = RwLockWriteGuard<'a, T>; #[derive(Debug, Default)]
pub struct Mutex<T> {
/// Debug-only tracing `Once`. inner: sync::Mutex<T>,
///
/// 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
/// the one it wraps.
#[derive(Debug, Default)]
pub struct TracingMutex<T> {
inner: Mutex<T>,
id: MutexId, id: MutexId,
} }
/// 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>,
} }
fn map_lockresult<T, I, F>(result: LockResult<I>, mapper: F) -> LockResult<T> fn map_lockresult<T, I, F>(result: LockResult<I>, mapper: F) -> LockResult<T>
where where
F: FnOnce(I) -> T, F: FnOnce(I) -> T,
{ {
match result { match result {
Ok(inner) => Ok(mapper(inner)), Ok(inner) => Ok(mapper(inner)),
Err(poisoned) => Err(PoisonError::new(mapper(poisoned.into_inner()))), Err(poisoned) => Err(PoisonError::new(mapper(poisoned.into_inner()))),
} }
} }
fn map_trylockresult<T, I, F>(result: TryLockResult<I>, mapper: F) -> TryLockResult<T> fn map_trylockresult<T, I, F>(result: TryLockResult<I>, mapper: F) -> TryLockResult<T>
where where
F: FnOnce(I) -> T, F: FnOnce(I) -> T,
{ {
match result { match result {
Ok(inner) => Ok(mapper(inner)), Ok(inner) => Ok(mapper(inner)),
Err(TryLockError::WouldBlock) => Err(TryLockError::WouldBlock), Err(TryLockError::WouldBlock) => Err(TryLockError::WouldBlock),
@@ -121,13 +85,13 @@ where
Err(PoisonError::new(mapper(poisoned.into_inner())).into()) Err(PoisonError::new(mapper(poisoned.into_inner())).into())
} }
} }
} }
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 fn new(t: T) -> Self {
Self { Self {
inner: Mutex::new(t), inner: sync::Mutex::new(t),
id: MutexId::new(), id: MutexId::new(),
} }
} }
@@ -139,11 +103,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 +122,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,
}; };
@@ -186,59 +150,171 @@ impl<T> TracingMutex<T> {
pub fn into_inner(self) -> LockResult<T> { pub fn into_inner(self) -> LockResult<T> {
self.inner.into_inner() self.inner.into_inner()
} }
} }
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 {
&self.inner &self.inner
} }
} }
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 for [`std::sync::RwLock`]. /// Wrapper around [`std::sync::Condvar`].
#[derive(Debug, Default)] ///
pub struct TracingRwLock<T> { /// Allows `TracingMutexGuard` to be used with a `Condvar`. Unlike other structs in this module,
inner: RwLock<T>, /// 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 fn new() -> Self {
Default::default()
}
/// 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`].
#[derive(Debug, Default)]
pub struct RwLock<T> {
inner: sync::RwLock<T>,
id: MutexId, id: MutexId,
} }
/// 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,
_mutex: BorrowedMutex<'a>, _mutex: BorrowedMutex<'a>,
} }
/// 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 fn new(t: T) -> Self {
Self { Self {
inner: RwLock::new(t), inner: sync::RwLock::new(t),
id: MutexId::new(), id: MutexId::new(),
} }
} }
@@ -250,7 +326,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 +343,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 +360,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 +377,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();
@@ -322,49 +398,49 @@ impl<T> TracingRwLock<T> {
pub fn into_inner(self) -> LockResult<T> { pub fn into_inner(self) -> LockResult<T> {
self.inner.into_inner() self.inner.into_inner()
} }
} }
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)
} }
} }
impl<'a, L, T> Deref for TracingRwLockGuard<'a, L> impl<'a, L, T> Deref for TracingRwLockGuard<'a, L>
where where
L: Deref<Target = T>, L: Deref<Target = T>,
{ {
type Target = T; type Target = T;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
self.inner.deref() self.inner.deref()
} }
} }
impl<'a, T, L> DerefMut for TracingRwLockGuard<'a, L> impl<'a, T, L> DerefMut for TracingRwLockGuard<'a, L>
where where
L: Deref<Target = T> + DerefMut, L: Deref<Target = T> + DerefMut,
{ {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
self.inner.deref_mut() self.inner.deref_mut()
} }
} }
/// 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 and
/// the one it wraps. /// 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 +459,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
@@ -402,10 +478,10 @@ impl TracingOnce {
pub fn is_completed(&self) -> bool { pub fn is_completed(&self) -> bool {
self.inner.is_completed() self.inner.is_completed()
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
@@ -413,7 +489,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 +511,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 +538,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 +559,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();
@@ -494,4 +570,5 @@ mod tests {
let _hold_b = b.lock().unwrap(); let _hold_b = b.lock().unwrap();
let _ = a.lock(); let _ = a.lock();
} }
}
} }