use std::pin::pin;
use std::sync::atomic::{AtomicBool, Ordering};
use nagoya::reactor::Handle;
use nagoya::signal::{Signal, SignalKind};
use nagoya::sync::Notify;
pub struct Shutdown {
cancelled: AtomicBool,
notify: Notify,
}
impl Default for Shutdown {
fn default() -> Self {
Self::new()
}
}
impl Shutdown {
#[must_use]
pub const fn new() -> Self {
Self {
cancelled: AtomicBool::new(false),
notify: Notify::new(),
}
}
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::Release);
self.notify.notify_waiters();
}
#[must_use]
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::Acquire)
}
pub async fn cancelled(&self) {
loop {
let notified = self.notify.notified();
let mut notified = pin!(notified);
if notified.as_mut().enable() {
if self.is_cancelled() {
return;
}
continue;
}
if self.is_cancelled() {
return;
}
notified.await;
}
}
}
pub static CANCELLATION_TOKEN: Shutdown = Shutdown::new();
pub fn init_signals(handle: &Handle) -> eyre::Result<(Signal, Signal)> {
let sigterm = Signal::new(SignalKind::terminate(), handle)?;
let sigint = Signal::new(SignalKind::interrupt(), handle)?;
Ok((sigterm, sigint))
}
pub async fn wait_for_signals(sigterm: &mut Signal, sigint: &mut Signal) {
let term = sigterm.recv();
let int = sigint.recv();
futures::pin_mut!(term, int);
match futures::future::select(term, int).await {
futures::future::Either::Left(_) => inform_terminate("SIGTERM"),
futures::future::Either::Right(_) => inform_terminate("SIGINT"),
}
}
pub async fn signal_received_silent(handle: &Handle) -> eyre::Result<()> {
let (mut sigterm, mut sigint) = init_signals(handle)?;
let term = sigterm.recv();
let int = sigint.recv();
futures::pin_mut!(term, int);
let _ = futures::future::select(term, int).await;
Ok(())
}
fn inform_terminate(signal_alias: &str) {
if !get_terminate_flag() {
tracing::warn!("received {signal_alias} signal, terminating program");
set_terminate_flag()
}
}
pub fn set_terminate_flag() {
CANCELLATION_TOKEN.cancel();
}
pub fn get_terminate_flag() -> bool {
CANCELLATION_TOKEN.is_cancelled()
}
#[cfg(test)]
mod tests {
use std::future::Future;
use std::task::Context;
use super::*;
#[test]
fn cancel_completes_a_waiter_that_already_parked() {
let shutdown = Shutdown::new();
let mut fut = pin!(shutdown.cancelled());
let waker = futures::task::noop_waker();
let mut cx = Context::from_waker(&waker);
assert!(fut.as_mut().poll(&mut cx).is_pending());
assert!(!shutdown.is_cancelled());
shutdown.cancel();
assert!(shutdown.is_cancelled());
assert!(fut.as_mut().poll(&mut cx).is_ready());
}
#[test]
fn a_waiter_that_starts_after_cancel_completes_immediately() {
let shutdown = Shutdown::new();
shutdown.cancel();
let mut fut = pin!(shutdown.cancelled());
let waker = futures::task::noop_waker();
let mut cx = Context::from_waker(&waker);
assert!(shutdown.is_cancelled());
assert!(fut.as_mut().poll(&mut cx).is_ready());
}
}