Skip to main content

rust_supervisor/runtime/
watchdog.rs

1//! Runtime control loop watchdog.
2//!
3//! This module consumes a `JoinHandle` and writes its one-shot exit result into
4//! `RuntimeControlPlane` so public handles can read the final report repeatedly.
5
6use crate::runtime::lifecycle::{RuntimeControlPlane, RuntimeControlPlaneState, RuntimeExitReport};
7use tokio::sync::broadcast;
8use tokio::task::JoinHandle;
9
10/// Watchdog that observes runtime control loop exit results.
11#[derive(Debug, Clone, Copy)]
12pub struct RuntimeWatchdog;
13
14impl RuntimeWatchdog {
15    /// Publishes a control loop started event.
16    ///
17    /// # Arguments
18    ///
19    /// - `control_plane`: Control plane that should become alive.
20    /// - `event_sender`: Event channel used for diagnostic text.
21    ///
22    /// # Returns
23    ///
24    /// This function does not return a value.
25    pub fn publish_started(
26        control_plane: RuntimeControlPlane,
27        event_sender: broadcast::Sender<String>,
28    ) {
29        control_plane.mark_alive();
30        let _ignored = event_sender.send("runtime_control_loop_started:startup".to_owned());
31    }
32
33    /// Spawns the background watchdog.
34    ///
35    /// # Arguments
36    ///
37    /// - `control_plane`: Control plane that stores the final report.
38    /// - `join_handle`: Runtime control loop task handle.
39    /// - `event_sender`: Event channel used for diagnostic text.
40    ///
41    /// # Returns
42    ///
43    /// This function does not return a value.
44    pub fn spawn(
45        control_plane: RuntimeControlPlane,
46        join_handle: JoinHandle<RuntimeExitReport>,
47        event_sender: broadcast::Sender<String>,
48    ) {
49        tokio::spawn(async move {
50            let report = match join_handle.await {
51                Ok(report) => report,
52                Err(error) => RuntimeExitReport::failed(
53                    "watchdog",
54                    format!("runtime control loop panic or cancellation: {error}"),
55                    error.is_panic(),
56                    true,
57                ),
58            };
59            let report = control_plane.complete(report);
60            let event_name = match report.state {
61                RuntimeControlPlaneState::Completed => "runtime_control_loop_completed",
62                RuntimeControlPlaneState::Failed => "runtime_control_loop_failed",
63                RuntimeControlPlaneState::Starting
64                | RuntimeControlPlaneState::Alive
65                | RuntimeControlPlaneState::ShuttingDown => "runtime_control_loop_unexpected",
66            };
67            let _ignored =
68                event_sender.send(format!("{event_name}:{}:{}", report.phase, report.reason));
69        });
70    }
71}