loadwise_core/strategy/
round_robin.rs1use std::sync::atomic::{AtomicUsize, Ordering};
2
3use super::{SelectionContext, Strategy};
4
5#[derive(Debug)]
7pub struct RoundRobin {
8 counter: AtomicUsize,
9}
10
11impl RoundRobin {
12 pub fn new() -> Self {
13 Self {
14 counter: AtomicUsize::new(0),
15 }
16 }
17}
18
19impl Default for RoundRobin {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl<N> Strategy<N> for RoundRobin {
26 fn select(&self, candidates: &[N], ctx: &SelectionContext) -> Option<usize> {
27 if candidates.is_empty() {
28 return None;
29 }
30 let len = candidates.len();
31 let start = self.counter.fetch_add(1, Ordering::Relaxed);
32 for i in 0..len {
33 let idx = (start + i) % len;
34 if !ctx.is_excluded(idx) {
35 return Some(idx);
36 }
37 }
38 None
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 #[test]
47 fn cycles_through_candidates() {
48 let rr = RoundRobin::new();
49 let nodes = [1, 2, 3];
50 let ctx = SelectionContext::default();
51
52 assert_eq!(rr.select(&nodes, &ctx), Some(0));
53 assert_eq!(rr.select(&nodes, &ctx), Some(1));
54 assert_eq!(rr.select(&nodes, &ctx), Some(2));
55 assert_eq!(rr.select(&nodes, &ctx), Some(0));
56 }
57
58 #[test]
59 fn empty_returns_none() {
60 let rr = RoundRobin::new();
61 let nodes: [i32; 0] = [];
62 assert_eq!(rr.select(&nodes, &SelectionContext::default()), None);
63 }
64
65 #[test]
66 fn skips_excluded() {
67 let rr = RoundRobin::new();
68 let nodes = [1, 2, 3];
69 let ctx = SelectionContext::builder().exclude(vec![1]).build();
70
71 assert_eq!(rr.select(&nodes, &ctx), Some(0));
73 assert_eq!(rr.select(&nodes, &ctx), Some(2));
75 assert_eq!(rr.select(&nodes, &ctx), Some(2));
77 assert_eq!(rr.select(&nodes, &ctx), Some(0));
79 }
80
81 #[test]
82 fn all_excluded_returns_none() {
83 let rr = RoundRobin::new();
84 let nodes = [1, 2];
85 let ctx = SelectionContext::builder().exclude(vec![0, 1]).build();
86 assert_eq!(rr.select(&nodes, &ctx), None);
87 }
88}