#![allow(clippy::unnecessary_cast)]
use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::sync::Arc;
use std::time::Duration;
use super::Readiness;
const WAKE: libc::uintptr_t = 0;
pub(crate) struct Poller {
queue: Arc<OwnedFd>,
events: Vec<libc::kevent>,
}
unsafe impl Send for Poller {}
#[derive(Clone)]
pub(crate) struct Notifier {
queue: Arc<OwnedFd>,
}
#[derive(Clone)]
pub(crate) struct Registrar {
queue: Arc<OwnedFd>,
}
fn wake_event(flags: u16, fflags: u32) -> libc::kevent {
let mut event: libc::kevent = unsafe { std::mem::zeroed() };
event.ident = WAKE;
event.filter = libc::EVFILT_USER as _;
event.flags = flags as _;
event.fflags = fflags as _;
event
}
fn fd_event(fd: RawFd, filter: i16, flags: u16, token: u64) -> libc::kevent {
let mut event: libc::kevent = unsafe { std::mem::zeroed() };
event.ident = fd as _;
event.filter = filter as _;
event.flags = flags as _;
event.udata = token as usize as _;
event
}
fn exit_event(pid: u32, flags: impl Into<u32>, token: u64) -> libc::kevent {
let mut event: libc::kevent = unsafe { std::mem::zeroed() };
event.ident = pid as _;
event.filter = libc::EVFILT_PROC as _;
event.flags = flags.into() as _;
event.fflags = libc::NOTE_EXIT as _;
event.udata = token as usize as _;
event
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn urgent_event(fd: RawFd, flags: u16, token: u64) -> libc::kevent {
let mut event = fd_event(fd, libc::EVFILT_EXCEPT, flags, token);
event.fflags = libc::NOTE_OOB;
event
}
fn submit(queue: &OwnedFd, event: &libc::kevent) -> io::Result<()> {
submit_all(queue, core::slice::from_ref(event))
}
fn submit_all(queue: &OwnedFd, events: &[libc::kevent]) -> io::Result<()> {
let ret = unsafe {
libc::kevent(
queue.as_raw_fd(),
events.as_ptr(),
events.len() as _,
std::ptr::null_mut(),
0,
std::ptr::null(),
)
};
if ret < 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
impl Poller {
pub(crate) fn new() -> io::Result<Self> {
let raw = unsafe { libc::kqueue() };
if raw < 0 {
return Err(io::Error::last_os_error());
}
let queue = unsafe { OwnedFd::from_raw_fd(raw) };
unsafe { libc::fcntl(queue.as_raw_fd(), libc::F_SETFD, libc::FD_CLOEXEC) };
submit(&queue, &wake_event((libc::EV_ADD | libc::EV_CLEAR) as _, 0))?;
Ok(Self {
queue: Arc::new(queue),
events: Vec::with_capacity(64),
})
}
pub(crate) fn notifier(&self) -> Notifier {
Notifier {
queue: Arc::clone(&self.queue),
}
}
pub(crate) fn registrar(&self) -> Registrar {
Registrar {
queue: Arc::clone(&self.queue),
}
}
#[cfg(test)]
pub(crate) fn wait(
&mut self,
timeout: Option<Duration>,
mut on_event: impl FnMut(u64, Readiness),
) -> io::Result<bool> {
self.wait_at_most(usize::MAX, timeout, &mut on_event)
}
pub(crate) fn wait_at_most(
&mut self,
max: usize,
timeout: Option<Duration>,
mut on_event: impl FnMut(u64, Readiness),
) -> io::Result<bool> {
let ts = timeout.map(|t| libc::timespec {
tv_sec: t.as_secs().min(libc::time_t::MAX as u64) as libc::time_t,
tv_nsec: t.subsec_nanos() as _,
});
let ts_ptr = ts
.as_ref()
.map_or(std::ptr::null(), |t| t as *const libc::timespec);
let n = unsafe {
let n = libc::kevent(
self.queue.as_raw_fd(),
std::ptr::null(),
0,
self.events.as_mut_ptr(),
self.events.capacity().min(max).max(1) as _,
ts_ptr,
);
if n < 0 {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
0
} else {
return Err(err);
}
} else {
n as usize
}
};
unsafe { self.events.set_len(n) };
let mut woken = false;
for e in &self.events {
if e.filter == libc::EVFILT_USER as _ {
woken |= e.ident == WAKE;
continue;
}
let done = u64::from(e.flags) & u64::from(libc::EV_EOF | libc::EV_ERROR) != 0;
let urgent = urgent(e);
on_event(
e.udata as usize as u64,
Readiness {
readable: e.filter == libc::EVFILT_READ as _ || done || urgent,
writable: e.filter == libc::EVFILT_WRITE as _ || done,
urgent,
hangup: done,
},
);
}
Ok(woken)
}
}
fn urgent(e: &libc::kevent) -> bool {
#[cfg(any(target_os = "macos", target_os = "ios"))]
{
e.filter == libc::EVFILT_EXCEPT as _ && e.fflags & libc::NOTE_OOB != 0
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
{
let _ = e;
false
}
}
impl Registrar {
pub(crate) fn add(&self, fd: RawFd, token: u64, urgent: bool) -> io::Result<()> {
let flags = (libc::EV_ADD | libc::EV_CLEAR) as u16;
let rw = [
fd_event(fd, libc::EVFILT_READ as i16, flags, token),
fd_event(fd, libc::EVFILT_WRITE as i16, flags, token),
];
#[cfg(any(target_os = "macos", target_os = "ios"))]
if urgent {
return submit_all(&self.queue, &[rw[0], rw[1], urgent_event(fd, flags, token)]);
}
let _ = urgent;
submit_all(&self.queue, &rw)
}
pub(crate) fn add_exit(&self, pid: u32, token: u64) -> io::Result<bool> {
match submit(
&self.queue,
&exit_event(pid, libc::EV_ADD | libc::EV_ONESHOT, token),
) {
Ok(()) => Ok(true),
Err(e) if e.raw_os_error() == Some(libc::ESRCH) => Ok(false),
Err(e) => Err(e),
}
}
pub(crate) fn delete_exit(&self, pid: u32) {
let _ = submit(&self.queue, &exit_event(pid, libc::EV_DELETE, 0));
}
pub(crate) fn delete(&self, fd: RawFd, urgent: bool) -> io::Result<()> {
let flags = libc::EV_DELETE as u16;
let rw = [
fd_event(fd, libc::EVFILT_READ as i16, flags, 0),
fd_event(fd, libc::EVFILT_WRITE as i16, flags, 0),
];
#[cfg(any(target_os = "macos", target_os = "ios"))]
if urgent {
return submit_all(&self.queue, &[rw[0], rw[1], urgent_event(fd, flags, 0)]);
}
let _ = urgent;
submit_all(&self.queue, &rw)
}
}
impl Notifier {
pub(crate) fn notify(&self) {
let _ = submit(&self.queue, &wake_event(0, libc::NOTE_TRIGGER));
}
}