use std::future::poll_fn;
use std::io;
use std::marker::PhantomData;
use std::rc::Rc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, OnceLock, Weak};
use std::task::{Poll, Waker};
use windows_sys::Win32::System::Console::{
CTRL_BREAK_EVENT, CTRL_C_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT,
SetConsoleCtrlHandler,
};
use crate::platform::current::runtime::ThreadHandle;
struct StreamState {
event: u32,
thread: ThreadHandle,
generation: AtomicU64,
waker: Mutex<Option<Waker>>,
}
impl StreamState {
fn notify(self: &Arc<Self>) {
self.generation.fetch_add(1, Ordering::AcqRel);
let state = Arc::clone(self);
let _ = self.thread.queue_macrotask(move || {
if let Some(waker) = state
.waker
.lock()
.expect("ctrl waker mutex poisoned")
.take()
{
waker.wake();
}
});
}
}
static REGISTRY: OnceLock<Mutex<Vec<Weak<StreamState>>>> = OnceLock::new();
fn registry() -> &'static Mutex<Vec<Weak<StreamState>>> {
REGISTRY.get_or_init(|| Mutex::new(Vec::new()))
}
unsafe extern "system" fn ctrl_handler(event: u32) -> i32 {
let listeners = {
let mut registry = registry().lock().expect("ctrl registry mutex poisoned");
registry.retain(|state| state.upgrade().is_some());
registry
.iter()
.filter_map(Weak::upgrade)
.filter(|state| state.event == event)
.collect::<Vec<_>>()
};
let mut delivered = false;
for state in listeners {
state.notify();
delivered = true;
}
i32::from(delivered)
}
fn install_handler() -> io::Result<()> {
static INSTALL: OnceLock<io::Result<()>> = OnceLock::new();
INSTALL
.get_or_init(|| {
let installed = unsafe { SetConsoleCtrlHandler(Some(ctrl_handler), 1) };
if installed == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
})
.as_ref()
.map(|_| ())
.map_err(|error| io::Error::new(error.kind(), error.to_string()))
}
fn new_stream(event: u32) -> io::Result<Arc<StreamState>> {
install_handler()?;
let state = Arc::new(StreamState {
event,
thread: crate::current_thread_handle(),
generation: AtomicU64::new(0),
waker: Mutex::new(None),
});
registry()
.lock()
.expect("ctrl registry mutex poisoned")
.push(Arc::downgrade(&state));
state.thread.begin_async_operation();
Ok(state)
}
macro_rules! ctrl_stream {
(
$(#[$meta:meta])*
$name:ident, $factory:ident, $event:expr, $event_name:literal
) => {
$(#[$meta])*
pub struct $name {
last_seen: u64,
state: Arc<StreamState>,
_not_send: PhantomData<Rc<()>>,
}
#[doc = concat!("Registers interest in `", $event_name, "` on the current runtime thread.")]
pub fn $factory() -> io::Result<$name> {
let state = new_stream($event)?;
Ok($name {
last_seen: state.generation.load(Ordering::Acquire),
state,
_not_send: PhantomData,
})
}
impl $name {
pub async fn recv(&mut self) -> Option<()> {
poll_fn(|cx| {
let current = self.state.generation.load(Ordering::Acquire);
if current != self.last_seen {
self.last_seen = current;
return Poll::Ready(Some(()));
}
let mut waker = self
.state
.waker
.lock()
.expect("ctrl stream waker mutex poisoned");
*waker = Some(cx.waker().clone());
let current = self.state.generation.load(Ordering::Acquire);
if current != self.last_seen {
self.last_seen = current;
*waker = None;
Poll::Ready(Some(()))
} else {
Poll::Pending
}
})
.await
}
}
impl Drop for $name {
fn drop(&mut self) {
self.state.thread.finish_async_operation();
}
}
};
}
ctrl_stream!(
CtrlC,
ctrl_c,
CTRL_C_EVENT,
"CTRL_C_EVENT"
);
ctrl_stream!(
CtrlBreak,
ctrl_break,
CTRL_BREAK_EVENT,
"CTRL_BREAK_EVENT"
);
ctrl_stream!(
CtrlClose,
ctrl_close,
CTRL_CLOSE_EVENT,
"CTRL_CLOSE_EVENT"
);
ctrl_stream!(
CtrlLogoff,
ctrl_logoff,
CTRL_LOGOFF_EVENT,
"CTRL_LOGOFF_EVENT"
);
ctrl_stream!(
CtrlShutdown,
ctrl_shutdown,
CTRL_SHUTDOWN_EVENT,
"CTRL_SHUTDOWN_EVENT"
);