loadwise_core/strategy/
least_load.rs1use super::{SelectionContext, Strategy};
2use crate::LoadMetric;
3
4#[derive(Debug)]
6pub struct LeastLoad;
7
8impl LeastLoad {
9 pub fn new() -> Self {
10 Self
11 }
12}
13
14impl Default for LeastLoad {
15 fn default() -> Self {
16 Self
17 }
18}
19
20impl<N: LoadMetric> Strategy<N> for LeastLoad {
21 fn select(&self, candidates: &[N], ctx: &SelectionContext) -> Option<usize> {
22 if candidates.is_empty() {
23 return None;
24 }
25
26 let mut best_idx = None;
27 let mut best_score = f64::INFINITY;
28
29 for (i, node) in candidates.iter().enumerate() {
30 if ctx.is_excluded(i) {
31 continue;
32 }
33 let score = node.load_score();
34 if score < best_score {
35 best_score = score;
36 best_idx = Some(i);
37 }
38 }
39
40 best_idx
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47
48 struct L(f64);
49 impl LoadMetric for L {
50 fn load_score(&self) -> f64 {
51 self.0
52 }
53 }
54
55 #[test]
56 fn picks_lowest_load() {
57 let ll = LeastLoad::new();
58 let nodes = [L(3.0), L(1.0), L(2.0)];
59 assert_eq!(ll.select(&nodes, &SelectionContext::default()), Some(1));
60 }
61
62 #[test]
63 fn picks_first_on_tie() {
64 let ll = LeastLoad::new();
65 let nodes = [L(1.0), L(1.0), L(2.0)];
66 assert_eq!(ll.select(&nodes, &SelectionContext::default()), Some(0));
67 }
68
69 #[test]
70 fn skips_excluded_picks_next_best() {
71 let ll = LeastLoad::new();
72 let nodes = [L(1.0), L(2.0), L(3.0)];
73 let ctx = SelectionContext::builder().exclude(vec![0]).build();
75 assert_eq!(ll.select(&nodes, &ctx), Some(1));
76 }
77}