mirror of https://github.com/smol-rs/polling
323 lines
9.6 KiB
Rust
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,
|
|
})
|
|
}
|
|
}
|