polling/src/epoll.rs

323 lines
9.6 KiB
Rust

//! Bindings to epoll (Linux, Android).
use std::convert::TryInto;
use std::io;
use std::os::unix::io::{AsRawFd, RawFd};
use std::ptr;
use std::time::Duration;
#[cfg(not(polling_no_io_safety))]
use std::os::unix::io::{AsFd, BorrowedFd};
use crate::{Event, PollMode};
/// Interface to epoll.
#[derive(Debug)]
pub struct Poller {
/// File descriptor for the epoll instance.
epoll_fd: RawFd,
/// File descriptor for the eventfd that produces notifications.
event_fd: RawFd,
/// File descriptor for the timerfd that produces timeouts.
timer_fd: Option<RawFd>,
}
impl Poller {
/// Creates a new poller.
pub fn new() -> io::Result<Poller> {
// Create an epoll instance.
//
// Use `epoll_create1` with `EPOLL_CLOEXEC`.
let epoll_fd = syscall!(syscall(
libc::SYS_epoll_create1,
libc::EPOLL_CLOEXEC as libc::c_int
))
.map(|fd| fd as libc::c_int)
.or_else(|e| {
match e.raw_os_error() {
Some(libc::ENOSYS) => {
// If `epoll_create1` is not implemented, use `epoll_create`
// and manually set `FD_CLOEXEC`.
let fd = syscall!(epoll_create(1024))?;
if let Ok(flags) = syscall!(fcntl(fd, libc::F_GETFD)) {
let _ = syscall!(fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC));
}
Ok(fd)
}
_ => Err(e),
}
})?;
// Set up eventfd and timerfd.
let event_fd = syscall!(eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK))?;
let timer_fd = syscall!(syscall(
libc::SYS_timerfd_create,
libc::CLOCK_MONOTONIC as libc::c_int,
(libc::TFD_CLOEXEC | libc::TFD_NONBLOCK) as libc::c_int,
))
.map(|fd| fd as libc::c_int)
.ok();
let poller = Poller {
epoll_fd,
event_fd,
timer_fd,
};
if let Some(timer_fd) = timer_fd {
poller.add(timer_fd, Event::none(crate::NOTIFY_KEY), PollMode::Oneshot)?;
}
poller.add(
event_fd,
Event {
key: crate::NOTIFY_KEY,
readable: true,
writable: false,
},
PollMode::Oneshot,
)?;
log::trace!(
"new: epoll_fd={}, event_fd={}, timer_fd={:?}",
epoll_fd,
event_fd,
timer_fd
);
Ok(poller)
}
/// Whether this poller supports level-triggered events.
pub fn supports_level(&self) -> bool {
true
}
/// Whether the poller supports edge-triggered events.
pub fn supports_edge(&self) -> bool {
true
}
/// Adds a new file descriptor.
pub fn add(&self, fd: RawFd, ev: Event, mode: PollMode) -> io::Result<()> {
log::trace!("add: epoll_fd={}, fd={}, ev={:?}", self.epoll_fd, fd, ev);
self.ctl(libc::EPOLL_CTL_ADD, fd, Some((ev, mode)))
}
/// Modifies an existing file descriptor.
pub fn modify(&self, fd: RawFd, ev: Event, mode: PollMode) -> io::Result<()> {
log::trace!("modify: epoll_fd={}, fd={}, ev={:?}", self.epoll_fd, fd, ev);
self.ctl(libc::EPOLL_CTL_MOD, fd, Some((ev, mode)))
}
/// Deletes a file descriptor.
pub fn delete(&self, fd: RawFd) -> io::Result<()> {
log::trace!("remove: epoll_fd={}, fd={}", self.epoll_fd, fd);
self.ctl(libc::EPOLL_CTL_DEL, fd, None)
}
/// Waits for I/O events with an optional timeout.
pub fn wait(&self, events: &mut Events, timeout: Option<Duration>) -> io::Result<()> {
log::trace!("wait: epoll_fd={}, timeout={:?}", self.epoll_fd, timeout);
if let Some(timer_fd) = self.timer_fd {
// Configure the timeout using timerfd.
let new_val = libc::itimerspec {
it_interval: TS_ZERO,
it_value: match timeout {
None => TS_ZERO,
Some(t) => {
let mut ts = TS_ZERO;
ts.tv_sec = t.as_secs() as libc::time_t;
ts.tv_nsec = (t.subsec_nanos() as libc::c_long).into();
ts
}
},
};
syscall!(timerfd_settime(
timer_fd as libc::c_int,
0 as libc::c_int,
&new_val as *const libc::itimerspec,
ptr::null_mut() as *mut libc::itimerspec
))?;
// Set interest in timerfd.
self.modify(
timer_fd,
Event {
key: crate::NOTIFY_KEY,
readable: true,
writable: false,
},
PollMode::Oneshot,
)?;
}
// Timeout in milliseconds for epoll.
let timeout_ms = match (self.timer_fd, timeout) {
(_, Some(t)) if t == Duration::from_secs(0) => 0,
(None, Some(t)) => {
// Round up to a whole millisecond.
let mut ms = t.as_millis().try_into().unwrap_or(std::i32::MAX);
if Duration::from_millis(ms as u64) < t {
ms = ms.saturating_add(1);
}
ms
}
_ => -1,
};
// Wait for I/O events.
let res = syscall!(epoll_wait(
self.epoll_fd,
events.list.as_mut_ptr() as *mut libc::epoll_event,
events.list.len() as libc::c_int,
timeout_ms as libc::c_int,
))?;
events.len = res as usize;
log::trace!("new events: epoll_fd={}, res={}", self.epoll_fd, res);
// Clear the notification (if received) and re-register interest in it.
let mut buf = [0u8; 8];
let _ = syscall!(read(
self.event_fd,
buf.as_mut_ptr() as *mut libc::c_void,
buf.len()
));
self.modify(
self.event_fd,
Event {
key: crate::NOTIFY_KEY,
readable: true,
writable: false,
},
PollMode::Oneshot,
)?;
Ok(())
}
/// Sends a notification to wake up the current or next `wait()` call.
pub fn notify(&self) -> io::Result<()> {
log::trace!(
"notify: epoll_fd={}, event_fd={}",
self.epoll_fd,
self.event_fd
);
let buf: [u8; 8] = 1u64.to_ne_bytes();
let _ = syscall!(write(
self.event_fd,
buf.as_ptr() as *const libc::c_void,
buf.len()
));
Ok(())
}
/// Passes arguments to `epoll_ctl`.
fn ctl(&self, op: libc::c_int, fd: RawFd, ev: Option<(Event, PollMode)>) -> io::Result<()> {
let mut ev = ev.map(|(ev, mode)| {
let mut flags = match mode {
PollMode::Oneshot => libc::EPOLLONESHOT,
PollMode::Level => 0,
PollMode::Edge => libc::EPOLLET,
PollMode::EdgeOneshot => libc::EPOLLONESHOT | libc::EPOLLET,
};
if ev.readable {
flags |= read_flags();
}
if ev.writable {
flags |= write_flags();
}
libc::epoll_event {
events: flags as _,
u64: ev.key as u64,
}
});
syscall!(epoll_ctl(
self.epoll_fd,
op,
fd,
ev.as_mut()
.map(|ev| ev as *mut libc::epoll_event)
.unwrap_or(ptr::null_mut()),
))?;
Ok(())
}
}
impl AsRawFd for Poller {
fn as_raw_fd(&self) -> RawFd {
self.epoll_fd
}
}
#[cfg(not(polling_no_io_safety))]
impl AsFd for Poller {
fn as_fd(&self) -> BorrowedFd<'_> {
// SAFETY: lifetime is bound by "self"
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
}
}
impl Drop for Poller {
fn drop(&mut self) {
log::trace!(
"drop: epoll_fd={}, event_fd={}, timer_fd={:?}",
self.epoll_fd,
self.event_fd,
self.timer_fd
);
if let Some(timer_fd) = self.timer_fd {
let _ = self.delete(timer_fd);
let _ = syscall!(close(timer_fd));
}
let _ = self.delete(self.event_fd);
let _ = syscall!(close(self.event_fd));
let _ = syscall!(close(self.epoll_fd));
}
}
/// `timespec` value that equals zero.
const TS_ZERO: libc::timespec =
unsafe { std::mem::transmute([0u8; std::mem::size_of::<libc::timespec>()]) };
/// Epoll flags for all possible readability events.
fn read_flags() -> libc::c_int {
libc::EPOLLIN | libc::EPOLLRDHUP | libc::EPOLLHUP | libc::EPOLLERR | libc::EPOLLPRI
}
/// Epoll flags for all possible writability events.
fn write_flags() -> libc::c_int {
libc::EPOLLOUT | libc::EPOLLHUP | libc::EPOLLERR
}
/// A list of reported I/O events.
pub struct Events {
list: Box<[libc::epoll_event; 1024]>,
len: usize,
}
unsafe impl Send for Events {}
impl Events {
/// Creates an empty list.
pub fn new() -> Events {
let ev = libc::epoll_event { events: 0, u64: 0 };
let list = Box::new([ev; 1024]);
let len = 0;
Events { list, len }
}
/// Iterates over I/O events.
pub fn iter(&self) -> impl Iterator<Item = Event> + '_ {
self.list[..self.len].iter().map(|ev| Event {
key: ev.u64 as usize,
readable: (ev.events as libc::c_int & read_flags()) != 0,
writable: (ev.events as libc::c_int & write_flags()) != 0,
})
}
}