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 recv_returns_after_trigger() {
let mut shutdown = Shutdown::new();
assert!(!shutdown.is_shutdown());
let trigger = shutdown.clone();
tokio::spawn(async move {
sleep(Duration::from_millis(10)).await;
trigger.trigger();
});
shutdown.recv().await;
assert!(shutdown.is_shutdown());
}
#[tokio::test]
async fn trigger_wakes_every_clone() {
let mut shutdown = Shutdown::new();
let mut clones = vec![shutdown.clone(), shutdown.clone()];
shutdown.trigger();
shutdown.recv().await;
assert!(shutdown.is_shutdown());
for clone in &mut clones {
clone.recv().await;
assert!(clone.is_shutdown());
}
}
#[tokio::test]
async fn clone_copies_the_shutdown_flag() {
let mut shutdown = Shutdown::new();
shutdown.trigger();
shutdown.recv().await;
let clone = shutdown.clone();
assert!(clone.is_shutdown());
}
#[tokio::test]
async fn recv_returns_immediately_once_shutdown() {
let mut shutdown = Shutdown::new();
shutdown.trigger();
shutdown.recv().await;
let start = std::time::Instant::now();
shutdown.recv().await;
assert!(start.elapsed() < Duration::from_millis(5));
}
}