use crate::{
Result,
backend::{
PollInfo, Wake,
poller::{Poller, Ready, last_error, timespec},
},
};
use std::{
os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
time::Duration,
};
const WAKE: u64 = 0;
const TIMER: u64 = u64::MAX;
pub struct EpollWake {
fd: OwnedFd,
calls: AtomicU64,
}
impl Wake for EpollWake {
fn wake(&self) -> Result<()> {
let value: u64 = 1;
loop {
self.calls.fetch_add(1, Ordering::Relaxed);
let n = unsafe { libc::write(self.fd.as_raw_fd(), (&value as *const u64).cast(), 8) };
if n == 8 {
return Ok(());
}
let e = std::io::Error::last_os_error();
if e.kind() == std::io::ErrorKind::WouldBlock {
return Ok(());
} if e.kind() != std::io::ErrorKind::Interrupted {
return Err(e.into());
}
}
}
fn syscall_count(&self) -> u64 {
self.calls.load(Ordering::Relaxed)
}
}
pub(crate) struct Epoll {
fd: OwnedFd,
wake: Arc<EpollWake>,
timer: Option<OwnedFd>,
events: Vec<libc::epoll_event>,
}
fn owned(fd: i32) -> Result<OwnedFd> {
if fd < 0 {
return Err(last_error());
}
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}
fn add(epoll: RawFd, fd: RawFd, key: u64, flags: i32) -> Result<()> {
let mut e = libc::epoll_event {
events: flags as u32,
u64: key,
};
if unsafe { libc::epoll_ctl(epoll, libc::EPOLL_CTL_ADD, fd, &mut e) } < 0 {
return Err(last_error());
}
Ok(())
}
fn drain_counter(fd: RawFd) -> Result<()> {
let mut count = 0u64;
loop {
let n = unsafe { libc::read(fd, (&mut count as *mut u64).cast(), 8) };
if n == 8 {
continue;
}
let e = std::io::Error::last_os_error();
if e.kind() == std::io::ErrorKind::WouldBlock {
return Ok(());
}
if e.kind() != std::io::ErrorKind::Interrupted {
return Err(e.into());
}
}
}
impl Poller for Epoll {
type W = EpollWake;
fn new(capacity: usize) -> Result<Self> {
let fd = owned(unsafe { libc::epoll_create1(libc::EPOLL_CLOEXEC) })?;
let wakefd = owned(unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) })?;
add(
fd.as_raw_fd(),
wakefd.as_raw_fd(),
WAKE,
libc::EPOLLIN | libc::EPOLLET,
)?;
let mut event = libc::epoll_event { events: 0, u64: 0 };
let zero = timespec(Duration::ZERO);
let probe = unsafe {
libc::syscall(
libc::SYS_epoll_pwait2,
fd.as_raw_fd(),
&mut event,
1,
&zero,
std::ptr::null::<libc::sigset_t>(),
8usize,
)
};
let unsupported = if probe < 0 {
let e = std::io::Error::last_os_error();
if e.raw_os_error() != Some(libc::ENOSYS) {
return Err(e.into());
}
true
} else {
false
};
let timer = if unsupported || cfg!(feature = "epoll-timerfd") {
let timer = owned(unsafe {
libc::timerfd_create(
libc::CLOCK_MONOTONIC,
libc::TFD_NONBLOCK | libc::TFD_CLOEXEC,
)
})?;
add(
fd.as_raw_fd(),
timer.as_raw_fd(),
TIMER,
libc::EPOLLIN | libc::EPOLLET,
)?;
Some(timer)
} else {
None
};
Ok(Self {
fd,
wake: Arc::new(EpollWake {
fd: wakefd,
calls: AtomicU64::new(0),
}),
timer,
events: vec![libc::epoll_event { events: 0, u64: 0 }; capacity],
})
}
fn waker(&self) -> Arc<Self::W> {
self.wake.clone()
}
fn register(&mut self, fd: RawFd, key: u64) -> Result<()> {
add(
self.fd(),
fd,
key,
libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLRDHUP | libc::EPOLLET,
)
}
fn deregister(&mut self, fd: RawFd) -> Result<()> {
if unsafe { libc::epoll_ctl(self.fd(), libc::EPOLL_CTL_DEL, fd, std::ptr::null_mut()) } < 0
{
return Err(last_error());
}
Ok(())
}
fn process(&mut self, pid: u32, key: u64) -> Result<Option<OwnedFd>> {
if cfg!(feature = "process-sigchld") {
return Err(std::io::Error::from_raw_os_error(libc::ENOSYS).into());
}
let fd =
owned(unsafe { libc::syscall(libc::SYS_pidfd_open, pid as libc::pid_t, 0u32) } as i32)?;
add(
self.fd(),
fd.as_raw_fd(),
key,
libc::EPOLLIN | libc::EPOLLET,
)?;
Ok(Some(fd))
}
fn remove_process(&mut self, _pid: u32, fd: Option<RawFd>) {
if let Some(fd) = fd {
let _ = self.deregister(fd);
}
}
fn wait(&mut self, timeout: Option<Duration>, out: &mut Vec<Ready>) -> Result<PollInfo> {
let n = if let Some(timer) = &self.timer {
let spec = libc::itimerspec {
it_interval: timespec(Duration::ZERO),
it_value: timespec(timeout.unwrap_or(Duration::ZERO)),
};
if unsafe { libc::timerfd_settime(timer.as_raw_fd(), 0, &spec, std::ptr::null_mut()) }
< 0
{
return Err(last_error());
}
let millis = if timeout == Some(Duration::ZERO) {
0
} else {
-1
};
unsafe {
libc::epoll_wait(
self.fd(),
self.events.as_mut_ptr(),
self.events.len() as i32,
millis,
) as i64
}
} else {
let ts = timeout.map(timespec);
unsafe {
libc::syscall(
libc::SYS_epoll_pwait2,
self.fd(),
self.events.as_mut_ptr(),
self.events.len() as i32,
ts.as_ref().map_or(std::ptr::null(), |v| v),
std::ptr::null::<libc::sigset_t>(),
8usize,
) as i64
}
};
if n < 0 {
let e = std::io::Error::last_os_error();
if e.kind() == std::io::ErrorKind::Interrupted {
return Ok(PollInfo::native(timeout, true));
}
return Err(e.into());
}
let mut native_events = 0;
for e in &self.events[..n as usize] {
native_events += usize::from(e.u64 != TIMER);
let key = e.u64;
if key == WAKE {
drain_counter(self.wake.fd.as_raw_fd())?;
} else if key == TIMER {
if let Some(timer) = &self.timer {
drain_counter(timer.as_raw_fd())?;
}
} else {
let flags = e.events as i32;
out.push(Ready {
key,
read: flags
& (libc::EPOLLIN | libc::EPOLLHUP | libc::EPOLLRDHUP | libc::EPOLLERR)
!= 0,
write: flags & (libc::EPOLLOUT | libc::EPOLLHUP | libc::EPOLLERR) != 0,
vnode: 0,
});
}
}
Ok(PollInfo::native(timeout, native_events == 0))
}
fn fd(&self) -> RawFd {
self.fd.as_raw_fd()
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
use std::time::Instant;
#[test]
fn timeout_and_notifier_waits_keep_distinct_counts_after_rearming() {
let mut poller = Epoll::new(4).expect("epoll");
if cfg!(feature = "epoll-timerfd") {
assert!(poller.timer.is_some(), "forced timerfd mode must execute");
}
let mut ready = Vec::with_capacity(4);
let mut timeouts = 0;
for duration in [
Duration::from_micros(500),
Duration::from_millis(2),
Duration::from_millis(10),
] {
poller.waker().wake().expect("notify");
let info = poller.wait(Some(duration), &mut ready).expect("wake wait");
assert_eq!(
(info.waits, info.discovery_polls, info.zero_event_waits),
(1, 0, 0)
);
assert!(ready.is_empty());
std::thread::sleep(duration);
let at = Instant::now() + duration;
let info = poller.wait(Some(duration), &mut ready).expect("timeout");
assert_eq!(
(info.waits, info.discovery_polls, info.zero_event_waits),
(1, 0, 1)
);
assert!(Instant::now() >= at, "the OS wait must reach its deadline");
assert!(ready.is_empty());
timeouts += 1;
}
assert_eq!(timeouts, 3);
let info = poller.wait(Some(Duration::ZERO), &mut ready).expect("now");
assert_eq!(
(info.waits, info.discovery_polls, info.zero_event_waits),
(0, 1, 1)
);
poller.waker().wake().expect("notify unbounded wait");
let info = poller.wait(None, &mut ready).expect("unbounded wake");
assert_eq!(
(info.waits, info.discovery_polls, info.zero_event_waits),
(1, 0, 0)
);
}
#[test]
fn io_readiness_is_not_a_zero_event_wait() {
use std::{io::Write, os::unix::net::UnixStream};
let mut poller = Epoll::new(4).expect("epoll");
let (reader, mut writer) = UnixStream::pair().expect("socket pair");
reader.set_nonblocking(true).expect("nonblocking");
let key = 1 << 32;
poller.register(reader.as_raw_fd(), key).expect("register");
writer.write_all(b"ready").expect("socket bytes");
let mut ready = Vec::with_capacity(4);
let info = poller
.wait(Some(Duration::from_secs(1)), &mut ready)
.expect("I/O wait");
assert_eq!(
(info.waits, info.discovery_polls, info.zero_event_waits),
(1, 0, 0)
);
assert_eq!(ready.len(), 1);
assert_eq!(ready[0].key, key);
assert!(ready[0].read);
}
#[cfg(feature = "process-sigchld")]
#[test]
fn forced_sigchld_mode_never_registers_a_pidfd() {
let mut poller = Epoll::new(4).expect("epoll");
let error = poller
.process(std::process::id(), 1 << 32)
.expect_err("force SIGCHLD");
assert_eq!(error.os, Some(libc::ENOSYS));
}
}