mirror of https://github.com/smol-rs/polling
Sub-nanosecond precision for epoll
This commit is contained in:
parent
4a1a2d673a
commit
c6818f038f
60
src/epoll.rs
60
src/epoll.rs
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
Loading…
Reference in New Issue