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}