mermaid_cli/app/
lifecycle.rs1use tokio::sync::mpsc;
9
10use crate::domain::{Msg, RuntimeSignal};
11
12pub struct RuntimeLifecycle {
14 rx: mpsc::UnboundedReceiver<RuntimeSignal>,
15}
16
17impl RuntimeLifecycle {
18 pub fn new() -> Self {
19 let (tx, rx) = mpsc::unbounded_channel();
20 spawn_signal_tasks(tx);
21 Self { rx }
22 }
23
24 pub async fn next_msg(&mut self) -> Option<Msg> {
25 self.rx.recv().await.map(Msg::RuntimeSignal)
26 }
27}
28
29impl Default for RuntimeLifecycle {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35fn spawn_signal_tasks(tx: mpsc::UnboundedSender<RuntimeSignal>) {
36 let ctrl_c_tx = tx.clone();
37 tokio::spawn(async move {
38 if tokio::signal::ctrl_c().await.is_ok() {
39 let _ = ctrl_c_tx.send(RuntimeSignal::Interrupt);
40 }
41 });
42
43 spawn_unix_signal_tasks(tx);
44}
45
46#[cfg(unix)]
47fn spawn_unix_signal_tasks(tx: mpsc::UnboundedSender<RuntimeSignal>) {
48 use tokio::signal::unix::{SignalKind, signal};
49
50 let terminate_tx = tx.clone();
51 tokio::spawn(async move {
52 if let Ok(mut sigterm) = signal(SignalKind::terminate())
53 && sigterm.recv().await.is_some()
54 {
55 let _ = terminate_tx.send(RuntimeSignal::Terminate);
56 }
57 });
58
59 tokio::spawn(async move {
60 if let Ok(mut sighup) = signal(SignalKind::hangup())
61 && sighup.recv().await.is_some()
62 {
63 let _ = tx.send(RuntimeSignal::Hangup);
64 }
65 });
66}
67
68#[cfg(not(unix))]
69fn spawn_unix_signal_tasks(_tx: mpsc::UnboundedSender<RuntimeSignal>) {}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[tokio::test]
76 async fn lifecycle_wraps_signal_as_reducer_msg() {
77 let (tx, rx) = mpsc::unbounded_channel();
78 let mut lifecycle = RuntimeLifecycle { rx };
79 tx.send(RuntimeSignal::Terminate).expect("send signal");
80
81 let msg = lifecycle.next_msg().await.expect("signal msg");
82 assert!(matches!(msg, Msg::RuntimeSignal(RuntimeSignal::Terminate)));
83 }
84}