Skip to main content

laminar_core/alloc/
guard.rs

1//! RAII guard for priority class enforcement (debug builds only).
2
3/// Priority class for the current execution context.
4///
5/// Used in debug builds to verify functions run in the correct priority class.
6/// In release builds, all checks are compiled away.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum PriorityClass {
9    /// Event processing — <10ms cycle budget, no blocking I/O.
10    EventProcessing,
11    /// Background I/O — checkpoint, WAL, table polling.
12    BackgroundIo,
13    /// Control plane — DDL, config changes, metrics.
14    Control,
15}
16
17#[cfg(debug_assertions)]
18std::thread_local! {
19    static CURRENT_PRIORITY: std::cell::Cell<Option<PriorityClass>> = const { std::cell::Cell::new(None) };
20}
21
22/// RAII guard that sets the thread-local priority class in debug builds.
23///
24/// On creation, records the current priority class. On drop, restores
25/// the previous value. In release builds this is a zero-size no-op.
26///
27/// `Send` so it can be held across `.await` points in single-threaded async runtimes.
28pub struct PriorityGuard {
29    #[cfg(debug_assertions)]
30    previous: Option<PriorityClass>,
31}
32
33impl PriorityGuard {
34    /// Enter a priority class section.
35    #[inline]
36    #[must_use]
37    pub fn enter(#[allow(unused_variables)] class: PriorityClass) -> Self {
38        #[cfg(debug_assertions)]
39        {
40            let previous = CURRENT_PRIORITY.with(|c| {
41                let prev = c.get();
42                c.set(Some(class));
43                prev
44            });
45            Self { previous }
46        }
47        #[cfg(not(debug_assertions))]
48        {
49            Self {}
50        }
51    }
52
53    /// Returns the current thread's priority class (debug builds only).
54    #[inline]
55    #[must_use]
56    pub fn current() -> Option<PriorityClass> {
57        #[cfg(debug_assertions)]
58        {
59            CURRENT_PRIORITY.with(std::cell::Cell::get)
60        }
61        #[cfg(not(debug_assertions))]
62        {
63            None
64        }
65    }
66}
67
68impl Drop for PriorityGuard {
69    #[inline]
70    fn drop(&mut self) {
71        #[cfg(debug_assertions)]
72        {
73            CURRENT_PRIORITY.with(|c| c.set(self.previous));
74        }
75    }
76}
77
78impl std::fmt::Debug for PriorityGuard {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        f.debug_struct("PriorityGuard").finish()
81    }
82}
83
84/// Assert that the current thread is running in the expected priority class.
85///
86/// In debug builds, panics if the current priority class does not match.
87/// In release builds, this is a no-op.
88#[macro_export]
89macro_rules! debug_assert_priority {
90    ($class:expr) => {
91        #[cfg(debug_assertions)]
92        {
93            let current = $crate::alloc::PriorityGuard::current();
94            debug_assert!(
95                current == Some($class),
96                "priority class mismatch: expected {:?}, got {:?}",
97                $class,
98                current,
99            );
100        }
101    };
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_priority_guard_enter_exit() {
110        assert_eq!(PriorityGuard::current(), None);
111
112        {
113            let _guard = PriorityGuard::enter(PriorityClass::EventProcessing);
114            assert_eq!(
115                PriorityGuard::current(),
116                Some(PriorityClass::EventProcessing)
117            );
118        }
119
120        assert_eq!(PriorityGuard::current(), None);
121    }
122
123    #[test]
124    fn test_priority_guard_nesting() {
125        let _outer = PriorityGuard::enter(PriorityClass::EventProcessing);
126        assert_eq!(
127            PriorityGuard::current(),
128            Some(PriorityClass::EventProcessing)
129        );
130
131        {
132            let _inner = PriorityGuard::enter(PriorityClass::BackgroundIo);
133            assert_eq!(PriorityGuard::current(), Some(PriorityClass::BackgroundIo));
134        }
135
136        assert_eq!(
137            PriorityGuard::current(),
138            Some(PriorityClass::EventProcessing)
139        );
140    }
141
142    #[test]
143    fn test_debug_assert_priority() {
144        let _guard = PriorityGuard::enter(PriorityClass::Control);
145        debug_assert_priority!(PriorityClass::Control);
146    }
147}