Skip to main content

laminar_core/budget/
yield_reason.rs

1//! Yield reasons for Ring 1 cooperative scheduling.
2
3use std::fmt;
4
5/// Reason Ring 1 yielded control.
6///
7/// Ring 1 background tasks cooperatively yield to ensure Ring 0
8/// hot path operations are not delayed. This enum captures why
9/// the yield occurred for metrics and debugging.
10///
11/// # Example
12///
13/// ```rust,ignore
14/// use laminar_core::budget::{TaskBudget, YieldReason};
15///
16/// fn process_background_work(&mut self) -> YieldReason {
17///     let budget = TaskBudget::ring1_chunk();
18///
19///     while !budget.exceeded() {
20///         // Check if Ring 0 needs attention (priority)
21///         if self.ring0_has_pending() {
22///             return YieldReason::Ring0Priority;
23///         }
24///
25///         // Get next work item
26///         match self.background_queue.pop() {
27///             Some(work) => self.process_work_item(work),
28///             None => return YieldReason::QueueEmpty,
29///         }
30///     }
31///
32///     YieldReason::BudgetExceeded
33/// }
34/// ```
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub enum YieldReason {
37    /// Budget exceeded, need to give Ring 0 a chance.
38    ///
39    /// The Ring 1 task has used its entire time budget (typically 1ms)
40    /// and must yield to avoid blocking Ring 0 operations.
41    BudgetExceeded,
42
43    /// Ring 0 has pending events (priority yield).
44    ///
45    /// Even if budget remains, Ring 0 events take priority over
46    /// Ring 1 background work. This ensures latency SLAs.
47    Ring0Priority,
48
49    /// No more work in queue.
50    ///
51    /// The background work queue is empty. The task can sleep
52    /// or wait for new work to arrive.
53    QueueEmpty,
54
55    /// Shutdown has been requested.
56    ///
57    /// The task should stop processing and allow graceful shutdown.
58    ShutdownRequested,
59
60    /// External interrupt (e.g., signal).
61    ///
62    /// An external event has interrupted processing.
63    Interrupted,
64
65    /// Yielded to allow checkpoint coordination.
66    ///
67    /// During checkpointing, tasks may need to yield to ensure
68    /// consistent state snapshots.
69    CheckpointBarrier,
70
71    /// Yielded due to backpressure.
72    ///
73    /// Downstream cannot accept more outputs, so the task yields
74    /// to avoid buffering too much data.
75    Backpressure,
76
77    /// Yielded to allow other Ring 1 tasks to run.
78    ///
79    /// Fair scheduling among multiple Ring 1 tasks.
80    FairScheduling,
81}
82
83impl YieldReason {
84    /// Returns true if this yield reason indicates the task should stop.
85    #[must_use]
86    pub fn should_stop(&self) -> bool {
87        matches!(
88            self,
89            YieldReason::ShutdownRequested | YieldReason::Interrupted
90        )
91    }
92
93    /// Returns true if this yield reason indicates work is available.
94    #[must_use]
95    pub fn has_more_work(&self) -> bool {
96        matches!(
97            self,
98            YieldReason::BudgetExceeded
99                | YieldReason::Ring0Priority
100                | YieldReason::CheckpointBarrier
101                | YieldReason::FairScheduling
102        )
103    }
104
105    /// Returns true if the task can immediately resume after yield.
106    #[must_use]
107    pub fn can_resume_immediately(&self) -> bool {
108        matches!(
109            self,
110            YieldReason::BudgetExceeded | YieldReason::Ring0Priority | YieldReason::FairScheduling
111        )
112    }
113
114    /// Returns true if this yield is due to Ring 0 priority.
115    #[must_use]
116    pub fn is_ring0_priority(&self) -> bool {
117        matches!(self, YieldReason::Ring0Priority)
118    }
119
120    /// Returns true if this yield is due to budget constraints.
121    #[must_use]
122    pub fn is_budget_related(&self) -> bool {
123        matches!(self, YieldReason::BudgetExceeded)
124    }
125
126    /// Get metric label for this yield reason.
127    #[must_use]
128    pub fn metric_label(&self) -> &'static str {
129        match self {
130            YieldReason::BudgetExceeded => "budget_exceeded",
131            YieldReason::Ring0Priority => "ring0_priority",
132            YieldReason::QueueEmpty => "queue_empty",
133            YieldReason::ShutdownRequested => "shutdown",
134            YieldReason::Interrupted => "interrupted",
135            YieldReason::CheckpointBarrier => "checkpoint",
136            YieldReason::Backpressure => "backpressure",
137            YieldReason::FairScheduling => "fair_scheduling",
138        }
139    }
140}
141
142impl fmt::Display for YieldReason {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        match self {
145            YieldReason::BudgetExceeded => write!(f, "BudgetExceeded"),
146            YieldReason::Ring0Priority => write!(f, "Ring0Priority"),
147            YieldReason::QueueEmpty => write!(f, "QueueEmpty"),
148            YieldReason::ShutdownRequested => write!(f, "ShutdownRequested"),
149            YieldReason::Interrupted => write!(f, "Interrupted"),
150            YieldReason::CheckpointBarrier => write!(f, "CheckpointBarrier"),
151            YieldReason::Backpressure => write!(f, "Backpressure"),
152            YieldReason::FairScheduling => write!(f, "FairScheduling"),
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_should_stop() {
163        assert!(!YieldReason::BudgetExceeded.should_stop());
164        assert!(!YieldReason::Ring0Priority.should_stop());
165        assert!(!YieldReason::QueueEmpty.should_stop());
166        assert!(YieldReason::ShutdownRequested.should_stop());
167        assert!(YieldReason::Interrupted.should_stop());
168        assert!(!YieldReason::CheckpointBarrier.should_stop());
169        assert!(!YieldReason::Backpressure.should_stop());
170        assert!(!YieldReason::FairScheduling.should_stop());
171    }
172
173    #[test]
174    fn test_has_more_work() {
175        assert!(YieldReason::BudgetExceeded.has_more_work());
176        assert!(YieldReason::Ring0Priority.has_more_work());
177        assert!(!YieldReason::QueueEmpty.has_more_work());
178        assert!(!YieldReason::ShutdownRequested.has_more_work());
179        assert!(YieldReason::CheckpointBarrier.has_more_work());
180        assert!(!YieldReason::Backpressure.has_more_work());
181        assert!(YieldReason::FairScheduling.has_more_work());
182    }
183
184    #[test]
185    fn test_can_resume_immediately() {
186        assert!(YieldReason::BudgetExceeded.can_resume_immediately());
187        assert!(YieldReason::Ring0Priority.can_resume_immediately());
188        assert!(!YieldReason::QueueEmpty.can_resume_immediately());
189        assert!(!YieldReason::ShutdownRequested.can_resume_immediately());
190        assert!(!YieldReason::CheckpointBarrier.can_resume_immediately());
191        assert!(YieldReason::FairScheduling.can_resume_immediately());
192    }
193
194    #[test]
195    fn test_metric_labels() {
196        assert_eq!(
197            YieldReason::BudgetExceeded.metric_label(),
198            "budget_exceeded"
199        );
200        assert_eq!(YieldReason::Ring0Priority.metric_label(), "ring0_priority");
201        assert_eq!(YieldReason::QueueEmpty.metric_label(), "queue_empty");
202        assert_eq!(YieldReason::ShutdownRequested.metric_label(), "shutdown");
203        assert_eq!(YieldReason::Interrupted.metric_label(), "interrupted");
204        assert_eq!(YieldReason::CheckpointBarrier.metric_label(), "checkpoint");
205        assert_eq!(YieldReason::Backpressure.metric_label(), "backpressure");
206        assert_eq!(
207            YieldReason::FairScheduling.metric_label(),
208            "fair_scheduling"
209        );
210    }
211
212    #[test]
213    fn test_is_ring0_priority() {
214        assert!(!YieldReason::BudgetExceeded.is_ring0_priority());
215        assert!(YieldReason::Ring0Priority.is_ring0_priority());
216        assert!(!YieldReason::QueueEmpty.is_ring0_priority());
217    }
218
219    #[test]
220    fn test_is_budget_related() {
221        assert!(YieldReason::BudgetExceeded.is_budget_related());
222        assert!(!YieldReason::Ring0Priority.is_budget_related());
223        assert!(!YieldReason::QueueEmpty.is_budget_related());
224    }
225
226    #[test]
227    fn test_equality() {
228        assert_eq!(YieldReason::BudgetExceeded, YieldReason::BudgetExceeded);
229        assert_ne!(YieldReason::BudgetExceeded, YieldReason::Ring0Priority);
230    }
231
232    #[test]
233    fn test_clone() {
234        let reason = YieldReason::Ring0Priority;
235        let cloned = reason;
236        assert_eq!(reason, cloned);
237    }
238
239    #[test]
240    fn test_debug() {
241        let debug = format!("{:?}", YieldReason::BudgetExceeded);
242        assert!(debug.contains("BudgetExceeded"));
243    }
244}