use std::sync::atomic::{AtomicBool, Ordering};
static CANCELLED: AtomicBool = AtomicBool::new(false);
fn gate() -> &'static tokio::sync::Notify {
static GATE: std::sync::OnceLock<tokio::sync::Notify> = std::sync::OnceLock::new();
GATE.get_or_init(tokio::sync::Notify::new)
}
pub fn request() {
CANCELLED.store(true, Ordering::SeqCst);
gate().notify_one();
}
#[must_use]
pub fn is_requested() -> bool {
CANCELLED.load(Ordering::SeqCst)
}
pub async fn cancelled() {
loop {
if is_requested() {
return;
}
gate().notified().await;
}
}
pub fn reset() {
CANCELLED.store(false, Ordering::SeqCst);
}
#[must_use]
pub fn interrupted_error(what: &str) -> crate::errors::TokenSaveError {
crate::errors::TokenSaveError::Config {
message: format!(
"{what} interrupted by shutdown signal — no partial results were committed, \
and the index is left marked stale; run `tokensave sync` to finish it"
),
}
}
pub fn check(what: &str) -> crate::errors::Result<()> {
if is_requested() {
return Err(interrupted_error(what));
}
Ok(())
}
pub fn check_partial(what: &str) -> crate::errors::Result<()> {
if is_requested() {
return Err(crate::errors::TokenSaveError::Config {
message: format!(
"{what} interrupted by shutdown signal partway through writing — the index \
is partially updated and left marked stale; run `tokensave sync` to finish it"
),
});
}
Ok(())
}
pub fn install_signal_handlers() {
use std::sync::Once;
static ONCE: Once = Once::new();
ONCE.call_once(|| {
tokio::spawn(async {
#[cfg(unix)]
{
let Ok(mut sigterm) =
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
else {
return;
};
tokio::select! {
_ = sigterm.recv() => request(),
_ = tokio::signal::ctrl_c() => request(),
}
}
#[cfg(not(unix))]
{
if tokio::signal::ctrl_c().await.is_ok() {
request();
}
}
});
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_passes_until_a_shutdown_is_requested() {
reset();
assert!(check("sync").is_ok());
request();
match check("sync") {
Ok(()) => panic!("must fail once a shutdown is requested"),
Err(err) => assert!(
err.to_string().contains("interrupted by shutdown signal"),
"got: {err}"
),
}
reset();
assert!(check("sync").is_ok(), "reset must clear the flag");
}
}