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