use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::OnceLock;
use tokio_util::sync::CancellationToken;
use tracing::{info, warn};
static TOKEN: OnceLock<CancellationToken> = OnceLock::new();
pub struct AppContext;
impl AppContext {
fn token() -> &'static CancellationToken {
TOKEN.get_or_init(CancellationToken::new)
}
pub async fn terminated() {
Self::token().cancelled().await;
}
pub fn terminate() {
info!("Terminating application context");
Self::token().cancel();
}
pub async fn auto_terminate() {
static CALLED: AtomicBool = AtomicBool::new(false);
if CALLED.swap(true, Ordering::Relaxed) {
return;
}
tokio::spawn(Self::listen_for_shutdown_signals());
tokio::task::yield_now().await;
}
pub fn is_terminated() -> bool {
Self::token().is_cancelled()
}
pub fn is_alive() -> bool {
!Self::token().is_cancelled()
}
async fn listen_for_shutdown_signals() -> ! {
Self::wait_for_shutdown_signal().await;
info!("Shutdown signal intercepted");
Self::token().cancel();
Self::wait_for_shutdown_signal().await;
warn!("Repeated shutdown signal intercepted; exiting");
std::process::exit(1);
}
#[cfg(unix)]
async fn wait_for_shutdown_signal() {
use tokio::signal::unix::{signal, SignalKind};
let mut sigint = signal(SignalKind::interrupt()).unwrap();
let mut sigterm = signal(SignalKind::terminate()).unwrap();
tokio::select! {
biased; _ = sigint.recv() => {}
_ = sigterm.recv() => {}
}
}
#[cfg(not(unix))]
async fn wait_for_shutdown_signal() {
tokio::signal::ctrl_c().await.unwrap();
}
}