ranked_semaphore/
config.rs

1/// Queue strategy for waiters at a given priority level.
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3pub enum QueueStrategy {
4    /// First In, First Out - waiters are served in the order they arrive.
5    Fifo,
6    /// Last In, First Out - waiters are served in reverse order of arrival.
7    Lifo,
8}
9
10/// A rule for determining queue strategy based on priority.
11#[derive(Debug, Clone)]
12pub(crate) enum PriorityRule {
13    /// Default strategy for all priorities not matched by other rules.
14    Default(QueueStrategy),
15    /// Strategy for an exact priority value.
16    Exact(isize, QueueStrategy),
17    /// Strategy for priorities within a range [min, max] (inclusive).
18    /// Use Range(x, isize::MAX) for greater-or-equal behavior.
19    /// Use Range(isize::MIN, x) for less-or-equal behavior.
20    Range(isize, isize, QueueStrategy),
21}
22
23/// Configuration for priority-based queue strategies.
24#[derive(Debug, Clone)]
25pub struct PriorityConfig {
26    pub(crate) rules: Vec<PriorityRule>,
27}
28
29impl PriorityConfig {
30    /// Create a new empty priority configuration.
31    pub fn new() -> Self {
32        Self { rules: Vec::new() }
33    }
34
35    /// Set the default queue strategy for all priorities not matched by other rules.
36    pub fn default_strategy(mut self, strategy: QueueStrategy) -> Self {
37        self.rules.push(PriorityRule::Default(strategy));
38        self
39    }
40
41    /// Set the queue strategy for an exact priority value.
42    pub fn exact(mut self, priority: isize, strategy: QueueStrategy) -> Self {
43        self.rules.push(PriorityRule::Exact(priority, strategy));
44        self
45    }
46
47    /// Set the queue strategy for priorities greater than or equal to the threshold.
48    /// This is a convenience method that creates a range [threshold, isize::MAX].
49    pub fn greater_or_equal(mut self, threshold: isize, strategy: QueueStrategy) -> Self {
50        self.rules
51            .push(PriorityRule::Range(threshold, isize::MAX, strategy));
52        self
53    }
54
55    /// Set the queue strategy for priorities greater than the threshold (exclusive).
56    /// This is a convenience method that creates a range [threshold + 1, isize::MAX].
57    pub fn greater_than(mut self, threshold: isize, strategy: QueueStrategy) -> Self {
58        let min = threshold.saturating_add(1);
59        self.rules
60            .push(PriorityRule::Range(min, isize::MAX, strategy));
61        self
62    }
63
64    /// Set the queue strategy for priorities less than or equal to the threshold.
65    /// This is a convenience method that creates a range [isize::MIN, threshold].
66    pub fn less_or_equal(mut self, threshold: isize, strategy: QueueStrategy) -> Self {
67        self.rules
68            .push(PriorityRule::Range(isize::MIN, threshold, strategy));
69        self
70    }
71
72    /// Set the queue strategy for priorities less than the threshold (exclusive).
73    /// This is a convenience method that creates a range [isize::MIN, threshold - 1].
74    pub fn less_than(mut self, threshold: isize, strategy: QueueStrategy) -> Self {
75        let max = threshold.saturating_sub(1);
76        self.rules
77            .push(PriorityRule::Range(isize::MIN, max, strategy));
78        self
79    }
80
81    /// Set the queue strategy for priorities within a range [min, max] (inclusive).
82    pub fn range(mut self, min: isize, max: isize, strategy: QueueStrategy) -> Self {
83        self.rules.push(PriorityRule::Range(min, max, strategy));
84        self
85    }
86
87    /// Resolve the queue strategy for a given priority.
88    ///
89    /// Rules are evaluated in the following order:
90    /// 1. Exact matches (highest priority)
91    /// 2. Range matches (first match wins)
92    /// 3. Default strategy
93    pub(crate) fn resolve_strategy(&self, priority: isize) -> QueueStrategy {
94        let mut default_strategy = QueueStrategy::Fifo;
95
96        // First pass: Check for exact matches
97        for rule in &self.rules {
98            match rule {
99                PriorityRule::Exact(p, strategy) if *p == priority => return *strategy,
100                PriorityRule::Default(strategy) => default_strategy = *strategy,
101                _ => {}
102            }
103        }
104
105        // Second pass: Check for range matches
106        for rule in &self.rules {
107            match rule {
108                PriorityRule::Range(min, max, strategy) if priority >= *min && priority <= *max => {
109                    return *strategy
110                }
111                _ => {}
112            }
113        }
114
115        default_strategy
116    }
117
118    /// Get the queue strategy for a specific priority.
119    pub fn strategy_for_priority(&self, priority: isize) -> QueueStrategy {
120        // Use the existing resolve_strategy method which has the correct logic
121        self.resolve_strategy(priority)
122    }
123}
124
125impl Default for PriorityConfig {
126    fn default() -> Self {
127        Self::new().default_strategy(QueueStrategy::Fifo)
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn test_exact_match() {
137        let config = PriorityConfig::new()
138            .default_strategy(QueueStrategy::Fifo)
139            .exact(5, QueueStrategy::Lifo);
140
141        assert_eq!(config.resolve_strategy(5), QueueStrategy::Lifo);
142        assert_eq!(config.resolve_strategy(4), QueueStrategy::Fifo);
143        assert_eq!(config.resolve_strategy(6), QueueStrategy::Fifo);
144    }
145
146    #[test]
147    fn test_range_match() {
148        let config = PriorityConfig::new()
149            .default_strategy(QueueStrategy::Fifo)
150            .range(1, 10, QueueStrategy::Lifo);
151
152        assert_eq!(config.resolve_strategy(0), QueueStrategy::Fifo);
153        assert_eq!(config.resolve_strategy(1), QueueStrategy::Lifo);
154        assert_eq!(config.resolve_strategy(5), QueueStrategy::Lifo);
155        assert_eq!(config.resolve_strategy(10), QueueStrategy::Lifo);
156        assert_eq!(config.resolve_strategy(11), QueueStrategy::Fifo);
157    }
158
159    #[test]
160    fn test_greater_or_equal() {
161        let config = PriorityConfig::new()
162            .default_strategy(QueueStrategy::Fifo)
163            .greater_or_equal(5, QueueStrategy::Lifo);
164
165        assert_eq!(config.resolve_strategy(4), QueueStrategy::Fifo);
166        assert_eq!(config.resolve_strategy(5), QueueStrategy::Lifo);
167        assert_eq!(config.resolve_strategy(10), QueueStrategy::Lifo);
168    }
169
170    #[test]
171    fn test_greater_than() {
172        let config = PriorityConfig::new()
173            .default_strategy(QueueStrategy::Fifo)
174            .greater_than(5, QueueStrategy::Lifo);
175
176        assert_eq!(config.resolve_strategy(5), QueueStrategy::Fifo);
177        assert_eq!(config.resolve_strategy(6), QueueStrategy::Lifo);
178        assert_eq!(config.resolve_strategy(10), QueueStrategy::Lifo);
179    }
180
181    #[test]
182    fn test_less_or_equal() {
183        let config = PriorityConfig::new()
184            .default_strategy(QueueStrategy::Lifo)
185            .less_or_equal(0, QueueStrategy::Fifo);
186
187        assert_eq!(config.resolve_strategy(-5), QueueStrategy::Fifo);
188        assert_eq!(config.resolve_strategy(0), QueueStrategy::Fifo);
189        assert_eq!(config.resolve_strategy(1), QueueStrategy::Lifo);
190    }
191
192    #[test]
193    fn test_less_than() {
194        let config = PriorityConfig::new()
195            .default_strategy(QueueStrategy::Fifo)
196            .less_than(0, QueueStrategy::Lifo);
197
198        assert_eq!(config.resolve_strategy(-5), QueueStrategy::Lifo);
199        assert_eq!(config.resolve_strategy(0), QueueStrategy::Fifo);
200        assert_eq!(config.resolve_strategy(1), QueueStrategy::Fifo);
201    }
202
203    #[test]
204    fn test_priority_order() {
205        let config = PriorityConfig::new()
206            .default_strategy(QueueStrategy::Fifo)
207            .greater_or_equal(5, QueueStrategy::Lifo)
208            .exact(10, QueueStrategy::Fifo); // Exact should override range
209
210        assert_eq!(config.resolve_strategy(5), QueueStrategy::Lifo);
211        assert_eq!(config.resolve_strategy(8), QueueStrategy::Lifo);
212        assert_eq!(config.resolve_strategy(10), QueueStrategy::Fifo); // Exact wins
213    }
214
215    #[test]
216    fn test_multiple_thresholds() {
217        let config = PriorityConfig::new()
218            .default_strategy(QueueStrategy::Fifo)
219            .range(5, isize::MAX, QueueStrategy::Lifo)
220            .range(10, isize::MAX, QueueStrategy::Fifo); // More specific range should win
221
222        assert_eq!(config.resolve_strategy(4), QueueStrategy::Fifo);
223        // First range [5, MAX] matches, so LIFO (first match wins)
224        assert_eq!(config.resolve_strategy(7), QueueStrategy::Lifo);
225        assert_eq!(config.resolve_strategy(15), QueueStrategy::Lifo);
226    }
227}