use super::poller::last_error;
use super::services::ReadyQueue;
use crate::{Error, ErrorKind, Handle, Notifier, Result, Signal};
use std::{
os::fd::{AsRawFd, FromRawFd, OwnedFd},
sync::{
Arc, Mutex, OnceLock, Weak,
atomic::{AtomicBool, Ordering},
},
};
pub(super) fn number(signal: Signal) -> Result<i32> {
Ok(match signal {
Signal::Int => libc::SIGINT,
Signal::Term => libc::SIGTERM,
Signal::Kill => libc::SIGKILL,
Signal::Hup => libc::SIGHUP,
Signal::Chld => libc::SIGCHLD,
Signal::WinCh => libc::SIGWINCH,
Signal::Usr1 => libc::SIGUSR1,
Signal::Usr2 => libc::SIGUSR2,
Signal::Break => return Err(Error::new(ErrorKind::Unsupported)),
})
}
pub(super) struct Ticket {
signal: Signal,
pending: AtomicBool,
notifier: Notifier,
handle: Handle,
ready: Arc<ReadyQueue>,
}
impl Ticket {
pub fn take(&self) -> bool {
self.pending.swap(false, Ordering::AcqRel)
}
pub fn ready(&self) -> bool {
self.pending.load(Ordering::Acquire)
}
}
struct Record {
number: i32,
original: libc::sigaction,
subscribers: Vec<Weak<Ticket>>,
}
struct Dispatcher {
fd: OwnedFd,
records: Mutex<Vec<Record>>,
#[cfg(turnloop_backend = "epoll")]
_writer: OwnedFd,
}
static DISPATCHER: OnceLock<Result<Arc<Dispatcher>>> = OnceLock::new();
#[cfg(turnloop_backend = "epoll")]
static WRITE_FD: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(-1);
#[cfg(turnloop_backend = "epoll")]
static PENDING: [AtomicBool; 128] = [const { AtomicBool::new(false) }; 128];
extern "C" fn handler(_signal: i32) {
#[cfg(turnloop_backend = "epoll")]
{
if let Some(pending) = PENDING.get(_signal as usize) {
pending.store(true, Ordering::Release);
let fd = WRITE_FD.load(Ordering::Acquire);
let byte = 1u8;
unsafe {
let saved = *super::ipc::errno();
libc::write(fd, (&byte as *const u8).cast(), 1);
*super::ipc::errno() = saved;
}
}
}
}
#[cfg(turnloop_backend = "kqueue")]
fn change(fd: i32, signal: i32, flags: u16) -> Result<()> {
let event = libc::kevent {
ident: signal as usize,
filter: libc::EVFILT_SIGNAL,
flags,
fflags: 0,
data: 0,
udata: std::ptr::null_mut(),
};
if unsafe { libc::kevent(fd, &event, 1, std::ptr::null_mut(), 0, std::ptr::null()) } < 0 {
return Err(last_error());
}
Ok(())
}
fn start() -> Result<Arc<Dispatcher>> {
#[cfg(turnloop_backend = "kqueue")]
let fd = {
let raw = unsafe { libc::kqueue() };
if raw < 0 {
return Err(last_error());
}
let fd = unsafe { OwnedFd::from_raw_fd(raw) };
if unsafe { libc::fcntl(raw, libc::F_SETFD, libc::FD_CLOEXEC) } < 0 {
return Err(last_error());
}
fd
};
#[cfg(turnloop_backend = "epoll")]
let (fd, writer) = {
let mut pair = [-1; 2];
if unsafe { libc::pipe2(pair.as_mut_ptr(), libc::O_CLOEXEC | libc::O_NONBLOCK) } < 0 {
return Err(last_error());
}
let (reader, writer) =
unsafe { (OwnedFd::from_raw_fd(pair[0]), OwnedFd::from_raw_fd(pair[1])) };
WRITE_FD.store(writer.as_raw_fd(), Ordering::Release);
(reader, writer)
};
let dispatcher = Arc::new(Dispatcher {
fd,
records: Mutex::new(Vec::new()),
#[cfg(turnloop_backend = "epoll")]
_writer: writer,
});
let d = dispatcher.clone();
std::thread::Builder::new()
.name("turnloop-signals".into())
.spawn(move || d.run())
.map_err(Error::from)?;
Ok(dispatcher)
}
impl Dispatcher {
fn dispatch(&self, number: i32) {
let records = self
.records
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(record) = records.iter().find(|r| r.number == number) {
for ticket in &record.subscribers {
if let Some(t) = ticket.upgrade() {
t.pending.store(true, Ordering::Release);
t.ready.push(t.handle);
let _ = t.notifier.notify();
}
}
}
}
fn run(&self) {
loop {
#[cfg(turnloop_backend = "kqueue")]
{
let mut event: libc::kevent = unsafe { std::mem::zeroed() };
let n = unsafe {
libc::kevent(
self.fd.as_raw_fd(),
std::ptr::null(),
0,
&mut event,
1,
std::ptr::null(),
)
};
if n == 1 {
self.dispatch(event.ident as i32);
} else if n < 0 && last_error().os != Some(libc::EINTR) {
return;
}
}
#[cfg(turnloop_backend = "epoll")]
{
let mut fd = libc::pollfd {
fd: self.fd.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
};
let n = unsafe { libc::poll(&mut fd, 1, -1) };
if n < 0 {
if last_error().os == Some(libc::EINTR) {
continue;
}
return;
}
let mut bytes = [0u8; 256];
while unsafe { libc::read(fd.fd, bytes.as_mut_ptr().cast(), bytes.len()) } > 0 {}
for (sig, pending) in PENDING.iter().enumerate() {
if pending.swap(false, Ordering::AcqRel) {
self.dispatch(sig as i32);
}
}
}
}
}
}
pub(super) fn subscribe(
signal: Signal,
notifier: Notifier,
handle: Handle,
ready: Arc<ReadyQueue>,
) -> Result<Subscription> {
let number = number(signal)?;
if signal == Signal::Kill {
return Err(Error::new(ErrorKind::InvalidInput));
}
let dispatcher = DISPATCHER.get_or_init(start).as_ref().map_err(|&e| e)?;
let mut records = dispatcher
.records
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let ticket = Arc::new(Ticket {
signal,
pending: AtomicBool::new(false),
notifier,
handle,
ready,
});
if let Some(record) = records.iter_mut().find(|r| r.number == number) {
record.subscribers.push(Arc::downgrade(&ticket));
} else {
let (mut action, mut original): (libc::sigaction, libc::sigaction) =
unsafe { std::mem::zeroed() };
action.sa_sigaction = handler as *const () as usize;
#[cfg(turnloop_backend = "kqueue")]
{
action.sa_sigaction = if signal == Signal::Chld {
libc::SIG_DFL
} else {
libc::SIG_IGN
};
}
action.sa_flags = libc::SA_RESTART
| if signal == Signal::Chld {
libc::SA_NOCLDSTOP
} else {
0
};
unsafe {
libc::sigemptyset(&mut action.sa_mask);
}
if unsafe { libc::sigaction(number, &action, &mut original) } < 0 {
return Err(last_error());
}
#[cfg(turnloop_backend = "kqueue")]
if let Err(e) = change(
dispatcher.fd.as_raw_fd(),
number,
libc::EV_ADD | libc::EV_CLEAR,
) {
unsafe {
libc::sigaction(number, &original, std::ptr::null_mut());
}
return Err(e);
}
records.push(Record {
number,
original,
subscribers: vec![Arc::downgrade(&ticket)],
});
}
Ok(Subscription(ticket))
}
pub(super) struct Subscription(Arc<Ticket>);
impl std::ops::Deref for Subscription {
type Target = Ticket;
fn deref(&self) -> &Ticket {
&self.0
}
}
impl Drop for Subscription {
fn drop(&mut self) {
let Some(Ok(dispatcher)) = DISPATCHER.get() else {
return;
};
let Ok(number) = number(self.signal) else {
return;
};
let mut records = dispatcher
.records
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(i) = records.iter().position(|r| r.number == number) {
records[i]
.subscribers
.retain(|w| w.as_ptr() != Arc::as_ptr(&self.0));
if records[i].subscribers.is_empty() {
let record = records.swap_remove(i);
#[cfg(turnloop_backend = "kqueue")]
let _ = change(dispatcher.fd.as_raw_fd(), number, libc::EV_DELETE);
unsafe {
libc::sigaction(number, &record.original, std::ptr::null_mut());
}
}
}
}
}