use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use serde::Serialize;
use tokio::sync::mpsc;
use tokio::time::{Duration, interval};
use crate::containerisation::traits::HyperionHeartbeatMessage;
use crate::heartbeat::handler::HeartbeatTimeoutHandler;
use crate::messages::heartbeat::HeartbeatRequest;
use crate::utilities::time::current_epoch_ms;
pub struct HeartbeatReceiver<T> {
timeout_ms: u64,
last_received_ms: Arc<AtomicU64>,
request_rx: mpsc::Receiver<HeartbeatRequest>,
all_senders: HashMap<String, mpsc::Sender<T>>, handler: Box<dyn HeartbeatTimeoutHandler>,
}
impl<T> HeartbeatReceiver<T>
where
T: HyperionHeartbeatMessage + Send + Clone + Serialize + 'static,
{
pub fn new(
timeout_ms: u64,
all_senders: HashMap<String, mpsc::Sender<T>>,
handler: Box<dyn HeartbeatTimeoutHandler>,
) -> (Self, mpsc::Sender<HeartbeatRequest>) {
let (request_tx, request_rx) = mpsc::channel(32);
(
Self {
timeout_ms,
last_received_ms: Arc::new(AtomicU64::new(current_epoch_ms())),
request_rx,
all_senders,
handler,
},
request_tx,
)
}
pub async fn run(mut self) {
let check_interval_ms = (self.timeout_ms / 3).max(1000);
let mut watchdog = interval(Duration::from_millis(check_interval_ms));
log::info!("HeartbeatReceiver running — timeout: {}ms", self.timeout_ms);
loop {
tokio::select! {
Some(request) = self.request_rx.recv() => {
self.last_received_ms.store(current_epoch_ms(), Ordering::SeqCst);
if let Some(sender) = self.all_senders.get(&request.sender_name) {
if let Some(msg) = T::make_heartbeat_response(
request.request_id,
current_epoch_ms(),
request.component_alive,
request.ms_since_last_activity,
request.container_state_val,
)
&& sender.try_send(msg).is_err() {
log::warn!("HeartbeatReceiver: failed to send response to '{}'", request.sender_name);
}
} else {
log::warn!("HeartbeatReceiver: no sender found for '{}' — cannot respond", request.sender_name);
}
}
_ = watchdog.tick() => {
let elapsed_ms = current_epoch_ms()
.saturating_sub(self.last_received_ms.load(Ordering::SeqCst));
if elapsed_ms > self.timeout_ms {
log::warn!(
"HeartbeatReceiver: {}ms since last request (timeout: {}ms) — calling handler",
elapsed_ms, self.timeout_ms
);
self.handler.on_timeout();
}
}
}
}
}
}