use onlyne_proto::Report;
use std::collections::{HashMap, HashSet};
use std::time::{Duration, Instant};
pub const STALLED: &str = "stalled";
pub const STALLED_REASON: &str = "no applied progress";
#[derive(Debug, Default)]
pub struct StallWatch {
last_progress: HashMap<String, Instant>,
reported: HashSet<String>,
}
impl StallWatch {
pub fn new() -> Self {
Self::default()
}
pub fn note_assigned(&mut self, task_id: impl Into<String>, now: Instant) {
self.last_progress.entry(task_id.into()).or_insert(now);
}
pub fn note_applied(&mut self, task_id: &str, now: Instant) {
if let Some(last_progress) = self.last_progress.get_mut(task_id) {
*last_progress = now;
self.reported.remove(task_id);
}
}
pub fn forget(&mut self, task_id: &str) {
self.last_progress.remove(task_id);
self.reported.remove(task_id);
}
pub fn due(&self, now: Instant, threshold_secs: u64) -> Vec<String> {
if threshold_secs == 0 {
return Vec::new();
}
let limit = Duration::from_secs(threshold_secs);
let mut due: Vec<String> = self
.last_progress
.iter()
.filter(|(task_id, _)| !self.reported.contains(*task_id))
.filter(|(_, last)| now.saturating_duration_since(**last) > limit)
.map(|(task_id, _)| task_id.clone())
.collect();
due.sort();
due
}
pub fn mark_reported(&mut self, task_id: &str) {
self.reported.insert(task_id.to_string());
}
}
pub fn report(
task_id: &str,
session_id: Option<String>,
generation: Option<u64>,
seq: Option<u64>,
) -> Report {
Report::Fault {
task_id: Some(task_id.to_string()),
session_id,
generation,
seq,
kind: STALLED.to_string(),
reason: STALLED_REASON.to_string(),
desired: None,
observed: None,
}
}
#[cfg(test)]
mod tests;