Skip to main content

hyperion_framework/heartbeat/
handler.rs

1// -------------------------------------------------------------------------------------------------
2// Hyperion Framework
3// https://github.com/robert-hannah/hyperion-framework
4//
5// A lightweight component-based TCP framework for building service-oriented Rust applications with
6// CLI control, async messaging, and lifecycle management.
7//
8// Copyright 2025 Robert Hannah
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14//     http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21// -------------------------------------------------------------------------------------------------
22
23// Standard =
24use std::sync::Arc;
25use std::sync::atomic::{AtomicUsize, Ordering};
26
27// Package
28use tokio::sync::Notify;
29
30// Local
31use crate::containerisation::container_state::ContainerState;
32
33/// Called by the receiver watchdog when no heartbeat request has arrived within the timeout window.
34pub trait HeartbeatTimeoutHandler: Send + Sync + 'static {
35    fn on_timeout(&self);
36}
37
38/// Called by the sender on every heartbeat tick where a target has not responded within
39/// `response_timeout_ms`. Fires repeatedly while the target remains silent.
40pub trait HeartbeatMissedHandler: Send + Sync + 'static {
41    fn on_missed(&self, target: &str);
42}
43
44/// Wraps a `FnMut(&str)` closure as a `HeartbeatMissedHandler`.
45pub struct FnMissedHandler(Box<dyn Fn(&str) + Send + Sync>);
46
47impl FnMissedHandler {
48    pub fn new(f: impl Fn(&str) + Send + Sync + 'static) -> Self {
49        Self(Box::new(f))
50    }
51}
52
53impl HeartbeatMissedHandler for FnMissedHandler {
54    fn on_missed(&self, target: &str) {
55        (self.0)(target);
56    }
57}
58
59/// Wraps a closure as a `HeartbeatTimeoutHandler`, allowing per-component custom behaviour to be
60/// defined inline in `main.rs` without needing a dedicated struct.
61pub struct FnHandler(Box<dyn Fn() + Send + Sync>);
62
63impl FnHandler {
64    pub fn new(f: impl Fn() + Send + Sync + 'static) -> Self {
65        Self(Box::new(f))
66    }
67}
68
69impl HeartbeatTimeoutHandler for FnHandler {
70    fn on_timeout(&self) {
71        (self.0)();
72    }
73}
74
75/// Convenience handler: initiates a graceful container shutdown.
76pub struct ShutdownOnTimeout {
77    container_state: Arc<AtomicUsize>,
78    container_state_notify: Arc<Notify>,
79}
80
81impl ShutdownOnTimeout {
82    pub fn new(container_state: Arc<AtomicUsize>, container_state_notify: Arc<Notify>) -> Self {
83        Self {
84            container_state,
85            container_state_notify,
86        }
87    }
88}
89
90impl HeartbeatTimeoutHandler for ShutdownOnTimeout {
91    fn on_timeout(&self) {
92        log::warn!(
93            "HeartbeatReceiver: timeout — no request received within window. Initiating shutdown."
94        );
95        self.container_state
96            .store(ContainerState::ShuttingDown as usize, Ordering::SeqCst);
97        self.container_state_notify.notify_waiters();
98    }
99}