Sub-nanosecond precision for epoll

This commit is contained in:
Stjepan Glavina 2020-08-14 13:46:58 +02:00
parent 4a1a2d673a
commit c6818f038f
3 changed files with 101 additions and 18 deletions

View File

@ -1,6 +1,5 @@
//! Bindings to epoll (Linux, Android).
use std::convert::TryInto;
use std::io;
use std::os::unix::io::RawFd;
use std::ptr;
@ -16,6 +15,8 @@ pub struct Poller {
epoll_fd: RawFd,
/// File descriptor for the eventfd that produces notifications.
event_fd: RawFd,
/// File descriptor for the timerfd that produces notifications.
timer_fd: RawFd,
}
impl Poller {
@ -58,10 +59,19 @@ impl Poller {
}
};
// Set up eventfd.
// Set up eventfd and timerfd.
let event_fd = syscall!(eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK))?;
let poller = Poller { epoll_fd, event_fd };
let timer_fd = syscall!(timerfd_create(
libc::CLOCK_MONOTONIC,
libc::TFD_CLOEXEC | libc::TFD_NONBLOCK,
))?;
let poller = Poller {
epoll_fd,
event_fd,
timer_fd,
};
poller.insert(event_fd)?;
poller.insert(timer_fd)?;
poller.interest(
event_fd,
Event {
@ -127,25 +137,35 @@ impl Poller {
/// If a notification occurs, the notification event will be included in the `events` list
/// identifiable by key `usize::MAX`.
pub fn wait(&self, events: &mut Events, timeout: Option<Duration>) -> io::Result<usize> {
// Convert the timeout to milliseconds.
let timeout_ms = timeout
.map(|t| {
if t == Duration::from_millis(0) {
t
} else {
// Non-zero duration must be at least 1ms.
t.max(Duration::from_millis(1))
}
})
.and_then(|t| t.as_millis().try_into().ok())
.unwrap_or(-1);
// Convert the `Duration` to `libc::timespec`.
let timeout = timeout.map(|t| libc::timespec {
tv_sec: t.as_secs() as libc::time_t,
tv_nsec: t.subsec_nanos() as libc::c_long,
});
// Set interest in timerfd.
self.interest(
self.timer_fd,
Event {
key: NOTIFY_KEY,
readable: true,
writable: false,
},
)?;
// Configure the timeout using timerfd.
let new_val = libc::itimerspec {
it_interval: TS_ZERO,
it_value: timeout.unwrap_or(TS_ZERO),
};
syscall!(timerfd_settime(self.timer_fd, 0, &new_val, ptr::null_mut()))?;
// 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,
-1,
))?;
events.len = res as usize;
@ -183,11 +203,19 @@ impl Poller {
impl Drop for Poller {
fn drop(&mut self) {
let _ = self.remove(self.event_fd);
let _ = self.remove(self.timer_fd);
let _ = syscall!(close(self.event_fd));
let _ = syscall!(close(self.timer_fd));
let _ = syscall!(close(self.epoll_fd));
}
}
/// `timespec` value that equals zero.
const TS_ZERO: libc::timespec = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
/// Key associated with the eventfd for producing notifications.
const NOTIFY_KEY: usize = usize::MAX;

View File

@ -337,9 +337,10 @@ impl Poller {
/// ```
pub fn wait(&self, events: &mut Vec<Event>, timeout: Option<Duration>) -> io::Result<usize> {
if let Ok(mut lock) = self.events.try_lock() {
let n = self.poller.wait(&mut lock, timeout)?;
self.poller.wait(&mut lock, timeout)?;
let len = events.len();
events.extend(lock.iter().filter(|ev| ev.key != usize::MAX));
Ok(n)
Ok(events.len() - len)
} else {
Ok(0)
}

54
tests/precision.rs Normal file
View File

@ -0,0 +1,54 @@
//! Makes sure that timeouts have precision lower than 1ns on non-Windows systems.
#![cfg(not(windows))]
use std::io;
use std::time::{Instant, Duration};
use polling::Poller;
#[test]
fn below_ms() -> io::Result<()> {
let poller = Poller::new()?;
let mut events = Vec::new();
let margin = Duration::from_micros(500);
for _ in 0..1_000 {
let now = Instant::now();
let dur = Duration::from_micros(100);
let n = poller.wait(&mut events, Some(dur))?;
let elapsed = now.elapsed();
assert_eq!(n, 0);
assert!(elapsed >= dur);
if elapsed < dur + margin {
return Ok(());
}
}
panic!("timeouts are not precise enough");
}
#[test]
fn above_ms() -> io::Result<()> {
let poller = Poller::new()?;
let mut events = Vec::new();
let margin = Duration::from_micros(500);
for _ in 0..1_000 {
let now = Instant::now();
let dur = Duration::from_micros(10_100);
let n = poller.wait(&mut events, Some(dur))?;
let elapsed = now.elapsed();
assert_eq!(n, 0);
assert!(elapsed >= dur);
if elapsed < dur + margin {
return Ok(());
}
}
panic!("timeouts are not precise enough");
}