use rustc_hash::FxHashMap as HashMap;
use std::sync::Mutex;
use crate::sched::ProcessId;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum RepeatOutcome {
StillWaiting,
Satisfied,
}
pub struct RepeatCounters {
counters: Mutex<HashMap<(ProcessId, usize), u64>>,
}
impl RepeatCounters {
pub fn new() -> Self {
Self {
counters: Mutex::new(HashMap::default()),
}
}
pub fn on_term_fired(
&self,
proc: ProcessId,
term_index: usize,
declared_total: u64,
) -> RepeatOutcome {
let mut counters = self.counters.lock().expect("RepeatCounters mutex poisoned");
let remaining = counters.entry((proc, term_index)).or_insert(declared_total);
*remaining = remaining.saturating_sub(1);
if *remaining == 0 {
counters.remove(&(proc, term_index));
RepeatOutcome::Satisfied
} else {
RepeatOutcome::StillWaiting
}
}
pub fn clear(&self, proc: ProcessId) {
let mut counters = self.counters.lock().expect("RepeatCounters mutex poisoned");
counters.retain(|&(p, _), _| p != proc);
}
}
impl Default for RepeatCounters {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn first_and_second_firing_of_a_three_count_repeat_report_still_waiting_third_reports_satisfied()
{
let counters = RepeatCounters::new();
let proc: ProcessId = 1;
assert_eq!(
counters.on_term_fired(proc, 0, 3),
RepeatOutcome::StillWaiting
);
assert_eq!(
counters.on_term_fired(proc, 0, 3),
RepeatOutcome::StillWaiting
);
assert_eq!(counters.on_term_fired(proc, 0, 3), RepeatOutcome::Satisfied);
}
#[test]
fn two_different_term_indices_for_the_same_process_count_independently() {
let counters = RepeatCounters::new();
let proc: ProcessId = 7;
assert_eq!(
counters.on_term_fired(proc, 0, 2),
RepeatOutcome::StillWaiting
);
assert_eq!(
counters.on_term_fired(proc, 1, 3),
RepeatOutcome::StillWaiting
);
assert_eq!(
counters.on_term_fired(proc, 1, 3),
RepeatOutcome::StillWaiting
);
assert_eq!(
counters.on_term_fired(proc, 0, 2),
RepeatOutcome::Satisfied,
"term 0 must reach Satisfied at its own declared count (2), unaffected by \
term 1's firings"
);
assert_eq!(
counters.on_term_fired(proc, 1, 3),
RepeatOutcome::Satisfied,
"term 1 must reach Satisfied at its own declared count (3)"
);
}
#[test]
fn clear_removes_only_the_named_processs_entries() {
let counters = RepeatCounters::new();
let proc_a: ProcessId = 1;
let proc_b: ProcessId = 2;
assert_eq!(
counters.on_term_fired(proc_a, 0, 3),
RepeatOutcome::StillWaiting
);
assert_eq!(
counters.on_term_fired(proc_b, 0, 5),
RepeatOutcome::StillWaiting
);
counters.clear(proc_a);
assert_eq!(
counters.on_term_fired(proc_a, 0, 3),
RepeatOutcome::StillWaiting
);
assert_eq!(
counters.on_term_fired(proc_a, 0, 3),
RepeatOutcome::StillWaiting
);
assert_eq!(
counters.on_term_fired(proc_a, 0, 3),
RepeatOutcome::Satisfied
);
for _ in 0..3 {
assert_eq!(
counters.on_term_fired(proc_b, 0, 5),
RepeatOutcome::StillWaiting,
"proc_b's own in-progress count must survive proc_a's clear untouched"
);
}
assert_eq!(
counters.on_term_fired(proc_b, 0, 5),
RepeatOutcome::Satisfied
);
}
}