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}