use std::future::Future;
use tokio::sync::watch;
use tracing::error;
pub async fn shutdown_signal() {
let ctrl_c = async {
match tokio::signal::ctrl_c().await {
Ok(()) => {}
Err(source) => {
error!(%source, "cannot install Ctrl-C handler; the SIGTERM path still governs shutdown");
std::future::pending::<()>().await;
}
}
};
#[cfg(unix)]
let terminate = async {
use tokio::signal::unix::{signal, SignalKind};
match signal(SignalKind::terminate()) {
Ok(mut stream) => {
stream.recv().await;
}
Err(source) => {
error!(%source, "cannot install SIGTERM handler; the Ctrl-C path still governs shutdown");
std::future::pending::<()>().await;
}
}
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {}
_ = terminate => {}
}
}
pub struct ShutdownController {
tx: watch::Sender<bool>,
rx: watch::Receiver<bool>,
}
impl ShutdownController {
#[must_use]
pub fn install() -> Self {
let (tx, rx) = watch::channel(false);
let task_tx = tx.clone();
tokio::spawn(async move {
shutdown_signal().await;
let _ = task_tx.send(true);
});
Self { tx, rx }
}
#[must_use]
pub fn manual() -> Self {
let (tx, rx) = watch::channel(false);
Self { tx, rx }
}
#[must_use]
pub fn subscribe(&self) -> watch::Receiver<bool> {
self.rx.clone()
}
pub fn trigger(&self) {
let _ = self.tx.send(true);
}
#[must_use]
pub fn is_shutdown(&self) -> bool {
*self.rx.borrow()
}
pub fn signalled(&self) -> impl Future<Output = ()> + Send + 'static {
wait_for_shutdown(self.rx.clone())
}
}
pub(crate) async fn wait_for_shutdown(mut rx: watch::Receiver<bool>) {
while !*rx.borrow_and_update() {
if rx.changed().await.is_err() {
break;
}
}
}