use crate::core::types::DynError;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Slot {
pub server: usize,
}
#[derive(Clone, Debug, Default)]
pub struct Continuum {
slots: Vec<Slot>,
}
#[derive(Clone, Debug)]
pub struct ServerSpec {
pub name: String,
pub weight: u32,
}
impl Continuum {
pub fn build(servers: &[ServerSpec]) -> Result<Self, DynError> {
let total: usize = servers.iter().map(|s| s.weight as usize).sum();
let mut slots = Vec::with_capacity(total);
for (idx, server) in servers.iter().enumerate() {
for _ in 0..server.weight {
slots.push(Slot { server: idx });
}
}
Ok(Self { slots })
}
#[must_use]
pub fn slots(&self) -> &[Slot] {
&self.slots
}
#[must_use]
pub fn len(&self) -> usize {
self.slots.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.slots.is_empty()
}
pub fn dispatch(&self, hash: u32) -> Result<usize, DynError> {
if self.slots.is_empty() {
return Err(DynError::Generic("empty modula continuum".into()));
}
let i = (hash as usize) % self.slots.len();
Ok(self.slots[i].server)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn equal_servers(n: usize) -> Vec<ServerSpec> {
(0..n)
.map(|i| ServerSpec {
name: format!("s-{i}"),
weight: 1,
})
.collect()
}
#[test]
fn empty_input_yields_empty_continuum() {
let c = Continuum::build(&[]).unwrap();
assert!(c.is_empty());
assert!(c.dispatch(0).is_err());
}
#[test]
fn equal_weight_dispatches_modulo() {
let c = Continuum::build(&equal_servers(4)).unwrap();
for h in 0u32..32 {
assert_eq!(c.dispatch(h).unwrap(), (h as usize) % 4);
}
}
#[test]
fn weights_expand_slots() {
let servers = vec![
ServerSpec {
name: "a".into(),
weight: 3,
},
ServerSpec {
name: "b".into(),
weight: 1,
},
];
let c = Continuum::build(&servers).unwrap();
assert_eq!(c.len(), 4);
assert_eq!(c.dispatch(0).unwrap(), 0);
assert_eq!(c.dispatch(1).unwrap(), 0);
assert_eq!(c.dispatch(2).unwrap(), 0);
assert_eq!(c.dispatch(3).unwrap(), 1);
assert_eq!(c.dispatch(4).unwrap(), 0);
}
#[test]
fn dispatch_is_deterministic() {
let c = Continuum::build(&equal_servers(3)).unwrap();
for h in [0xdead_beef_u32, 1, 0, u32::MAX] {
assert_eq!(c.dispatch(h).unwrap(), c.dispatch(h).unwrap());
}
}
}