use tokio::signal::unix::{signal, SignalKind};
use tokio::sync::broadcast;
use tracing::info;
#[derive(Debug)]
pub struct Shutdown {
is_shutdown: bool,
sender: broadcast::Sender<()>,
receiver: broadcast::Receiver<()>,
}
impl Shutdown {
pub fn new() -> Shutdown {
let (sender, receiver) = broadcast::channel(1);
Self {
is_shutdown: false,
sender,
receiver,
}
}
pub fn is_shutdown(&self) -> bool {
self.is_shutdown
}
pub fn trigger(&self) {
let _ = self.sender.send(());
}
pub async fn recv(&mut self) {
if self.is_shutdown {
return;
}
let _ = self.receiver.recv().await;
self.is_shutdown = true;
}
}
impl Default for Shutdown {
fn default() -> Self {
Self::new()
}
}
impl Clone for Shutdown {
fn clone(&self) -> Self {
let sender = self.sender.clone();
let receiver = self.sender.subscribe();
Self {
is_shutdown: self.is_shutdown,
sender,
receiver,
}
}
}
pub async fn shutdown_signal() {
let mut sigint = signal(SignalKind::interrupt()).unwrap();
let mut sigterm = signal(SignalKind::terminate()).unwrap();
let mut sigquit = signal(SignalKind::quit()).unwrap();
tokio::select! {
_ = sigint.recv() => {
info!("received SIGINT, shutting down");
},
_ = sigterm.recv() => {
info!("received SIGTERM, shutting down");
}
_ = sigquit.recv() => {
info!("received SIGQUIT, shutting down");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::time::{sleep, Duration};
#[tokio::test]
async fn test_shutdown_trigger_and_recv() {
let mut shutdown = Shutdown::new();
let shutdown_clone = shutdown.clone();
tokio::spawn(async move {
sleep(Duration::from_millis(10)).await;
shutdown_clone.trigger();
});
shutdown.recv().await;
assert!(shutdown.is_shutdown());
}
#[tokio::test]
async fn test_shutdown_multiple_receivers() {
let mut shutdown1 = Shutdown::new();
let mut shutdown2 = shutdown1.clone();
let mut shutdown3 = shutdown1.clone();
shutdown1.trigger();
shutdown1.recv().await;
shutdown2.recv().await;
shutdown3.recv().await;
assert!(shutdown1.is_shutdown());
assert!(shutdown2.is_shutdown());
assert!(shutdown3.is_shutdown());
}
#[tokio::test]
async fn test_shutdown_clone_behavior() {
let mut shutdown1 = Shutdown::new();
shutdown1.trigger();
shutdown1.recv().await;
assert!(shutdown1.is_shutdown());
let shutdown2 = shutdown1.clone();
assert_eq!(shutdown1.is_shutdown(), shutdown2.is_shutdown());
let mut shutdown3 = Shutdown::new();
let mut shutdown4 = shutdown3.clone();
shutdown3.trigger();
shutdown3.recv().await;
shutdown4.recv().await;
assert!(shutdown3.is_shutdown());
assert!(shutdown4.is_shutdown());
}
#[tokio::test]
async fn test_shutdown_already_triggered() {
let mut shutdown = Shutdown::new();
shutdown.trigger();
shutdown.recv().await;
assert!(shutdown.is_shutdown());
let start = std::time::Instant::now();
shutdown.recv().await;
let elapsed = start.elapsed();
assert!(elapsed < Duration::from_millis(5));
}
}