ranked_semaphore/
config.rs

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