#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SourcePlan {
pub priority: u32,
pub max_connections: Option<usize>,
}
impl Default for SourcePlan {
fn default() -> Self {
SourcePlan {
priority: crate::sched::NO_PRIORITY,
max_connections: None,
}
}
}
impl SourcePlan {
pub fn ranked(priority: u32) -> Self {
SourcePlan {
priority,
..Default::default()
}
}
fn cap(&self, per_host: usize) -> usize {
let client = per_host.max(1);
match self.max_connections {
Some(n) => n.clamp(1, client),
None => client,
}
}
fn weight(&self) -> f64 {
1.0 / (self.priority.max(1) as f64)
}
}
pub fn allocate(
sources: &[SourcePlan],
requested: usize,
per_host: usize,
total: usize,
) -> Vec<usize> {
let n = sources.len();
let mut out = vec![0usize; n];
if n == 0 {
return out;
}
let budget = requested.max(1).min(total.max(1));
let caps: Vec<usize> = sources.iter().map(|s| s.cap(per_host)).collect();
let mut order: Vec<usize> = (0..n).collect();
order.sort_by_key(|&i| (sources[i].priority, i));
let seated = n.min(budget);
for &i in order.iter().take(seated) {
out[i] = 1;
}
let mut left = budget - seated;
while left > 0 {
let mut best: Option<(usize, f64)> = None;
for &i in order.iter().take(seated) {
if out[i] >= caps[i] {
continue;
}
let score = sources[i].weight() / (out[i] + 1) as f64;
if best.map(|(_, b)| score > b).unwrap_or(true) {
best = Some((i, score));
}
}
let Some((i, _)) = best else {
break;
};
out[i] += 1;
left -= 1;
}
out
}
pub fn reserves(sources: &[SourcePlan], allocation: &[usize]) -> Vec<usize> {
let mut idx: Vec<usize> = (0..sources.len())
.filter(|&i| allocation.get(i).copied().unwrap_or(0) == 0)
.collect();
idx.sort_by_key(|&i| (sources[i].priority, i));
idx
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sched::NO_PRIORITY;
fn flat(n: usize) -> Vec<SourcePlan> {
vec![SourcePlan::default(); n]
}
#[test]
fn an_unranked_list_splits_evenly_and_the_remainder_leads() {
assert_eq!(allocate(&flat(2), 5, 8, 16), vec![3, 2]);
assert_eq!(allocate(&flat(1), 4, 8, 16), vec![4]);
assert_eq!(allocate(&flat(4), 4, 8, 16), vec![1, 1, 1, 1]);
}
#[test]
fn a_ranking_shifts_share_without_starving_the_rest() {
let s = vec![SourcePlan::ranked(1), SourcePlan::ranked(4)];
let got = allocate(&s, 6, 8, 16);
assert_eq!(got.iter().sum::<usize>(), 6);
assert!(got[0] > got[1], "rank 1 must lead rank 4: {got:?}");
assert!(got[1] >= 1, "rank 4 must not be starved: {got:?}");
}
#[test]
fn the_output_is_in_input_order_not_rank_order() {
let s = vec![SourcePlan::ranked(9), SourcePlan::ranked(1)];
let got = allocate(&s, 4, 8, 16);
assert!(got[1] > got[0], "the better mirror is at index 1: {got:?}");
}
#[test]
fn the_aggregate_ceiling_is_never_multiplied_by_the_mirror_count() {
for n in 1..8usize {
let got = allocate(&flat(n), 8, 8, 2);
assert_eq!(got.iter().sum::<usize>(), 2, "n={n} {got:?}");
}
}
#[test]
fn a_mirrors_own_stated_ceiling_narrows_but_never_widens_the_clients() {
let s = vec![
SourcePlan {
priority: 1,
max_connections: Some(1),
},
SourcePlan::ranked(2),
];
let got = allocate(&s, 6, 4, 16);
assert_eq!(got[0], 1, "the stated ceiling binds: {got:?}");
assert!(got[1] > 1);
let greedy = vec![SourcePlan {
priority: 1,
max_connections: Some(64),
}];
assert_eq!(allocate(&greedy, 16, 4, 16), vec![4]);
}
#[test]
fn surplus_budget_is_dropped_rather_than_spent_on_an_unseated_host() {
let s = vec![
SourcePlan {
priority: 1,
max_connections: Some(1),
},
SourcePlan {
priority: 2,
max_connections: Some(1),
},
SourcePlan::ranked(3),
];
let got = allocate(&s, 2, 4, 16);
assert_eq!(got, vec![1, 1, 0]);
assert_eq!(got.iter().sum::<usize>(), 2);
}
#[test]
fn more_mirrors_than_sockets_leaves_a_reserve_bench_in_rank_order() {
let s: Vec<SourcePlan> = (0..19).map(|i| SourcePlan::ranked(19 - i as u32)).collect();
let got = allocate(&s, 4, 4, 16);
assert_eq!(got.iter().sum::<usize>(), 4);
assert_eq!(got.iter().filter(|&&n| n > 0).count(), 4);
assert!(got[15..].iter().all(|&n| n > 0), "{got:?}");
let bench = reserves(&s, &got);
assert_eq!(bench.len(), 15);
assert_eq!(s[bench[0]].priority, 5);
assert!(bench.iter().all(|&i| got[i] == 0));
}
#[test]
fn allocation_is_deterministic_across_runs() {
let s = vec![
SourcePlan::ranked(3),
SourcePlan::ranked(3),
SourcePlan::ranked(3),
];
let first = allocate(&s, 7, 4, 16);
for _ in 0..50 {
assert_eq!(allocate(&s, 7, 4, 16), first);
}
assert!(first[0] >= first[1] && first[1] >= first[2], "{first:?}");
}
#[test]
fn degenerate_inputs_do_not_panic_or_over_allocate() {
assert!(allocate(&[], 4, 4, 16).is_empty());
assert_eq!(allocate(&flat(1), 0, 4, 16).iter().sum::<usize>(), 1);
assert_eq!(allocate(&flat(3), 4, 0, 16).iter().sum::<usize>(), 3);
assert_eq!(allocate(&flat(3), 4, 4, 0).iter().sum::<usize>(), 1);
assert_eq!(
allocate(&[SourcePlan::ranked(NO_PRIORITY)], 3, 4, 16),
allocate(&flat(1), 3, 4, 16)
);
}
}