10 Commits

Author SHA1 Message Date
4666923972 Merge pull request #41 from bertptrs/37-make-parking_lot-api-consistent-so-importing-from-toml-works-seamlessly
Complete `parking_lot` API wrappers
2025-01-18 20:03:01 +01:00
7ae0bd3989 Add example showcasing how to replace parking_lot 2025-01-18 20:00:00 +01:00
0f201989e0 Add missing functions to parking_lot API wrappers 2025-01-18 19:57:32 +01:00
3dd73fa7ff Merge pull request #40 from bertptrs/dependabot
Introduce dependabot
2025-01-18 12:48:37 +01:00
914320a91c Update cargo dependencies
This should fix a "security issue"
2025-01-18 12:45:38 +01:00
095279aa62 Auto-update github actions packages 2025-01-18 12:39:14 +01:00
d93879fcf2 Merge pull request #38 from bertptrs/lazylock
Add support for LazyLock
2025-01-18 12:30:56 +01:00
4b17c45a4a Update docs 2025-01-18 12:29:38 +01:00
7a943ebba6 Fix clippy issues 2025-01-18 12:11:26 +01:00
25ae542ade Add support for LazyLock 2025-01-18 11:59:18 +01:00
12 changed files with 508 additions and 234 deletions

7
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
---
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -6,6 +6,15 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [Unreleased]
### Added
- On Rust 1.80 or newer, a wrapper for `std::sync::LazyLock` is now available. The MSRV has not been
changed; older versions simply don't get this wrapper.
- Added missing const-initialisation wrappers for `parking_lot`. The API interface for
`tracing_mutex::parkinglot` is now identical to `parking_lot`, and an example showing how to use
it as a drop-in replacement was added.
### Changed ### Changed
- Reworked CI to better test continued support for the minimum supported Rust version - Reworked CI to better test continued support for the minimum supported Rust version

138
Cargo.lock generated
View File

@@ -25,9 +25,9 @@ checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
@@ -53,15 +53,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@@ -207,23 +198,12 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.2" version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [ dependencies = [
"errno-dragonfly",
"libc",
"windows-sys",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc", "libc",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -257,7 +237,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [ dependencies = [
"hermit-abi", "hermit-abi",
"rustix", "rustix",
"windows-sys", "windows-sys 0.48.0",
] ]
[[package]] [[package]]
@@ -286,15 +266,15 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.147" version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.5" version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@@ -378,7 +358,7 @@ dependencies = [
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"windows-targets", "windows-targets 0.48.5",
] ]
[[package]] [[package]]
@@ -525,15 +505,15 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.9" version = "0.38.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49" checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
dependencies = [ dependencies = [
"bitflags 2.4.0", "bitflags 2.4.0",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -619,6 +599,7 @@ dependencies = [
name = "tracing-mutex" name = "tracing-mutex"
version = "0.3.0" version = "0.3.0"
dependencies = [ dependencies = [
"autocfg",
"criterion", "criterion",
"lock_api", "lock_api",
"parking_lot", "parking_lot",
@@ -748,7 +729,16 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
] ]
[[package]] [[package]]
@@ -757,13 +747,29 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc", "windows_aarch64_msvc 0.48.5",
"windows_i686_gnu", "windows_i686_gnu 0.48.5",
"windows_i686_msvc", "windows_i686_msvc 0.48.5",
"windows_x86_64_gnu", "windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm", "windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc", "windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
] ]
[[package]] [[package]]
@@ -772,38 +778,86 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.48.5" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.48.5" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.48.5" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.48.5" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.48.5" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.48.5" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

View File

@@ -30,9 +30,16 @@ rand = "0.8"
name = "mutex" name = "mutex"
harness = false harness = false
[[example]]
name = "drop_in_parking_lot"
required-features = ["parkinglot"]
[features] [features]
default = ["backtraces"] default = ["backtraces"]
backtraces = [] backtraces = []
# Feature names do not match crate names pending namespaced features. # Feature names do not match crate names pending namespaced features.
lockapi = ["lock_api"] lockapi = ["lock_api"]
parkinglot = ["parking_lot", "lockapi"] parkinglot = ["parking_lot", "lockapi"]
[build-dependencies]
autocfg = "1.4.0"

View File

@@ -36,7 +36,7 @@ Add this dependency to your `Cargo.lock` file like any other:
```toml ```toml
[dependencies] [dependencies]
tracing-mutex = "0.2" tracing-mutex = "0.3"
``` ```
Then use the locks provided by this library instead of the ones you would use otherwise. Then use the locks provided by this library instead of the ones you would use otherwise.

10
build.rs Normal file
View File

@@ -0,0 +1,10 @@
use autocfg::AutoCfg;
fn main() {
// To avoid bumping MSRV unnecessarily, we can sniff certain features. Reevaluate this on major
// releases.
let ac = AutoCfg::new().unwrap();
ac.emit_has_path("std::sync::LazyLock");
autocfg::rerun_path("build.rs");
}

View File

@@ -0,0 +1,32 @@
//! This example shows how you can use the [`tracing-mutex`] crate as a drop-in replacement for the
//! parking_lot crate. By default, `tracing-mutex` offers a set of type aliases that allows you to
//! use cycle-checking in development, and raw primitives in release mode, but this way, you can
//! even remove the dependency altogether, or hide it behind a feature.
//!
//! You can use whatever conditional compilation makes sense in context.
use std::sync::Arc;
#[cfg(not(debug_assertions))]
use parking_lot;
#[cfg(debug_assertions)]
// Note: specifically use the `tracing` module, because at this point we are very sure we want to do
// deadlock tracing, so no need to use the automatic selection.
use tracing_mutex::parkinglot::tracing as parking_lot;
fn main() {
let mutex = Arc::new(parking_lot::const_mutex(0));
let handles: Vec<_> = (0..42)
.map(|_| {
let mutex = Arc::clone(&mutex);
std::thread::spawn(move || *mutex.lock() += 1)
})
.collect();
handles
.into_iter()
.for_each(|handle| handle.join().unwrap());
assert_eq!(*mutex.lock(), 42);
}

View File

@@ -193,6 +193,14 @@ impl MutexId {
unreachable!("Tried to drop lock for mutex {:?} but it wasn't held", self) unreachable!("Tried to drop lock for mutex {:?} but it wasn't held", self)
}); });
} }
/// Execute the given closure while the guard is held.
pub fn with_held<T>(&self, f: impl FnOnce() -> T) -> T {
// Note: we MUST construct the RAII guard, we cannot simply mark held + mark released, as
// f() may panic and corrupt our state.
let _guard = self.get_borrowed();
f()
}
} }
impl Default for MutexId { impl Default for MutexId {

View File

@@ -41,196 +41,26 @@
pub use parking_lot as raw; pub use parking_lot as raw;
pub use parking_lot::OnceState;
pub use parking_lot::RawThreadId;
pub use parking_lot::WaitTimeoutResult;
pub mod tracing;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
pub use tracing::{ pub use tracing::{
FairMutex, FairMutexGuard, MappedFairMutexGuard, MappedMutexGuard, MappedReentrantMutexGuard, const_fair_mutex, const_mutex, const_reentrant_mutex, const_rwlock, FairMutex, FairMutexGuard,
MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, MutexGuard, Once, OnceState, MappedFairMutexGuard, MappedMutexGuard, MappedReentrantMutexGuard, MappedRwLockReadGuard,
MappedRwLockWriteGuard, Mutex, MutexGuard, Once, RawFairMutex, RawMutex, RawRwLock,
ReentrantMutex, ReentrantMutexGuard, RwLock, RwLockReadGuard, RwLockUpgradableReadGuard, ReentrantMutex, ReentrantMutexGuard, RwLock, RwLockReadGuard, RwLockUpgradableReadGuard,
RwLockWriteGuard, RwLockWriteGuard,
}; };
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
pub use parking_lot::{ pub use parking_lot::{
FairMutex, FairMutexGuard, MappedFairMutexGuard, MappedMutexGuard, MappedReentrantMutexGuard, const_fair_mutex, const_mutex, const_reentrant_mutex, const_rwlock, FairMutex, FairMutexGuard,
MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, MutexGuard, Once, OnceState, MappedFairMutexGuard, MappedMutexGuard, MappedReentrantMutexGuard, MappedRwLockReadGuard,
MappedRwLockWriteGuard, Mutex, MutexGuard, Once, RawFairMutex, RawMutex, RawRwLock,
ReentrantMutex, ReentrantMutexGuard, RwLock, RwLockReadGuard, RwLockUpgradableReadGuard, ReentrantMutex, ReentrantMutexGuard, RwLock, RwLockReadGuard, RwLockUpgradableReadGuard,
RwLockWriteGuard, RwLockWriteGuard,
}; };
/// Dependency tracing wrappers for [`parking_lot`].
pub mod tracing {
pub use parking_lot::OnceState;
use crate::lockapi::TracingWrapper;
use crate::LazyMutexId;
type RawFairMutex = TracingWrapper<parking_lot::RawFairMutex>;
type RawMutex = TracingWrapper<parking_lot::RawMutex>;
type RawRwLock = TracingWrapper<parking_lot::RawRwLock>;
/// Dependency tracking fair mutex. See: [`parking_lot::FairMutex`].
pub type FairMutex<T> = lock_api::Mutex<RawFairMutex, T>;
/// Mutex guard for [`FairMutex`].
pub type FairMutexGuard<'a, T> = lock_api::MutexGuard<'a, RawFairMutex, T>;
/// RAII guard for [`FairMutexGuard::map`].
pub type MappedFairMutexGuard<'a, T> = lock_api::MappedMutexGuard<'a, RawFairMutex, T>;
/// Dependency tracking mutex. See: [`parking_lot::Mutex`].
pub type Mutex<T> = lock_api::Mutex<RawMutex, T>;
/// Mutex guard for [`Mutex`].
pub type MutexGuard<'a, T> = lock_api::MutexGuard<'a, RawMutex, T>;
/// RAII guard for [`MutexGuard::map`].
pub type MappedMutexGuard<'a, T> = lock_api::MappedMutexGuard<'a, RawMutex, T>;
/// Dependency tracking reentrant mutex. See: [`parking_lot::ReentrantMutex`].
///
/// **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
/// therefore be rejected.
pub type ReentrantMutex<T> = lock_api::ReentrantMutex<RawMutex, parking_lot::RawThreadId, T>;
/// Mutex guard for [`ReentrantMutex`].
pub type ReentrantMutexGuard<'a, T> =
lock_api::ReentrantMutexGuard<'a, RawMutex, parking_lot::RawThreadId, T>;
/// RAII guard for `ReentrantMutexGuard::map`.
pub type MappedReentrantMutexGuard<'a, T> =
lock_api::MappedReentrantMutexGuard<'a, RawMutex, parking_lot::RawThreadId, T>;
/// Dependency tracking RwLock. See: [`parking_lot::RwLock`].
pub type RwLock<T> = lock_api::RwLock<RawRwLock, T>;
/// Read guard for [`RwLock`].
pub type RwLockReadGuard<'a, T> = lock_api::RwLockReadGuard<'a, RawRwLock, T>;
/// Upgradable Read guard for [`RwLock`].
pub type RwLockUpgradableReadGuard<'a, T> =
lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>;
/// Write guard for [`RwLock`].
pub type RwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, RawRwLock, T>;
/// RAII guard for `RwLockReadGuard::map`.
pub type MappedRwLockReadGuard<'a, T> = lock_api::MappedRwLockReadGuard<'a, RawRwLock, T>;
/// RAII guard for `RwLockWriteGuard::map`.
pub type MappedRwLockWriteGuard<'a, T> = lock_api::MappedRwLockWriteGuard<'a, RawRwLock, T>;
/// A dependency-tracking wrapper for [`parking_lot::Once`].
#[derive(Debug, Default)]
pub struct Once {
inner: parking_lot::Once,
id: LazyMutexId,
}
impl Once {
/// Create a new `Once` value.
pub const fn new() -> Self {
Self {
inner: parking_lot::Once::new(),
id: LazyMutexId::new(),
}
}
/// Returns the current state of this `Once`.
pub fn state(&self) -> OnceState {
self.inner.state()
}
/// This call is considered as "locking this `Once`" and it participates in dependency
/// tracking as such.
///
/// # Panics
///
/// This method will panic if `f` panics, poisoning this `Once`. In addition, this function
/// panics when the lock acquisition order is determined to be inconsistent.
pub fn call_once(&self, f: impl FnOnce()) {
let _borrow = self.id.get_borrowed();
self.inner.call_once(f);
}
/// Performs the given initialization routine once and only once.
///
/// This method is identical to [`Once::call_once`] except it ignores poisoning.
pub fn call_once_force(&self, f: impl FnOnce(OnceState)) {
let _borrow = self.id.get_borrowed();
self.inner.call_once_force(f);
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::thread;
use super::tracing;
#[test]
fn test_mutex_usage() {
let mutex = Arc::new(tracing::Mutex::new(()));
let local_lock = mutex.lock();
drop(local_lock);
thread::spawn(move || {
let _remote_lock = mutex.lock();
})
.join()
.unwrap();
}
#[test]
#[should_panic]
fn test_mutex_conflict() {
let mutexes = [
tracing::Mutex::new(()),
tracing::Mutex::new(()),
tracing::Mutex::new(()),
];
for i in 0..3 {
let _first_lock = mutexes[i].lock();
let _second_lock = mutexes[(i + 1) % 3].lock();
}
}
#[test]
fn test_rwlock_usage() {
let lock = Arc::new(tracing::RwLock::new(()));
let lock2 = Arc::clone(&lock);
let _read_lock = lock.read();
// Should be able to acquire lock in the background
thread::spawn(move || {
let _read_lock = lock2.read();
})
.join()
.unwrap();
}
#[test]
fn test_rwlock_upgradable_read_usage() {
let lock = tracing::RwLock::new(());
// Should be able to acquire an upgradable read lock.
let upgradable_guard: tracing::RwLockUpgradableReadGuard<'_, _> = lock.upgradable_read();
// Should be able to upgrade the guard.
let _write_guard: tracing::RwLockWriteGuard<'_, _> =
tracing::RwLockUpgradableReadGuard::upgrade(upgradable_guard);
}
#[test]
fn test_once_usage() {
let once = Arc::new(tracing::Once::new());
let once_clone = once.clone();
assert!(!once_clone.state().done());
let handle = thread::spawn(move || {
assert!(!once_clone.state().done());
once_clone.call_once(|| {});
assert!(once_clone.state().done());
});
handle.join().unwrap();
assert!(once.state().done());
}
}

193
src/parkinglot/tracing.rs Normal file
View File

@@ -0,0 +1,193 @@
//! Dependency tracing wrappers for [`::parking_lot`].
pub use parking_lot::OnceState;
pub use parking_lot::RawThreadId;
use crate::lockapi::TracingWrapper;
use crate::LazyMutexId;
pub type RawFairMutex = TracingWrapper<::parking_lot::RawFairMutex>;
pub type RawMutex = TracingWrapper<::parking_lot::RawMutex>;
pub type RawRwLock = TracingWrapper<::parking_lot::RawRwLock>;
/// Dependency tracking fair mutex. See: [`::parking_lot::FairMutex`].
pub type FairMutex<T> = lock_api::Mutex<RawFairMutex, T>;
/// Mutex guard for [`FairMutex`].
pub type FairMutexGuard<'a, T> = lock_api::MutexGuard<'a, RawFairMutex, T>;
/// RAII guard for [`FairMutexGuard::map`].
pub type MappedFairMutexGuard<'a, T> = lock_api::MappedMutexGuard<'a, RawFairMutex, T>;
/// Dependency tracking mutex. See: [`::parking_lot::Mutex`].
pub type Mutex<T> = lock_api::Mutex<RawMutex, T>;
/// Mutex guard for [`Mutex`].
pub type MutexGuard<'a, T> = lock_api::MutexGuard<'a, RawMutex, T>;
/// RAII guard for [`MutexGuard::map`].
pub type MappedMutexGuard<'a, T> = lock_api::MappedMutexGuard<'a, RawMutex, T>;
/// Dependency tracking reentrant mutex. See: [`::parking_lot::ReentrantMutex`].
///
/// **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
/// therefore be rejected.
pub type ReentrantMutex<T> = lock_api::ReentrantMutex<RawMutex, parking_lot::RawThreadId, T>;
/// Mutex guard for [`ReentrantMutex`].
pub type ReentrantMutexGuard<'a, T> =
lock_api::ReentrantMutexGuard<'a, RawMutex, parking_lot::RawThreadId, T>;
/// RAII guard for `ReentrantMutexGuard::map`.
pub type MappedReentrantMutexGuard<'a, T> =
lock_api::MappedReentrantMutexGuard<'a, RawMutex, parking_lot::RawThreadId, T>;
/// Dependency tracking RwLock. See: [`::parking_lot::RwLock`].
pub type RwLock<T> = lock_api::RwLock<RawRwLock, T>;
/// Read guard for [`RwLock`].
pub type RwLockReadGuard<'a, T> = lock_api::RwLockReadGuard<'a, RawRwLock, T>;
/// Upgradable Read guard for [`RwLock`].
pub type RwLockUpgradableReadGuard<'a, T> = lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>;
/// Write guard for [`RwLock`].
pub type RwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, RawRwLock, T>;
/// RAII guard for `RwLockReadGuard::map`.
pub type MappedRwLockReadGuard<'a, T> = lock_api::MappedRwLockReadGuard<'a, RawRwLock, T>;
/// RAII guard for `RwLockWriteGuard::map`.
pub type MappedRwLockWriteGuard<'a, T> = lock_api::MappedRwLockWriteGuard<'a, RawRwLock, T>;
/// A dependency-tracking wrapper for [`::parking_lot::Once`].
#[derive(Debug, Default)]
pub struct Once {
inner: ::parking_lot::Once,
id: LazyMutexId,
}
impl Once {
/// Create a new `Once` value.
pub const fn new() -> Self {
Self {
inner: ::parking_lot::Once::new(),
id: LazyMutexId::new(),
}
}
/// Returns the current state of this `Once`.
pub fn state(&self) -> OnceState {
self.inner.state()
}
/// This call is considered as "locking this `Once`" and it participates in dependency
/// tracking as such.
///
/// # Panics
///
/// This method will panic if `f` panics, poisoning this `Once`. In addition, this function
/// panics when the lock acquisition order is determined to be inconsistent.
pub fn call_once(&self, f: impl FnOnce()) {
self.id.with_held(|| self.inner.call_once(f));
}
/// Performs the given initialization routine once and only once.
///
/// This method is identical to [`Once::call_once`] except it ignores poisoning.
pub fn call_once_force(&self, f: impl FnOnce(OnceState)) {
self.id.with_held(|| self.inner.call_once_force(f));
}
}
/// Creates a new fair mutex in an unlocked state ready for use.
pub const fn const_fair_mutex<T>(val: T) -> FairMutex<T> {
FairMutex::const_new(<RawFairMutex as lock_api::RawMutex>::INIT, val)
}
/// Creates a new mutex in an unlocked state ready for use.
pub const fn const_mutex<T>(val: T) -> Mutex<T> {
Mutex::const_new(<RawMutex as lock_api::RawMutex>::INIT, val)
}
/// Creates a new reentrant mutex in an unlocked state ready for use.
pub const fn const_reentrant_mutex<T>(val: T) -> ReentrantMutex<T> {
ReentrantMutex::const_new(
<RawMutex as lock_api::RawMutex>::INIT,
<RawThreadId as lock_api::GetThreadId>::INIT,
val,
)
}
/// Creates a new rwlock in an unlocked state ready for use.
pub const fn const_rwlock<T>(val: T) -> RwLock<T> {
RwLock::const_new(<RawRwLock as lock_api::RawRwLock>::INIT, val)
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::thread;
use super::*;
#[test]
fn test_mutex_usage() {
let mutex = Arc::new(Mutex::new(()));
let local_lock = mutex.lock();
drop(local_lock);
thread::spawn(move || {
let _remote_lock = mutex.lock();
})
.join()
.unwrap();
}
#[test]
#[should_panic]
fn test_mutex_conflict() {
let mutexes = [Mutex::new(()), Mutex::new(()), Mutex::new(())];
for i in 0..3 {
let _first_lock = mutexes[i].lock();
let _second_lock = mutexes[(i + 1) % 3].lock();
}
}
#[test]
fn test_rwlock_usage() {
let lock = Arc::new(RwLock::new(()));
let lock2 = Arc::clone(&lock);
let _read_lock = lock.read();
// Should be able to acquire lock in the background
thread::spawn(move || {
let _read_lock = lock2.read();
})
.join()
.unwrap();
}
#[test]
fn test_rwlock_upgradable_read_usage() {
let lock = RwLock::new(());
// Should be able to acquire an upgradable read lock.
let upgradable_guard: RwLockUpgradableReadGuard<'_, _> = lock.upgradable_read();
// Should be able to upgrade the guard.
let _write_guard: RwLockWriteGuard<'_, _> =
RwLockUpgradableReadGuard::upgrade(upgradable_guard);
}
#[test]
fn test_once_usage() {
let once = Arc::new(Once::new());
let once_clone = once.clone();
assert!(!once_clone.state().done());
let handle = thread::spawn(move || {
assert!(!once_clone.state().done());
once_clone.call_once(|| {});
assert!(once_clone.state().done());
});
handle.join().unwrap();
assert!(once.state().done());
}
}

View File

@@ -30,6 +30,12 @@ pub use tracing::{
Condvar, Mutex, MutexGuard, Once, OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard, Condvar, Mutex, MutexGuard, Once, OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard,
}; };
#[cfg(all(has_std__sync__LazyLock, debug_assertions))]
pub use tracing::LazyLock;
#[cfg(all(has_std__sync__LazyLock, not(debug_assertions)))]
pub use std::sync::LazyLock;
/// Dependency tracing versions of [`std::sync`]. /// Dependency tracing versions of [`std::sync`].
pub mod tracing { pub mod tracing {
use std::fmt; use std::fmt;
@@ -47,6 +53,12 @@ pub mod tracing {
use crate::BorrowedMutex; use crate::BorrowedMutex;
use crate::LazyMutexId; use crate::LazyMutexId;
#[cfg(has_std__sync__LazyLock)]
pub use lazy_lock::LazyLock;
#[cfg(has_std__sync__LazyLock)]
mod lazy_lock;
/// Wrapper for [`std::sync::Mutex`]. /// Wrapper for [`std::sync::Mutex`].
/// ///
/// Refer to the [crate-level][`crate`] documentation for the differences between this struct and /// Refer to the [crate-level][`crate`] documentation for the differences between this struct and
@@ -460,8 +472,7 @@ pub mod tracing {
where where
F: FnOnce(), F: FnOnce(),
{ {
let _guard = self.mutex_id.get_borrowed(); self.mutex_id.with_held(|| self.inner.call_once(f))
self.inner.call_once(f);
} }
/// Performs the same operation as [`call_once`][Once::call_once] except it ignores /// Performs the same operation as [`call_once`][Once::call_once] except it ignores
@@ -475,8 +486,7 @@ pub mod tracing {
where where
F: FnOnce(&OnceState), F: FnOnce(&OnceState),
{ {
let _guard = self.mutex_id.get_borrowed(); self.mutex_id.with_held(|| self.inner.call_once_force(f))
self.inner.call_once_force(f);
} }
/// Returns true if some `call_once` has completed successfully. /// Returns true if some `call_once` has completed successfully.
@@ -550,9 +560,7 @@ pub mod tracing {
/// As this method may block until initialization is complete, it participates in cycle /// As this method may block until initialization is complete, it participates in cycle
/// detection. /// detection.
pub fn set(&self, value: T) -> Result<(), T> { pub fn set(&self, value: T) -> Result<(), T> {
let _guard = self.id.get_borrowed(); self.id.with_held(|| self.inner.set(value))
self.inner.set(value)
} }
/// Gets the contents of the cell, initializing it with `f` if the cell was empty. /// Gets the contents of the cell, initializing it with `f` if the cell was empty.
@@ -562,8 +570,7 @@ pub mod tracing {
where where
F: FnOnce() -> T, F: FnOnce() -> T,
{ {
let _guard = self.id.get_borrowed(); self.id.with_held(|| self.inner.get_or_init(f))
self.inner.get_or_init(f)
} }
/// Takes the value out of this `OnceLock`, moving it back to an uninitialized state. /// Takes the value out of this `OnceLock`, moving it back to an uninitialized state.

View File

@@ -0,0 +1,117 @@
//! Wrapper implementation for LazyLock
//!
//! This lives in a separate module as LazyLock would otherwise raise our MSRV to 1.80. Reevaluate
//! this in the future.
use std::fmt;
use std::fmt::Debug;
use std::ops::Deref;
use crate::LazyMutexId;
/// Wrapper for [`std::sync::LazyLock`]
///
/// This wrapper participates in cycle detection like all other primitives in this crate. It should
/// only be possible to encounter cycles when acquiring mutexes in the initialisation function.
///
/// # Examples
///
/// ```
/// use tracing_mutex::stdsync::tracing::LazyLock;
///
/// static LOCK: LazyLock<i32> = LazyLock::new(|| {
/// println!("Hello, world!");
/// 42
/// });
///
/// // This should print "Hello, world!"
/// println!("{}", *LOCK);
/// // This should not.
/// println!("{}", *LOCK);
/// ```
pub struct LazyLock<T, F = fn() -> T> {
// MSRV violation is fine, this is gated behind a cfg! check
#[allow(clippy::incompatible_msrv)]
inner: std::sync::LazyLock<T, F>,
id: LazyMutexId,
}
impl<T, F: FnOnce() -> T> LazyLock<T, F> {
/// Creates a new lazy value with the given initializing function.
pub const fn new(f: F) -> LazyLock<T, F> {
Self {
id: LazyMutexId::new(),
// MSRV violation is fine, this is gated behind a cfg! check
#[allow(clippy::incompatible_msrv)]
inner: std::sync::LazyLock::new(f),
}
}
/// Force this lazy lock to be evaluated.
///
/// This is equivalent to dereferencing, but is more explicit.
pub fn force(this: &LazyLock<T, F>) -> &T {
this
}
}
impl<T, F: FnOnce() -> T> Deref for LazyLock<T, F> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.id.with_held(|| &*self.inner)
}
}
impl<T: Default> Default for LazyLock<T> {
/// Return a `LazyLock` that is initialized through [`Default`].
fn default() -> Self {
Self::new(Default::default)
}
}
impl<T: Debug, F> Debug for LazyLock<T, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Cannot implement this ourselves because the get() used is nightly, so delegate.
self.inner.fmt(f)
}
}
#[cfg(test)]
mod tests {
use crate::stdsync::Mutex;
use super::*;
#[test]
fn test_only_init_once() {
let mut init_counter = 0;
let lock = LazyLock::new(|| {
init_counter += 1;
42
});
assert_eq!(*lock, 42);
LazyLock::force(&lock);
// Ensure we can access the init counter
drop(lock);
assert_eq!(init_counter, 1);
}
#[test]
#[should_panic(expected = "Found cycle")]
fn test_panic_with_cycle() {
let mutex = Mutex::new(());
let lock = LazyLock::new(|| *mutex.lock().unwrap());
// Establish the relation from lock to mutex
LazyLock::force(&lock);
// Now do it the other way around, which should crash
let _guard = mutex.lock().unwrap();
LazyLock::force(&lock);
}
}