Skip to main content

melinoe/reentrant/
gate.rs

1use crate::reentrant::error::Reentered;
2use crate::reentrant::reset::Reset;
3use crate::token::ExclusiveToken;
4use core::cell::Cell;
5
6/// A thread-confined gate yielding at most one exclusive branded token at a time.
7///
8/// Place one in thread-local storage to brand a thread's ambient exclusive state
9/// (e.g. its allocator slot). `!Sync` by construction (it holds a [`Cell`]).
10#[derive(Debug, Default)]
11pub struct ReentrancyCell {
12    active: Cell<bool>,
13}
14
15impl ReentrancyCell {
16    /// Create an idle gate.
17    #[inline]
18    #[must_use]
19    pub const fn new() -> Self {
20        Self {
21            active: Cell::new(false),
22        }
23    }
24
25    /// Whether the gate is currently held (an `enter` is in progress).
26    #[inline]
27    #[must_use]
28    pub fn is_active(&self) -> bool {
29        self.active.get()
30    }
31
32    /// Acquire the gate and run `f` with a fresh-brand [`ExclusiveToken`].
33    ///
34    /// The flag is cleared when `f` returns, including across a panic unwinding
35    /// through `f`.
36    ///
37    /// # Errors
38    ///
39    /// Returns [`Reentered`] without running `f` if the gate is already held on
40    /// this thread (a re-entrant call) — callers take a fallback path.
41    ///
42    /// # Examples
43    ///
44    /// ```
45    /// use melinoe::reentrant::ReentrancyCell;
46    /// use melinoe::MelinoeCell;
47    ///
48    /// let gate = ReentrancyCell::new();
49    ///
50    /// let out = gate.enter(|mut token| {
51    ///     // Ambient state, now token-gated with a compile-time exclusivity proof.
52    ///     let slot = MelinoeCell::new(0_u64);
53    ///     *slot.borrow_mut(&mut token) = 7;
54    ///
55    ///     // A re-entrant acquisition is refused, not aliased.
56    ///     assert_eq!(gate.enter(|_| ()), Err(melinoe::reentrant::Reentered));
57    ///
58    ///     *slot.borrow(&token)
59    /// });
60    /// assert_eq!(out, Ok(7));
61    /// ```
62    #[inline]
63    pub fn enter<R>(
64        &self,
65        f: impl for<'brand> FnOnce(ExclusiveToken<'brand>) -> R,
66    ) -> Result<R, Reentered> {
67        let _reset = Reset::acquire(&self.active)?;
68        // SAFETY: the flag (set by `acquire`, re-checked by any nested `enter`)
69        // guarantees no other token minted by this cell is live, and `for<'brand>`
70        // makes the brand fresh and non-escaping — so this is the unique
71        // `ExclusiveToken` for its brand, satisfying `new_unchecked`.
72        let token = unsafe { ExclusiveToken::new_unchecked() };
73        Ok(f(token))
74    }
75}