const NIL: u32 = u32::MAX;
pub(super) fn retirement_key(team_tick: u64) -> u64 {
team_tick.max(1)
}
pub(super) fn due_key(now: u64, max_downtime: u32) -> Option<u64> {
now.checked_sub(u64::from(max_downtime) + 1)
}
#[derive(Debug, Default)]
pub(super) struct RetirementRing {
heads: Vec<u32>,
prev: Vec<u32>,
next: Vec<u32>,
key: Vec<u64>,
drained: u64,
}
impl RetirementRing {
pub fn prepare(&mut self, max_downtime: u32, live: &[u32], team_tick: &[u64]) {
let needed = (max_downtime as usize + 2).next_power_of_two();
if self.heads.len() >= needed {
return;
}
self.heads.clear();
self.heads.resize(needed, NIL);
for &i in live {
self.insert(i, retirement_key(team_tick[i as usize]));
}
}
fn bucket(&self, key: u64) -> usize {
(key & (self.heads.len() as u64 - 1)) as usize
}
pub fn insert(&mut self, node: u32, key: u64) {
let i = node as usize;
if i >= self.key.len() {
self.prev.resize(i + 1, NIL);
self.next.resize(i + 1, NIL);
self.key.resize(i + 1, 0);
}
let b = self.bucket(key);
let head = self.heads[b];
self.prev[i] = NIL;
self.next[i] = head;
if head != NIL {
self.prev[head as usize] = node;
}
self.heads[b] = node;
self.key[i] = key;
}
pub fn remove(&mut self, node: u32) {
let i = node as usize;
let (prev, next) = (self.prev[i], self.next[i]);
if prev == NIL {
let b = self.bucket(self.key[i]);
self.heads[b] = next;
} else {
self.next[prev as usize] = next;
}
if next != NIL {
self.prev[next as usize] = prev;
}
}
pub fn drain_through(&mut self, due: u64, out: &mut Vec<u32>) {
while self.drained < due {
self.drained += 1;
let b = self.bucket(self.drained);
let mut node = std::mem::replace(&mut self.heads[b], NIL);
while node != NIL {
out.push(node);
node = self.next[node as usize];
}
}
}
pub fn heap_bytes(&self) -> usize {
(self.heads.capacity() + self.prev.capacity() + self.next.capacity()) * size_of::<u32>()
+ self.key.capacity() * size_of::<u64>()
}
}
#[cfg(test)]
mod tests {
use super::{RetirementRing, due_key, retirement_key};
fn drained(ring: &mut RetirementRing, due: u64) -> Vec<u32> {
let mut out = Vec::new();
ring.drain_through(due, &mut out);
out.sort_unstable();
out
}
#[test]
fn a_node_is_drained_from_the_bucket_it_was_last_filed_under() {
let mut ring = RetirementRing::default();
ring.prepare(3, &[0, 1, 2, 3], &[1, 1, 1, 1]);
ring.remove(2);
ring.insert(2, 3);
ring.remove(0);
ring.insert(0, 4);
assert_eq!(drained(&mut ring, 1), [1, 3]);
assert!(drained(&mut ring, 2).is_empty());
assert_eq!(drained(&mut ring, 3), [2]);
assert_eq!(drained(&mut ring, 4), [0]);
assert!(drained(&mut ring, 4).is_empty(), "a key was drained twice");
}
#[test]
fn removing_the_head_of_a_bucket_keeps_the_rest() {
let mut ring = RetirementRing::default();
ring.prepare(5, &[0, 1, 2], &[2, 2, 2]);
ring.remove(2);
ring.remove(0);
assert_eq!(drained(&mut ring, 2), [1]);
}
#[test]
fn a_lower_max_downtime_drains_every_key_it_makes_overdue() {
let mut ring = RetirementRing::default();
let team_tick = [2, 8, 10, 1];
ring.prepare(20, &[0, 1, 2, 3], &team_tick);
assert!(drained(&mut ring, due_key(10, 20).unwrap_or(0)).is_empty());
ring.prepare(4, &[0, 1, 2, 3], &team_tick);
let due = due_key(10, 4).expect("tick 10 is past the downtime");
assert_eq!(due, 5);
assert_eq!(
drained(&mut ring, due),
[0, 3],
"the nodes idle since ticks 1 and 2 are overdue"
);
assert_eq!(drained(&mut ring, 8), [1]);
assert_eq!(drained(&mut ring, 10), [2]);
}
#[test]
fn a_higher_max_downtime_lays_the_buckets_out_again() {
let mut ring = RetirementRing::default();
let team_tick = [1, 3, 6];
ring.prepare(2, &[0, 1, 2], &team_tick);
ring.prepare(100, &[0, 1, 2], &team_tick);
assert_eq!(drained(&mut ring, 1), [0]);
assert_eq!(drained(&mut ring, 5), [1]);
assert_eq!(drained(&mut ring, 6), [2]);
}
#[test]
fn setup_nodes_retire_one_tick_later_than_their_team_tick_says() {
assert_eq!(retirement_key(0), 1);
assert_eq!(retirement_key(7), 7);
assert_eq!(due_key(3, 3), None);
assert_eq!(due_key(5, 3), Some(1));
assert_eq!(due_key(9, u32::MAX), None);
}
}