use std::sync::OnceLock;
use tokio::sync::broadcast;
#[cfg(feature = "tokio")]
use {killswitch::KillSwitch, tokio::signal};
use windows_sys::{
Win32::{
Foundation::{FALSE, TRUE},
System::Console::{
CTRL_BREAK_EVENT, CTRL_C_EVENT, CTRL_CLOSE_EVENT, SetConsoleCtrlHandler
}
},
core::BOOL
};
use crate::{
err::Error,
rt::{Demise, SvcEvt}
};
static CELL: OnceLock<Box<dyn Fn(u32) -> BOOL + Send + Sync>> =
OnceLock::new();
#[cfg(feature = "tokio")]
pub async fn wait_shutdown<F>(f: F, ks: KillSwitch)
where
F: FnOnce() + Send
{
tracing::trace!("CTRL+C task launched");
tokio::select! {
_ = signal::ctrl_c() => {
tracing::debug!("Received Ctrl+C");
f();
},
() = ks.wait() => {
tracing::debug!("killswitch triggered");
}
}
tracing::trace!("wait_shutdown() terminating");
}
#[cfg(feature = "tokio")]
pub async fn wait_term<F>(f: F, ks: KillSwitch)
where
F: FnOnce() + Send
{
tracing::trace!("CTRL+Break/Close task launched");
let Ok(mut cbreak) = signal::windows::ctrl_break() else {
log::error!("Unable to create Ctrl+Break monitor");
return;
};
let Ok(mut cclose) = signal::windows::ctrl_close() else {
log::error!("Unable to create Close monitor");
return;
};
tokio::select! {
_ = cbreak.recv() => {
tracing::debug!("Received Ctrl+Break");
f();
},
_ = cclose.recv() => {
tracing::debug!("Received Close");
f();
},
() = ks.wait() => {
tracing::debug!("killswitch triggered");
}
}
tracing::trace!("wait_term() terminating");
}
pub fn sync_kill_to_event(
tx: broadcast::Sender<SvcEvt>,
test_mode: bool
) -> Result<(), Error> {
setup_sync_fg_kill_handler(
move |ty| {
match ty {
CTRL_C_EVENT => {
tracing::trace!(
"Received some kind of event that should trigger a shutdown."
);
if tx.send(SvcEvt::Shutdown(Demise::Interrupted)).is_ok() {
TRUE
} else {
FALSE
}
}
CTRL_BREAK_EVENT | CTRL_CLOSE_EVENT => {
tracing::trace!(
"Received some kind of event that should trigger a termination."
);
if tx.send(SvcEvt::Shutdown(Demise::Terminated)).is_ok() {
TRUE
} else {
FALSE
}
}
_ => FALSE
}
},
test_mode
)?;
Ok(())
}
pub fn setup_sync_fg_kill_handler<F>(
f: F,
test_mode: bool
) -> Result<(), Error>
where
F: Fn(u32) -> BOOL + Send + Sync + 'static
{
if test_mode {
let _ = CELL.get_or_init(|| Box::new(f));
} else {
CELL
.set(Box::new(f))
.map_err(|_| Error::internal("Unable to set shared OnceLock cell"))?;
}
let rc = unsafe { SetConsoleCtrlHandler(Some(ctrlhandler), 1) };
(rc != 0)
.then_some(())
.ok_or_else(|| Error::internal("SetConsoleCtrlHandler failed"))?;
Ok(())
}
unsafe extern "system" fn ctrlhandler(ty: u32) -> BOOL {
let Some(f) = CELL.get() else {
return FALSE;
};
f(ty)
}