Skip to content

Commit 25ae542

Browse files
committed
Add support for LazyLock
1 parent 40f835a commit 25ae542

File tree

6 files changed

+152
-13
lines changed

6 files changed

+152
-13
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,6 @@ backtraces = []
3636
# Feature names do not match crate names pending namespaced features.
3737
lockapi = ["lock_api"]
3838
parkinglot = ["parking_lot", "lockapi"]
39+
40+
[build-dependencies]
41+
autocfg = "1.4.0"

build.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use autocfg::AutoCfg;
2+
3+
fn main() {
4+
// To avoid bumping MSRV unnecessarily, we can sniff certain features. Reevaluate this on major
5+
// releases.
6+
let ac = AutoCfg::new().unwrap();
7+
ac.emit_has_path("std::sync::LazyLock");
8+
9+
autocfg::rerun_path("build.rs");
10+
}

src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@ impl MutexId {
193193
unreachable!("Tried to drop lock for mutex {:?} but it wasn't held", self)
194194
});
195195
}
196+
197+
/// Execute the given closure while the guard is held.
198+
pub fn with_held<T>(&self, f: impl FnOnce() -> T) -> T {
199+
// Note: we MUST construct the RAII guard, we cannot simply mark held + mark released, as
200+
// f() may panic and corrupt our state.
201+
let _guard = self.get_borrowed();
202+
f()
203+
}
196204
}
197205

198206
impl Default for MutexId {

src/parkinglot.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,16 +138,14 @@ pub mod tracing {
138138
/// This method will panic if `f` panics, poisoning this `Once`. In addition, this function
139139
/// panics when the lock acquisition order is determined to be inconsistent.
140140
pub fn call_once(&self, f: impl FnOnce()) {
141-
let _borrow = self.id.get_borrowed();
142-
self.inner.call_once(f);
141+
self.id.with_held(|| self.inner.call_once(f));
143142
}
144143

145144
/// Performs the given initialization routine once and only once.
146145
///
147146
/// This method is identical to [`Once::call_once`] except it ignores poisoning.
148147
pub fn call_once_force(&self, f: impl FnOnce(OnceState)) {
149-
let _borrow = self.id.get_borrowed();
150-
self.inner.call_once_force(f);
148+
self.id.with_held(|| self.inner.call_once_force(f));
151149
}
152150
}
153151
}

src/stdsync.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ pub use tracing::{
3030
Condvar, Mutex, MutexGuard, Once, OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard,
3131
};
3232

33+
#[cfg(all(has_std__sync__LazyLock, debug_assertions))]
34+
pub use tracing::LazyLock;
35+
36+
#[cfg(all(has_std__sync__LazyLock, not(debug_assertions)))]
37+
pub use std::sync::LazyLock;
38+
3339
/// Dependency tracing versions of [`std::sync`].
3440
pub mod tracing {
3541
use std::fmt;
@@ -47,6 +53,12 @@ pub mod tracing {
4753
use crate::BorrowedMutex;
4854
use crate::LazyMutexId;
4955

56+
#[cfg(has_std__sync__LazyLock)]
57+
pub use lazy_lock::LazyLock;
58+
59+
#[cfg(has_std__sync__LazyLock)]
60+
mod lazy_lock;
61+
5062
/// Wrapper for [`std::sync::Mutex`].
5163
///
5264
/// Refer to the [crate-level][`crate`] documentation for the differences between this struct and
@@ -460,8 +472,7 @@ pub mod tracing {
460472
where
461473
F: FnOnce(),
462474
{
463-
let _guard = self.mutex_id.get_borrowed();
464-
self.inner.call_once(f);
475+
self.mutex_id.with_held(|| self.inner.call_once(f))
465476
}
466477

467478
/// Performs the same operation as [`call_once`][Once::call_once] except it ignores
@@ -475,8 +486,7 @@ pub mod tracing {
475486
where
476487
F: FnOnce(&OnceState),
477488
{
478-
let _guard = self.mutex_id.get_borrowed();
479-
self.inner.call_once_force(f);
489+
self.mutex_id.with_held(|| self.inner.call_once_force(f))
480490
}
481491

482492
/// Returns true if some `call_once` has completed successfully.
@@ -550,9 +560,7 @@ pub mod tracing {
550560
/// As this method may block until initialization is complete, it participates in cycle
551561
/// detection.
552562
pub fn set(&self, value: T) -> Result<(), T> {
553-
let _guard = self.id.get_borrowed();
554-
555-
self.inner.set(value)
563+
self.id.with_held(|| self.inner.set(value))
556564
}
557565

558566
/// Gets the contents of the cell, initializing it with `f` if the cell was empty.
@@ -562,8 +570,7 @@ pub mod tracing {
562570
where
563571
F: FnOnce() -> T,
564572
{
565-
let _guard = self.id.get_borrowed();
566-
self.inner.get_or_init(f)
573+
self.id.with_held(|| self.inner.get_or_init(f))
567574
}
568575

569576
/// Takes the value out of this `OnceLock`, moving it back to an uninitialized state.

src/stdsync/tracing/lazy_lock.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//! Wrapper implementation for LazyLock
2+
//!
3+
//! This lives in a separate module as LazyLock would otherwise raise our MSRV to 1.80. Reevaluate
4+
//! this in the future.
5+
use std::fmt;
6+
use std::fmt::Debug;
7+
use std::ops::Deref;
8+
9+
use crate::LazyMutexId;
10+
11+
/// Wrapper for [`std::sync::LazyLock`]
12+
///
13+
/// This wrapper participates in cycle detection like all other primitives in this crate. It should
14+
/// only be possible to encounter cycles when acquiring mutexes in the initialisation function.
15+
///
16+
/// # Examples
17+
///
18+
/// ```
19+
/// use tracing_mutex::stdsync::tracing::LazyLock;
20+
///
21+
/// static LOCK: LazyLock<i32> = LazyLock::new(|| {
22+
/// println!("Hello, world!");
23+
/// 42
24+
/// });
25+
///
26+
/// // This should print "Hello, world!"
27+
/// println!("{}", *LOCK);
28+
/// // This should not.
29+
/// println!("{}", *LOCK);
30+
/// ```
31+
pub struct LazyLock<T, F = fn() -> T> {
32+
inner: std::sync::LazyLock<T, F>,
33+
id: LazyMutexId,
34+
}
35+
36+
impl<T, F: FnOnce() -> T> LazyLock<T, F> {
37+
/// Creates a new lazy value with the given initializing function.
38+
pub const fn new(f: F) -> LazyLock<T, F> {
39+
Self {
40+
id: LazyMutexId::new(),
41+
inner: std::sync::LazyLock::new(f),
42+
}
43+
}
44+
45+
/// Force this lazy lock to be evaluated.
46+
///
47+
/// This is equivalent to dereferencing, but is more explicit.
48+
pub fn force(this: &LazyLock<T, F>) -> &T {
49+
&*this
50+
}
51+
}
52+
53+
impl<T, F: FnOnce() -> T> Deref for LazyLock<T, F> {
54+
type Target = T;
55+
56+
fn deref(&self) -> &Self::Target {
57+
self.id.with_held(|| &*self.inner)
58+
}
59+
}
60+
61+
impl<T: Default> Default for LazyLock<T> {
62+
/// Return a `LazyLock` that is initialized through [`Default`].
63+
fn default() -> Self {
64+
Self::new(Default::default)
65+
}
66+
}
67+
68+
impl<T: Debug, F> Debug for LazyLock<T, F> {
69+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70+
// Cannot implement this ourselves because the get() used is nightly, so delegate.
71+
self.inner.fmt(f)
72+
}
73+
}
74+
75+
#[cfg(test)]
76+
mod tests {
77+
use crate::stdsync::Mutex;
78+
79+
use super::*;
80+
81+
#[test]
82+
fn test_only_init_once() {
83+
let mut init_counter = 0;
84+
85+
let lock = LazyLock::new(|| {
86+
init_counter += 1;
87+
42
88+
});
89+
90+
assert_eq!(*lock, 42);
91+
LazyLock::force(&lock);
92+
93+
// Ensure we can access the init counter
94+
drop(lock);
95+
96+
assert_eq!(init_counter, 1);
97+
}
98+
99+
#[test]
100+
#[should_panic(expected = "Found cycle")]
101+
fn test_panic_with_cycle() {
102+
let mutex = Mutex::new(());
103+
104+
let lock = LazyLock::new(|| *mutex.lock().unwrap());
105+
106+
// Establish the relation from lock to mutex
107+
LazyLock::force(&lock);
108+
109+
// Now do it the other way around, which should crash
110+
let _guard = mutex.lock().unwrap();
111+
LazyLock::force(&lock);
112+
}
113+
}

0 commit comments

Comments
 (0)