blob: 45edf9f8c4e77e8920587e8c9a44a98c852a180a [file] [edit]
// Copyright 2017 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::cmp::min;
use std::fs::File;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::ptr::null_mut;
use std::time::Duration;
use libc::c_int;
use libc::epoll_create1;
use libc::epoll_ctl;
use libc::epoll_event;
use libc::epoll_wait;
use libc::ENOENT;
use libc::EPOLLHUP;
use libc::EPOLLIN;
use libc::EPOLLOUT;
use libc::EPOLLRDHUP;
use libc::EPOLL_CLOEXEC;
use libc::EPOLL_CTL_ADD;
use libc::EPOLL_CTL_DEL;
use libc::EPOLL_CTL_MOD;
use smallvec::SmallVec;
use super::errno_result;
use super::Result;
use crate::handle_eintr_errno;
use crate::AsRawDescriptor;
use crate::EventToken;
use crate::EventType;
use crate::FromRawDescriptor;
use crate::RawDescriptor;
use crate::TriggeredEvent;
const EVENT_CONTEXT_MAX_EVENTS: usize = 16;
impl From<EventType> for u32 {
fn from(et: EventType) -> u32 {
let v = match et {
EventType::None => 0,
EventType::Read => EPOLLIN,
EventType::Write => EPOLLOUT,
EventType::ReadWrite => EPOLLIN | EPOLLOUT,
};
v as u32
}
}
/// Used to poll multiple objects that have file descriptors.
///
/// See [`crate::WaitContext`] for an example that uses the cross-platform wrapper.
pub struct EventContext<T> {
epoll_ctx: File,
// Needed to satisfy usage of T
tokens: PhantomData<[T]>,
}
impl<T: EventToken> EventContext<T> {
/// Creates a new `EventContext`.
pub fn new() -> Result<EventContext<T>> {
// SAFETY:
// Safe because we check the return value.
let epoll_fd = unsafe { epoll_create1(EPOLL_CLOEXEC) };
if epoll_fd < 0 {
return errno_result();
}
Ok(EventContext {
// SAFETY:
// Safe because epoll_fd is valid.
epoll_ctx: unsafe { File::from_raw_descriptor(epoll_fd) },
tokens: PhantomData,
})
}
/// Creates a new `EventContext` and adds the slice of `fd` and `token` tuples to the new
/// context.
///
/// This is equivalent to calling `new` followed by `add_many`. If there is an error, this will
/// return the error instead of the new context.
pub fn build_with(fd_tokens: &[(&dyn AsRawDescriptor, T)]) -> Result<EventContext<T>> {
let ctx = EventContext::new()?;
ctx.add_many(fd_tokens)?;
Ok(ctx)
}
/// Adds the given slice of `fd` and `token` tuples to this context.
///
/// This is equivalent to calling `add` with each `fd` and `token`. If there are any errors,
/// this method will stop adding `fd`s and return the first error, leaving this context in a
/// undefined state.
pub fn add_many(&self, fd_tokens: &[(&dyn AsRawDescriptor, T)]) -> Result<()> {
for (fd, token) in fd_tokens {
self.add(*fd, T::from_raw_token(token.as_raw_token()))?;
}
Ok(())
}
/// Adds the given `fd` to this context and associates the given `token` with the `fd`'s
/// readable events.
///
/// A `fd` can only be added once and does not need to be kept open. If the `fd` is dropped and
/// there were no duplicated file descriptors (i.e. adding the same descriptor with a different
/// FD number) added to this context, events will not be reported by `wait` anymore.
pub fn add(&self, fd: &dyn AsRawDescriptor, token: T) -> Result<()> {
self.add_for_event(fd, EventType::Read, token)
}
/// Adds the given `descriptor` to this context, watching for the specified events and
/// associates the given 'token' with those events.
///
/// A `descriptor` can only be added once and does not need to be kept open. If the `descriptor`
/// is dropped and there were no duplicated file descriptors (i.e. adding the same descriptor
/// with a different FD number) added to this context, events will not be reported by `wait`
/// anymore.
pub fn add_for_event(
&self,
descriptor: &dyn AsRawDescriptor,
event_type: EventType,
token: T,
) -> Result<()> {
let mut evt = epoll_event {
events: event_type.into(),
u64: token.as_raw_token(),
};
// SAFETY:
// Safe because we give a valid epoll FD and FD to watch, as well as a valid epoll_event
// structure. Then we check the return value.
let ret = unsafe {
epoll_ctl(
self.epoll_ctx.as_raw_descriptor(),
EPOLL_CTL_ADD,
descriptor.as_raw_descriptor(),
&mut evt,
)
};
if ret < 0 {
return errno_result();
};
Ok(())
}
/// If `fd` was previously added to this context, the watched events will be replaced with
/// `event_type` and the token associated with it will be replaced with the given `token`.
pub fn modify(&self, fd: &dyn AsRawDescriptor, event_type: EventType, token: T) -> Result<()> {
let mut evt = epoll_event {
events: event_type.into(),
u64: token.as_raw_token(),
};
// SAFETY:
// Safe because we give a valid epoll FD and FD to modify, as well as a valid epoll_event
// structure. Then we check the return value.
let ret = unsafe {
epoll_ctl(
self.epoll_ctx.as_raw_descriptor(),
EPOLL_CTL_MOD,
fd.as_raw_descriptor(),
&mut evt,
)
};
if ret < 0 {
return errno_result();
};
Ok(())
}
/// Deletes the given `fd` from this context. If the `fd` is not being polled by this context,
/// the call is silently dropped without errors.
///
/// If an `fd`'s token shows up in the list of hangup events, it should be removed using this
/// method or by closing/dropping (if and only if the fd was never dup()'d/fork()'d) the `fd`.
/// Failure to do so will cause the `wait` method to always return immediately, causing ~100%
/// CPU load.
pub fn delete(&self, fd: &dyn AsRawDescriptor) -> Result<()> {
// SAFETY:
// Safe because we give a valid epoll FD and FD to stop watching. Then we check the return
// value.
let ret = unsafe {
epoll_ctl(
self.epoll_ctx.as_raw_descriptor(),
EPOLL_CTL_DEL,
fd.as_raw_descriptor(),
null_mut(),
)
};
// If epoll_ctl returns ENOENT it means the fd is not part of the current polling set so
// there is nothing to delete.
if ret < 0 && ret != ENOENT {
return errno_result();
};
Ok(())
}
/// Waits for any events to occur in FDs that were previously added to this context.
///
/// The events are level-triggered, meaning that if any events are unhandled (i.e. not reading
/// for readable events and not closing for hungup events), subsequent calls to `wait` will
/// return immediately. The consequence of not handling an event perpetually while calling
/// `wait` is that the callers loop will degenerated to busy loop polling, pinning a CPU to
/// ~100% usage.
pub fn wait(&self) -> Result<SmallVec<[TriggeredEvent<T>; 16]>> {
self.wait_timeout(Duration::new(i64::MAX as u64, 0))
}
/// Like `wait` except will only block for a maximum of the given `timeout`.
///
/// This may return earlier than `timeout` with zero events if the duration indicated exceeds
/// system limits.
pub fn wait_timeout(&self, timeout: Duration) -> Result<SmallVec<[TriggeredEvent<T>; 16]>> {
let mut epoll_events: [MaybeUninit<epoll_event>; EVENT_CONTEXT_MAX_EVENTS] =
// SAFETY:
// `MaybeUnint<T>` has the same layout as plain `T` (`epoll_event` in our case).
// We submit an uninitialized array to the `epoll_wait` system call, which returns how many
// elements it initialized, and then we convert only the initialized `MaybeUnint` values
// into `epoll_event` structures after the call.
unsafe { MaybeUninit::uninit().assume_init() };
let timeout_millis = if timeout.as_secs() as i64 == i64::MAX {
// We make the convenient assumption that 2^63 seconds is an effectively unbounded time
// frame. This is meant to mesh with `wait` calling us with no timeout.
-1
} else {
// In cases where we the number of milliseconds would overflow an i32, we substitute the
// maximum timeout which is ~24.8 days.
let millis = timeout
.as_secs()
.checked_mul(1_000)
.and_then(|ms| ms.checked_add(u64::from(timeout.subsec_nanos()) / 1_000_000))
.unwrap_or(i32::MAX as u64);
min(i32::MAX as u64, millis) as i32
};
let ret = {
let max_events = epoll_events.len() as c_int;
// SAFETY:
// Safe because we give an epoll context and a properly sized epoll_events array
// pointer, which we trust the kernel to fill in properly. The `transmute` is safe,
// since `MaybeUnint<T>` has the same layout as `T`, and the `epoll_wait` syscall will
// initialize as many elements of the `epoll_events` array as it returns.
unsafe {
handle_eintr_errno!(epoll_wait(
self.epoll_ctx.as_raw_descriptor(),
std::mem::transmute(&mut epoll_events[0]),
max_events,
timeout_millis
))
}
};
if ret < 0 {
return errno_result();
}
let count = ret as usize;
let events = epoll_events[0..count]
.iter()
.map(|e| {
// SAFETY:
// Converting `MaybeUninit<epoll_event>` into `epoll_event` is safe here, since we
// are only iterating over elements that the `epoll_wait` system call initialized.
let e = unsafe { e.assume_init() };
TriggeredEvent {
token: T::from_raw_token(e.u64),
is_readable: e.events & (EPOLLIN as u32) != 0,
is_writable: e.events & (EPOLLOUT as u32) != 0,
is_hungup: e.events & ((EPOLLHUP | EPOLLRDHUP) as u32) != 0,
}
})
.collect();
Ok(events)
}
}
impl<T: EventToken> AsRawDescriptor for EventContext<T> {
fn as_raw_descriptor(&self) -> RawDescriptor {
self.epoll_ctx.as_raw_descriptor()
}
}
#[cfg(test)]
mod tests {
use std::time::Instant;
use base_event_token_derive::EventToken;
use super::*;
use crate::Event;
#[test]
fn event_context() {
let evt1 = Event::new().unwrap();
let evt2 = Event::new().unwrap();
evt1.signal().unwrap();
evt2.signal().unwrap();
let ctx: EventContext<u32> = EventContext::build_with(&[(&evt1, 1), (&evt2, 2)]).unwrap();
let mut evt_count = 0;
while evt_count < 2 {
for event in ctx.wait().unwrap().iter().filter(|e| e.is_readable) {
evt_count += 1;
match event.token {
1 => {
evt1.wait().unwrap();
ctx.delete(&evt1).unwrap();
}
2 => {
evt2.wait().unwrap();
ctx.delete(&evt2).unwrap();
}
_ => panic!("unexpected token"),
};
}
}
assert_eq!(evt_count, 2);
}
#[test]
fn event_context_overflow() {
const EVT_COUNT: usize = EVENT_CONTEXT_MAX_EVENTS * 2 + 1;
let ctx: EventContext<usize> = EventContext::new().unwrap();
let mut evts = Vec::with_capacity(EVT_COUNT);
for i in 0..EVT_COUNT {
let evt = Event::new().unwrap();
evt.signal().unwrap();
ctx.add(&evt, i).unwrap();
evts.push(evt);
}
let mut evt_count = 0;
while evt_count < EVT_COUNT {
for event in ctx.wait().unwrap().iter().filter(|e| e.is_readable) {
evts[event.token].wait().unwrap();
evt_count += 1;
}
}
}
#[test]
fn event_context_timeout() {
let ctx: EventContext<u32> = EventContext::new().unwrap();
let dur = Duration::from_millis(10);
let start_inst = Instant::now();
ctx.wait_timeout(dur).unwrap();
assert!(start_inst.elapsed() >= dur);
}
#[test]
#[allow(dead_code)]
fn event_token_derive() {
#[derive(EventToken)]
enum EmptyToken {}
#[derive(PartialEq, Debug, EventToken)]
enum Token {
Alpha,
Beta,
// comments
Gamma(u32),
Delta { index: usize },
Omega,
}
assert_eq!(
Token::from_raw_token(Token::Alpha.as_raw_token()),
Token::Alpha
);
assert_eq!(
Token::from_raw_token(Token::Beta.as_raw_token()),
Token::Beta
);
assert_eq!(
Token::from_raw_token(Token::Gamma(55).as_raw_token()),
Token::Gamma(55)
);
assert_eq!(
Token::from_raw_token(Token::Delta { index: 100 }.as_raw_token()),
Token::Delta { index: 100 }
);
assert_eq!(
Token::from_raw_token(Token::Omega.as_raw_token()),
Token::Omega
);
}
}