use tokio::sync::mpsc;
use crate::domain::{Msg, RuntimeSignal};
pub struct RuntimeLifecycle {
rx: mpsc::UnboundedReceiver<RuntimeSignal>,
}
impl RuntimeLifecycle {
pub fn new() -> Self {
let (tx, rx) = mpsc::unbounded_channel();
spawn_signal_tasks(tx);
Self { rx }
}
pub async fn next_msg(&mut self) -> Option<Msg> {
self.rx.recv().await.map(Msg::RuntimeSignal)
}
}
impl Default for RuntimeLifecycle {
fn default() -> Self {
Self::new()
}
}
fn spawn_signal_tasks(tx: mpsc::UnboundedSender<RuntimeSignal>) {
let ctrl_c_tx = tx.clone();
tokio::spawn(async move {
loop {
if tokio::signal::ctrl_c().await.is_err() {
break;
}
if ctrl_c_tx.send(RuntimeSignal::Interrupt).is_err() {
break; }
}
});
spawn_unix_signal_tasks(tx);
}
#[cfg(unix)]
fn spawn_unix_signal_tasks(tx: mpsc::UnboundedSender<RuntimeSignal>) {
use tokio::signal::unix::{SignalKind, signal};
let terminate_tx = tx.clone();
tokio::spawn(async move {
if let Ok(mut sigterm) = signal(SignalKind::terminate()) {
while sigterm.recv().await.is_some() {
if terminate_tx.send(RuntimeSignal::Terminate).is_err() {
break;
}
}
}
});
tokio::spawn(async move {
if let Ok(mut sighup) = signal(SignalKind::hangup()) {
while sighup.recv().await.is_some() {
if tx.send(RuntimeSignal::Hangup).is_err() {
break;
}
}
}
});
}
#[cfg(not(unix))]
fn spawn_unix_signal_tasks(_tx: mpsc::UnboundedSender<RuntimeSignal>) {}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn lifecycle_wraps_signal_as_reducer_msg() {
let (tx, rx) = mpsc::unbounded_channel();
let mut lifecycle = RuntimeLifecycle { rx };
tx.send(RuntimeSignal::Terminate).expect("send signal");
let msg = lifecycle.next_msg().await.expect("signal msg");
assert!(matches!(msg, Msg::RuntimeSignal(RuntimeSignal::Terminate)));
}
}