use std::{
collections::HashMap,
future::{Future, poll_fn},
io,
pin::Pin,
sync::{
Arc, LazyLock, Mutex,
atomic::{AtomicUsize, Ordering},
},
task::{Context, Poll, Waker},
};
use crate::slab::Slab;
use crate::signal::half_lock::HalfLock;
struct SignalState {
counter: AtomicUsize,
waker: Mutex<Option<Waker>>,
}
#[derive(Clone)]
struct Entry {
signum: libc::c_int,
state: Arc<SignalState>,
}
static REGISTRY: LazyLock<HalfLock<Slab<Entry>>> = LazyLock::new(HalfLock::default);
static SAVED: LazyLock<Mutex<HashMap<libc::c_int, libc::sigaction>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct SignalKind(libc::c_int);
impl SignalKind {
#[inline]
pub const fn new(raw: libc::c_int) -> Self {
Self(raw)
}
#[inline]
pub const fn as_raw(self) -> libc::c_int {
self.0
}
#[inline]
pub const fn interrupt() -> Self {
Self(libc::SIGINT)
}
#[inline]
pub const fn terminate() -> Self {
Self(libc::SIGTERM)
}
#[inline]
pub const fn hangup() -> Self {
Self(libc::SIGHUP)
}
#[inline]
pub const fn quit() -> Self {
Self(libc::SIGQUIT)
}
#[inline]
pub const fn user_defined1() -> Self {
Self(libc::SIGUSR1)
}
#[inline]
pub const fn user_defined2() -> Self {
Self(libc::SIGUSR2)
}
#[inline]
pub const fn child() -> Self {
Self(libc::SIGCHLD)
}
#[inline]
pub const fn alarm() -> Self {
Self(libc::SIGALRM)
}
#[inline]
pub const fn pipe() -> Self {
Self(libc::SIGPIPE)
}
}
impl From<SignalKind> for libc::c_int {
#[inline]
fn from(kind: SignalKind) -> libc::c_int {
kind.0
}
}
pub struct Signal {
kind: SignalKind,
state: Arc<SignalState>,
key: usize,
last_seen: usize,
}
impl Signal {
pub fn new(kind: SignalKind) -> io::Result<Self> {
let (state, key) = register(kind)?;
let last_seen = state.counter.load(Ordering::Acquire);
Ok(Self {
kind,
state,
key,
last_seen,
})
}
#[inline]
pub fn kind(&self) -> SignalKind {
self.kind
}
pub async fn recv(&mut self) -> io::Result<()> {
poll_fn(|cx| self.poll_recv(cx)).await
}
fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let current = self.state.counter.load(Ordering::Acquire);
if current != self.last_seen {
self.last_seen = current;
return Poll::Ready(Ok(()));
}
{
let mut slot = self.state.waker.lock().unwrap_or_else(|e| e.into_inner());
match &*slot {
Some(existing) if existing.will_wake(cx.waker()) => {}
_ => *slot = Some(cx.waker().clone()),
}
}
let current = self.state.counter.load(Ordering::Acquire);
if current != self.last_seen {
self.last_seen = current;
return Poll::Ready(Ok(()));
}
Poll::Pending
}
}
impl Drop for Signal {
fn drop(&mut self) {
unregister(self.kind, self.key);
}
}
#[inline]
pub fn signal(kind: SignalKind) -> io::Result<Signal> {
Signal::new(kind)
}
pub struct CtrlC {
signal: Signal,
}
impl CtrlC {
pub fn new() -> io::Result<Self> {
Ok(Self {
signal: Signal::new(SignalKind::interrupt())?,
})
}
}
impl Future for CtrlC {
type Output = io::Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = unsafe { self.get_unchecked_mut() };
this.signal.poll_recv(cx)
}
}
#[inline]
pub fn ctrl_c() -> io::Result<CtrlC> {
CtrlC::new()
}
extern "C" fn signal_handler(signum: libc::c_int) {
for (_, entry) in REGISTRY.read().iter() {
if entry.signum != signum {
continue;
}
entry.state.counter.fetch_add(1, Ordering::Release);
if let Ok(mut slot) = entry.state.waker.try_lock()
&& let Some(waker) = slot.take()
{
waker.wake();
}
}
}
fn register(kind: SignalKind) -> io::Result<(Arc<SignalState>, usize)> {
let state = Arc::new(SignalState {
counter: AtomicUsize::new(0),
waker: Mutex::new(None),
});
let mut guard = REGISTRY.write();
let already_installed = guard.iter().any(|(_, entry)| entry.signum == kind.0);
if !already_installed {
let prev = unsafe { install_handler(kind.0)? };
SAVED.lock().unwrap_or_else(|e| e.into_inner()).insert(kind.0, prev);
}
let mut new = Slab::clone(&*guard);
let key = new.insert(Entry {
signum: kind.0,
state: Arc::clone(&state),
});
guard.store(new);
Ok((state, key))
}
fn unregister(kind: SignalKind, key: usize) {
let mut guard = REGISTRY.write();
let mut new = Slab::clone(&*guard);
if new.contains(key) {
new.remove(key);
}
let still_listening = new.iter().any(|(_, entry)| entry.signum == kind.0);
if !still_listening && let Some(prev) = SAVED.lock().unwrap_or_else(|e| e.into_inner()).remove(&kind.0) {
unsafe {
let _ = restore_handler(kind.0, &prev);
}
}
guard.store(new);
}
unsafe fn install_handler(signum: libc::c_int) -> io::Result<libc::sigaction> {
let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
action.sa_sigaction = signal_handler as extern "C" fn(libc::c_int) as usize;
action.sa_flags = libc::SA_RESTART;
unsafe { libc::sigemptyset(&mut action.sa_mask) };
let mut prev: libc::sigaction = unsafe { std::mem::zeroed() };
let rc = unsafe { libc::sigaction(signum, &action, &mut prev) };
if rc == -1 {
return Err(io::Error::last_os_error());
}
Ok(prev)
}
unsafe fn restore_handler(signum: libc::c_int, prev: &libc::sigaction) -> io::Result<()> {
let rc = unsafe { libc::sigaction(signum, prev, std::ptr::null_mut()) };
if rc == -1 {
return Err(io::Error::last_os_error());
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
use crate::runtime::Runtime;
use crate::time::timeout;
async fn with_timeout(fut: impl Future<Output = io::Result<()>>) -> io::Result<()> {
timeout(Duration::from_secs(2), fut)
.await
.map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "signal timeout"))?
}
fn raise_after_delay(signum: libc::c_int) {
let pid = rustix::process::getpid();
let signal = unsafe { rustix::process::Signal::from_raw_unchecked(signum) };
std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(20));
let _ = rustix::process::kill_process(pid, signal);
});
}
#[test]
fn signal_recv_wakes_on_delivery() {
let mut rt = Runtime::new().expect("runtime start");
let result = rt.block_on(async {
let mut sig = signal(SignalKind::user_defined1())?;
raise_after_delay(SignalKind::user_defined1().as_raw());
with_timeout(sig.recv()).await
});
assert!(result.is_ok(), "got: {result:?}");
}
#[test]
fn ctrl_c_wakes_on_sigint() {
let mut rt = Runtime::new().expect("runtime start");
let result = rt.block_on(async {
let ctrlc = ctrl_c()?;
raise_after_delay(SignalKind::interrupt().as_raw());
with_timeout(ctrlc).await
});
assert!(result.is_ok(), "got: {result:?}");
}
#[test]
fn multiple_listeners_all_notified() {
let mut rt = Runtime::new().expect("runtime start");
let result = rt.block_on(async {
let mut a = signal(SignalKind::user_defined2())?;
let mut b = signal(SignalKind::user_defined2())?;
raise_after_delay(SignalKind::user_defined2().as_raw());
with_timeout(a.recv()).await?;
with_timeout(b.recv()).await
});
assert!(result.is_ok(), "got: {result:?}");
}
}