use tokio::signal;
use tokio::sync::broadcast;
use tokio::task::JoinHandle;
use tracing::info;
pub struct SignalHandler {
shutdown_tx: broadcast::Sender<()>,
}
impl SignalHandler {
pub fn new(shutdown_tx: broadcast::Sender<()>) -> Self {
Self { shutdown_tx }
}
pub fn setup(&self) -> JoinHandle<()> {
let shutdown_tx = self.shutdown_tx.clone();
tokio::spawn(async move {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("Failed to install Ctrl+C handler");
info!("Ctrl+C signal received");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("Failed to install signal handler")
.recv()
.await;
info!("SIGTERM signal received");
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {
info!("Received Ctrl+C, initiating shutdown");
},
_ = terminate => {
info!("Received terminate signal, initiating shutdown");
},
}
let _ = shutdown_tx.send(());
})
}
}
pub fn create_shutdown_channel() -> (broadcast::Sender<()>, broadcast::Receiver<()>) {
broadcast::channel(1)
}
pub async fn wait_for_shutdown_signal(mut shutdown_rx: broadcast::Receiver<()>) {
let _ = shutdown_rx.recv().await;
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
use tokio::time::timeout;
#[tokio::test]
async fn test_shutdown_channel_creation() {
let (tx, mut rx) = create_shutdown_channel();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(10)).await;
let _ = tx.send(());
});
let result = timeout(Duration::from_millis(100), rx.recv()).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_signal_handler_creation() {
let (tx, _rx) = create_shutdown_channel();
let handler = SignalHandler::new(tx);
let _handle = handler.setup();
tokio::time::sleep(Duration::from_millis(10)).await;
}
#[tokio::test]
async fn test_wait_for_shutdown() {
let (tx, rx) = create_shutdown_channel();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(50)).await;
let _ = tx.send(());
});
let result = timeout(Duration::from_millis(200), wait_for_shutdown_signal(rx)).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_multiple_shutdown_receivers() {
let (tx, _) = create_shutdown_channel();
let mut rx1 = tx.subscribe();
let mut rx2 = tx.subscribe();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(10)).await;
let _ = tx.send(());
});
let result1 = timeout(Duration::from_millis(100), rx1.recv()).await;
let result2 = timeout(Duration::from_millis(100), rx2.recv()).await;
assert!(result1.is_ok());
assert!(result2.is_ok());
}
}