use std::ffi::{c_int, c_void};
use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use super::{PollEvent, Registered, Sock, WAKE_KEY};
const EVFILT_READ: i16 = -1;
const EVFILT_WRITE: i16 = -2;
#[cfg(target_os = "macos")]
const EVFILT_USER: i16 = -10;
#[cfg(target_os = "freebsd")]
const EVFILT_USER: i16 = -11;
const EV_ADD: u16 = 0x0001;
const EV_DELETE: u16 = 0x0002;
const EV_CLEAR: u16 = 0x0020;
const EV_EOF: u16 = 0x8000;
const EV_ERROR: u16 = 0x4000;
const NOTE_TRIGGER: u32 = 0x0100_0000;
#[repr(C)]
#[derive(Clone, Copy)]
struct Kevent {
ident: usize,
filter: i16,
flags: u16,
fflags: u32,
data: isize,
udata: *mut c_void,
#[cfg(target_os = "freebsd")]
ext: [u64; 4],
}
#[repr(C)]
#[derive(Clone, Copy)]
struct Timespec {
tv_sec: i64,
tv_nsec: i64,
}
unsafe extern "C" {
fn kqueue() -> c_int;
fn kevent(
kq: c_int,
changes: *const Kevent,
nchanges: c_int,
events: *mut Kevent,
nevents: c_int,
timeout: *const Timespec,
) -> c_int;
fn fcntl(fd: c_int, cmd: c_int, ...) -> c_int;
}
const F_SETFD: c_int = 2;
const FD_CLOEXEC: c_int = 1;
const WAKE_IDENT: usize = 1;
const EVENTS: usize = 256;
fn cvt(ret: c_int) -> io::Result<c_int> {
if ret == -1 {
Err(io::Error::last_os_error())
} else {
Ok(ret)
}
}
fn change(ident: usize, filter: i16, flags: u16, fflags: u32, key: u64) -> Kevent {
Kevent {
ident,
filter,
flags,
fflags,
data: 0,
udata: key as usize as *mut c_void,
#[cfg(target_os = "freebsd")]
ext: [0; 4],
}
}
#[derive(Debug)]
pub(crate) struct PollWaker {
kq: Arc<OwnedFd>,
pending: AtomicBool,
}
impl PollWaker {
pub(crate) fn wake(&self) {
if !self.pending.swap(true, Ordering::AcqRel) {
let ev = change(WAKE_IDENT, EVFILT_USER, 0, NOTE_TRIGGER, WAKE_KEY);
let _ = unsafe {
kevent(
self.kq.as_raw_fd(),
&raw const ev,
1,
std::ptr::null_mut(),
0,
std::ptr::null(),
)
};
}
}
}
pub(crate) struct Poller {
kq: Arc<OwnedFd>,
wake: Arc<PollWaker>,
buf: Box<[Kevent; EVENTS]>,
}
unsafe impl Send for Poller {}
impl std::fmt::Debug for Poller {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Poller").finish_non_exhaustive()
}
}
impl Poller {
pub(crate) fn new() -> io::Result<Poller> {
let fd = cvt(unsafe { kqueue() })?;
let kq = Arc::new(unsafe { OwnedFd::from_raw_fd(fd) });
cvt(unsafe { fcntl(kq.as_raw_fd(), F_SETFD, FD_CLOEXEC) })?;
let wake = change(WAKE_IDENT, EVFILT_USER, EV_ADD | EV_CLEAR, 0, WAKE_KEY);
cvt(unsafe {
kevent(
kq.as_raw_fd(),
&raw const wake,
1,
std::ptr::null_mut(),
0,
std::ptr::null(),
)
})?;
Ok(Poller {
wake: Arc::new(PollWaker {
kq: kq.clone(),
pending: AtomicBool::new(false),
}),
kq,
buf: Box::new([change(0, 0, 0, 0, 0); EVENTS]),
})
}
pub(crate) fn waker(&self) -> Arc<PollWaker> {
self.wake.clone()
}
pub(crate) fn add(&self, sock: Sock<'_>, key: u64) -> io::Result<Registered> {
let raw = sock.as_raw_fd();
let changes = [
change(raw as usize, EVFILT_READ, EV_ADD | EV_CLEAR, 0, key),
change(raw as usize, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, key),
];
cvt(unsafe {
kevent(
self.kq.as_raw_fd(),
changes.as_ptr(),
2,
std::ptr::null_mut(),
0,
std::ptr::null(),
)
})?;
Ok(Registered::new(raw))
}
pub(crate) fn remove(&self, reg: Registered) -> io::Result<()> {
let raw = reg.raw();
let changes = [
change(raw as usize, EVFILT_READ, EV_DELETE, 0, 0),
change(raw as usize, EVFILT_WRITE, EV_DELETE, 0, 0),
];
let _ = unsafe {
kevent(
self.kq.as_raw_fd(),
changes.as_ptr(),
2,
std::ptr::null_mut(),
0,
std::ptr::null(),
)
};
Ok(())
}
pub(crate) fn wait(
&mut self,
timeout: Option<Duration>,
mut f: impl FnMut(PollEvent),
) -> io::Result<()> {
#[cfg(any(test, net_io_shim))]
super::shim::check(super::shim::Op::Wait)?;
let ts = timeout.map(|d| Timespec {
tv_sec: i64::try_from(d.as_secs()).unwrap_or(i64::MAX),
tv_nsec: i64::from(d.subsec_nanos()),
});
let ts_ptr = ts
.as_ref()
.map_or(std::ptr::null(), |t| t as *const Timespec);
let n = unsafe {
kevent(
self.kq.as_raw_fd(),
std::ptr::null(),
0,
self.buf.as_mut_ptr(),
EVENTS as c_int,
ts_ptr,
)
};
if n < 0 {
let e = io::Error::last_os_error();
if e.kind() == io::ErrorKind::Interrupted {
return Ok(());
}
return Err(e);
}
for i in 0..n as usize {
let ev = self.buf[i];
let key = ev.udata as usize as u64;
if ev.filter == EVFILT_USER || key == WAKE_KEY {
self.wake.pending.store(false, Ordering::Release);
continue;
}
let failed = ev.flags & EV_ERROR != 0;
let hangup = ev.flags & EV_EOF != 0 || failed;
f(PollEvent {
key,
readable: ev.filter == EVFILT_READ || failed,
writable: ev.filter == EVFILT_WRITE || failed,
hangup: hangup && ev.filter == EVFILT_READ,
});
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use std::mem::{offset_of, size_of};
use std::os::fd::AsFd;
#[test]
fn test_layout_matches_libc() {
assert_eq!(size_of::<Kevent>(), size_of::<libc::kevent>());
assert_eq!(offset_of!(Kevent, udata), offset_of!(libc::kevent, udata));
assert_eq!(offset_of!(Kevent, data), offset_of!(libc::kevent, data));
assert_eq!(EVFILT_READ, libc::EVFILT_READ);
assert_eq!(EVFILT_WRITE, libc::EVFILT_WRITE);
assert_eq!(EVFILT_USER, libc::EVFILT_USER);
assert_eq!(EV_ADD, libc::EV_ADD);
assert_eq!(EV_DELETE, libc::EV_DELETE);
assert_eq!(EV_CLEAR, libc::EV_CLEAR);
assert_eq!(EV_EOF, libc::EV_EOF);
assert_eq!(EV_ERROR, libc::EV_ERROR);
assert_eq!(NOTE_TRIGGER, libc::NOTE_TRIGGER);
}
#[test]
fn test_edges_and_waker() {
let mut p = Poller::new().unwrap();
let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let c = std::net::TcpStream::connect(l.local_addr().unwrap()).unwrap();
let (s, _) = l.accept().unwrap();
s.set_nonblocking(true).unwrap();
let reg = p.add(s.as_fd(), 7).unwrap();
let mut seen = Vec::new();
p.wait(Some(Duration::from_secs(1)), |e| seen.push(e))
.unwrap();
assert!(seen.iter().any(|e| e.key == 7 && e.writable));
seen.clear();
(&c).write_all(b"hi").unwrap();
p.wait(Some(Duration::from_secs(1)), |e| seen.push(e))
.unwrap();
assert!(seen.iter().any(|e| e.readable));
let w = p.waker();
w.wake();
w.wake();
seen.clear();
p.wait(Some(Duration::from_secs(1)), |e| seen.push(e))
.unwrap();
assert!(seen.is_empty(), "the wake reports nothing");
p.remove(reg).unwrap();
}
}