maitake_sync/
util.rs

1//! Reusable utilities for synchronization primitives.
2//!
3//! This module contains utility code used in the implementation of the
4//! synchronization primitives provided by `maitake-sync`. To enable code reuse,
5//! some of these utilities are exposed as public APIs in this module, so that
6//! projects depending on `maitake-sync` can use them as well.
7//!
8//! This module exposes the following APIs:
9//!
10//! - [`Backoff`]: exponential backoff for spin loops
11//! - [`CachePadded`]: pads and aligns a value to the size of a cache line
12
13#[cfg(any(test, feature = "tracing", loom))]
14macro_rules! trace {
15    ($($t:tt)*) => { tracing::trace!($($t)*) }
16}
17
18#[cfg(not(any(test, feature = "tracing", loom)))]
19macro_rules! trace {
20    ($($t:tt)*) => {};
21}
22
23#[cfg(all(not(test), not(all(maitake_ultraverbose, feature = "tracing"))))]
24macro_rules! test_dbg {
25    ($e:expr) => {
26        $e
27    };
28}
29
30#[cfg(any(test, all(maitake_ultraverbose, feature = "tracing")))]
31macro_rules! test_dbg {
32    ($e:expr) => {
33        match $e {
34            e => {
35                tracing::debug!(
36                    location = %core::panic::Location::caller(),
37                    "{} = {:?}",
38                    stringify!($e),
39                    &e
40                );
41                e
42            }
43        }
44    };
45}
46
47#[cfg(all(not(test), not(all(maitake_ultraverbose, feature = "tracing"))))]
48macro_rules! test_debug {
49    ($($t:tt)*) => {};
50}
51
52#[cfg(any(test, all(maitake_ultraverbose, feature = "tracing")))]
53macro_rules! test_debug {
54    ($($t:tt)*) => { tracing::debug!($($t)*) }
55}
56
57#[cfg(all(not(test), not(all(maitake_ultraverbose, feature = "tracing"))))]
58macro_rules! test_trace {
59    ($($t:tt)*) => {};
60}
61
62#[cfg(any(test, all(maitake_ultraverbose, feature = "tracing")))]
63macro_rules! test_trace {
64    ($($t:tt)*) => { tracing::trace!($($t)*) }
65}
66
67#[cfg(all(not(test), not(all(maitake_ultraverbose, feature = "tracing"))))]
68macro_rules! enter_test_debug_span {
69    ($($args:tt)+) => {};
70}
71
72#[cfg(any(test, all(maitake_ultraverbose, feature = "tracing")))]
73macro_rules! enter_test_debug_span {
74    ($($args:tt)+) => {
75        let _span = tracing::debug_span!($($args)+).entered();
76    };
77}
78
79macro_rules! fmt_bits {
80    ($self: expr, $f: expr, $has_states: ident, $($name: ident),+) => {
81        $(
82            if $self.contains(Self::$name) {
83                if $has_states {
84                    $f.write_str(" | ")?;
85                }
86                $f.write_str(stringify!($name))?;
87                $has_states = true;
88            }
89        )+
90
91    };
92}
93
94macro_rules! feature {
95    (
96        #![$meta:meta]
97        $($item:item)*
98    ) => {
99        $(
100            #[cfg($meta)]
101            #[cfg_attr(docsrs, doc(cfg($meta)))]
102            $item
103        )*
104    }
105}
106
107macro_rules! loom_const_fn {
108    (
109        $(#[$meta:meta])*
110        $vis:vis unsafe fn $name:ident($($arg:ident: $T:ty),*) -> $Ret:ty $body:block
111    ) => {
112        $(#[$meta])*
113        #[cfg(not(loom))]
114        $vis const unsafe fn $name($($arg: $T),*) -> $Ret $body
115
116        $(#[$meta])*
117        #[cfg(loom)]
118        $vis unsafe fn $name($($arg: $T),*) -> $Ret $body
119    };
120    (
121        $(#[$meta:meta])*
122        $vis:vis fn $name:ident($($arg:ident: $T:ty),*) -> $Ret:ty $body:block
123    ) => {
124        $(#[$meta])*
125        #[cfg(not(loom))]
126        $vis const fn $name($($arg: $T),*) -> $Ret $body
127
128        $(#[$meta])*
129        #[cfg(loom)]
130        $vis fn $name($($arg: $T),*) -> $Ret $body
131    }
132}
133
134/// Indicates unreachable code that we are confident is *truly* unreachable.
135///
136/// This is essentially a compromise between `core::unreachable!()` and
137/// `core::hint::unreachable_unchecked()`. In debug mode builds and in tests,
138/// this expands to `unreachable!()`, causing a panic. However, in release mode
139/// non-test builds, this expands to `unreachable_unchecked`. Thus, this is a
140/// somewhat safer form of `unreachable_unchecked` that will allow cases where
141/// `unreachable_unchecked` would be invalid to be detected early.
142///
143/// Nonetheless, this must still be used with caution! If code is not adequately
144/// tested, it is entirely possible for the `unreachable_unchecked` to be
145/// reached in a scenario that was not reachable in tests.
146macro_rules! unreachable_unchecked {
147    () => ({
148        #[cfg(any(test, debug_assertions))]
149        panic!(
150            concat!(
151                env!("CARGO_PKG_NAME"),
152                "internal error: entered unreachable code \n",
153                "/!\\ EXTREMELY SERIOUS WARNING: in release mode, this would have been\n",
154                "    `unreachable_unchecked`! This could result in undefine behavior.\n",
155                "    Please double- or triple-check any assumptions about code which\n,",
156                "    could lead to this being triggered."
157            ),
158        );
159        #[allow(unreachable_code)] // lol
160        {
161            core::hint::unreachable_unchecked();
162        }
163    });
164    ($msg:expr) => ({
165        unreachable_unchecked!("{}", $msg)
166    });
167    ($msg:expr,) => ({
168        unreachable_unchecked!($msg)
169    });
170    ($fmt:expr, $($arg:tt)*) => ({
171        #[cfg(any(test, debug_assertions))]
172        panic!(
173            concat!(
174                env!("CARGO_PKG_NAME"),
175                "internal error: entered unreachable code: ",
176                $fmt,
177                "\n/!\\ EXTREMELY SERIOUS WARNING: in release mode, this would have been \n\
178                \x32   `unreachable_unchecked`! This could result in undefine behavior. \n\
179                \x32   Please double- or triple-check any assumptions about code which \n\
180                \x32   could lead to this being triggered."
181            ),
182            $($arg)*
183        );
184        #[allow(unreachable_code)] // lol
185        {
186            core::hint::unreachable_unchecked();
187        }
188    });
189}
190
191mod backoff;
192mod cache_pad;
193pub(crate) mod fmt;
194mod maybe_uninit;
195mod wake_batch;
196
197#[cfg(all(test, not(loom)))]
198pub(crate) use self::test::trace_init;
199pub use self::{backoff::Backoff, cache_pad::CachePadded};
200pub(crate) use self::{maybe_uninit::CheckedMaybeUninit, wake_batch::WakeBatch};
201
202#[cfg(test)]
203pub(crate) mod test {
204    /// A guard that represents the tracing default subscriber guard
205    ///
206    /// *should* be held until the end of the test, to ensure that tracing messages
207    /// actually make it to the fmt subscriber for the entire test.
208    ///
209    /// Exists to abstract over tracing 01/02 guard type differences.
210    #[must_use]
211    #[cfg(all(test, not(loom)))]
212    pub struct TestGuard {
213        _x1: tracing::subscriber::DefaultGuard,
214    }
215
216    /// Initialize tracing with a default filter directive
217    ///
218    /// Returns a [TestGuard] that must be held for the duration of test to ensure
219    /// tracing messages are correctly output
220    #[cfg(all(test, not(loom)))]
221    pub(crate) fn trace_init() -> TestGuard {
222        trace_init_with_default("maitake=debug,cordyceps=debug")
223    }
224
225    /// Initialize tracing with the given filter directive
226    ///
227    /// Returns a [TestGuard] that must be held for the duration of test to ensure
228    /// tracing messages are correctly output
229    #[cfg(all(test, not(loom)))]
230    pub(crate) fn trace_init_with_default(default: &str) -> TestGuard {
231        use tracing_subscriber::{
232            filter::{EnvFilter, LevelFilter},
233            util::SubscriberInitExt,
234        };
235        const ENV: &str = if cfg!(loom) { "LOOM_LOG" } else { "RUST_LOG" };
236
237        let env = std::env::var(ENV).unwrap_or_default();
238        let builder = EnvFilter::builder().with_default_directive(LevelFilter::INFO.into());
239        let filter = if env.is_empty() {
240            builder
241                .parse(default)
242                .unwrap()
243                // enable "loom=info" if using the default, so that we get
244                // loom's thread number and iteration count traces.
245                .add_directive("loom=info".parse().unwrap())
246        } else {
247            builder.parse_lossy(env)
248        };
249        let collector = tracing_subscriber::fmt()
250            .with_env_filter(filter)
251            .with_test_writer()
252            .without_time()
253            .finish();
254
255        TestGuard {
256            _x1: collector.set_default(),
257        }
258    }
259
260    #[allow(dead_code)]
261    pub(crate) fn assert_send<T: Send>() {}
262
263    #[allow(dead_code)]
264    pub(crate) fn assert_sync<T: Sync>() {}
265    pub(crate) fn assert_send_sync<T: Send + Sync>() {}
266
267    pub(crate) fn assert_future<F: core::future::Future>() {}
268
269    pub(crate) struct NopRawMutex;
270
271    unsafe impl mutex_traits::RawMutex for NopRawMutex {
272        type GuardMarker = ();
273        fn lock(&self) {
274            unimplemented!("don't actually try to lock this thing")
275        }
276
277        fn is_locked(&self) -> bool {
278            unimplemented!("don't actually try to lock this thing")
279        }
280
281        fn try_lock(&self) -> bool {
282            unimplemented!("don't actually try to lock this thing")
283        }
284
285        unsafe fn unlock(&self) {
286            unimplemented!("don't actually try to lock this thing")
287        }
288    }
289}