use tokio::signal::unix::{SignalKind, signal};
use tokio_util::{
sync::{CancellationToken, WaitForCancellationFuture},
task::task_tracker::{TaskTracker, TaskTrackerToken},
};
#[derive(Default, Clone)]
pub struct ShutdownController {
shutdown_notifyer: CancellationToken,
task_tracker: TaskTracker,
}
impl ShutdownController {
pub fn new() -> Self {
Self::default()
}
pub fn ask_shutdown(&self) {
self.shutdown_notifyer.cancel();
self.task_tracker.close();
}
pub async fn wait_all_task_shutdown(self) {
self.task_tracker.close();
self.task_tracker.wait().await;
}
pub fn token(&self) -> ShutdownToken {
ShutdownToken::new(self.shutdown_notifyer.clone(), self.task_tracker.token())
}
pub fn delegate(&self) -> ShutdownDelegate {
ShutdownDelegate(self.shutdown_notifyer.clone())
}
pub fn wait(&self) -> WaitForCancellationFuture<'_> {
self.shutdown_notifyer.cancelled()
}
}
pub struct ShutdownDelegate(CancellationToken);
impl ShutdownDelegate {
pub fn ask_shutdown(&self) {
self.0.cancel();
}
pub fn handle_quit_signals(self) -> Result<(), String> {
let err_str = |err| format!("could not register signal: {err}");
let mut sighup = signal(SignalKind::hangup()).map_err(err_str)?;
let mut sigint = signal(SignalKind::interrupt()).map_err(err_str)?;
let mut sigterm = signal(SignalKind::terminate()).map_err(err_str)?;
tokio::spawn(async move {
let signal = tokio::select! {
_ = sighup.recv() => "SIGHUP",
_ = sigint.recv() => "SIGINT",
_ = sigterm.recv() => "SIGTERM",
};
eprintln!("received {signal}, closing...");
self.ask_shutdown();
});
Ok(())
}
}
#[derive(Clone)]
pub struct ShutdownToken {
shutdown_notifyer: CancellationToken,
_task_tracker_token: TaskTrackerToken,
}
impl ShutdownToken {
fn new(shutdown_notifyer: CancellationToken, _task_tracker_token: TaskTrackerToken) -> Self {
Self {
shutdown_notifyer,
_task_tracker_token,
}
}
pub fn split(self) -> (CancellationToken, TaskTrackerToken) {
(self.shutdown_notifyer, self._task_tracker_token)
}
pub fn wait(&self) -> WaitForCancellationFuture<'_> {
self.shutdown_notifyer.cancelled()
}
pub fn is_shutdown(&self) -> bool {
self.shutdown_notifyer.is_cancelled()
}
pub fn ask_shutdown(&self) {
self.shutdown_notifyer.cancel();
}
}