virtio-devices: Extend EpollHelper and EpollHelperHandler

Extending and improving both the structure and the trait allows for more
flexibility regarding what can be achieved with the epoll loop. It
allows for a timeout to be configured instead of the default blocking
behavior. There is a new method in the trait to notify the caller that
the timeout has been reached. And there's a new knob to be notified with
the full list of events before the internal code will actually loop over
every event.

All of these new features are not affecting the previous behavior, and
using EpollHelper::run() should be unchanged.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
This commit is contained in:
Sebastien Boeuf 2022-08-25 10:31:16 +02:00
parent a940f525a8
commit 9d2e835d14

View File

@ -35,6 +35,8 @@ pub enum EpollHelperError {
QueueRingIndex(virtio_queue::Error),
#[error("Failed to handle virtio device events: {0}")]
HandleEvent(anyhow::Error),
#[error("Failed to handle timeout: {0}")]
HandleTimeout(anyhow::Error),
}
pub const EPOLL_HELPER_EVENT_PAUSE: u16 = 0;
@ -42,11 +44,39 @@ pub const EPOLL_HELPER_EVENT_KILL: u16 = 1;
pub const EPOLL_HELPER_EVENT_LAST: u16 = 15;
pub trait EpollHelperHandler {
// Handle one event at a time. The EpollHelper iterates over a list of
// events that have been returned by epoll_wait(). For each event, the
// current method is invoked to let the implementation decide how to process
// the incoming event.
fn handle_event(
&mut self,
helper: &mut EpollHelper,
event: &epoll::Event,
) -> Result<(), EpollHelperError>;
// This method is only invoked if the EpollHelper was configured to call
// epoll_wait() with a valid timeout (different from -1), meaning the call
// won't block forever. When the timeout is reached, and if no even has been
// triggered, this function will be called to let the implementation decide
// how to interpret such situation. By default, it provides a no-op
// implementation.
fn handle_timeout(&mut self, _helper: &mut EpollHelper) -> Result<(), EpollHelperError> {
Ok(())
}
// In some situations, it might be useful to know the full list of events
// triggered while waiting on epoll_wait(). And having this list provided
// prior to the iterations over each event might help make some informed
// decisions. This function should not replace handle_event(), otherwise it
// would completely defeat the purpose of having the loop being factorized
// through the EpollHelper structure.
fn event_list(
&mut self,
_helper: &mut EpollHelper,
_events: &[epoll::Event],
) -> Result<(), EpollHelperError> {
Ok(())
}
}
impl EpollHelper {
@ -88,6 +118,21 @@ impl EpollHelper {
.map_err(EpollHelperError::Ctl)
}
pub fn mod_event_custom(
&mut self,
fd: RawFd,
id: u16,
evts: epoll::Events,
) -> std::result::Result<(), EpollHelperError> {
epoll::ctl(
self.epoll_file.as_raw_fd(),
epoll::ControlOptions::EPOLL_CTL_MOD,
fd,
epoll::Event::new(evts, id.into()),
)
.map_err(EpollHelperError::Ctl)
}
pub fn del_event_custom(
&mut self,
fd: RawFd,
@ -108,6 +153,17 @@ impl EpollHelper {
paused: Arc<AtomicBool>,
paused_sync: Arc<Barrier>,
handler: &mut dyn EpollHelperHandler,
) -> std::result::Result<(), EpollHelperError> {
self.run_with_timeout(paused, paused_sync, handler, -1, false)
}
pub fn run_with_timeout(
&mut self,
paused: Arc<AtomicBool>,
paused_sync: Arc<Barrier>,
handler: &mut dyn EpollHelperHandler,
timeout: i32,
enable_event_list: bool,
) -> std::result::Result<(), EpollHelperError> {
const EPOLL_EVENTS_LEN: usize = 100;
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
@ -121,22 +177,34 @@ impl EpollHelper {
}
loop {
let num_events = match epoll::wait(self.epoll_file.as_raw_fd(), -1, &mut events[..]) {
Ok(res) => res,
Err(e) => {
if e.kind() == std::io::ErrorKind::Interrupted {
// It's well defined from the epoll_wait() syscall
// documentation that the epoll loop can be interrupted
// before any of the requested events occurred or the
// timeout expired. In both those cases, epoll_wait()
// returns an error of type EINTR, but this should not
// be considered as a regular error. Instead it is more
// appropriate to retry, by calling into epoll_wait().
continue;
let num_events =
match epoll::wait(self.epoll_file.as_raw_fd(), timeout, &mut events[..]) {
Ok(res) => res,
Err(e) => {
if e.kind() == std::io::ErrorKind::Interrupted {
// It's well defined from the epoll_wait() syscall
// documentation that the epoll loop can be interrupted
// before any of the requested events occurred or the
// timeout expired. In both those cases, epoll_wait()
// returns an error of type EINTR, but this should not
// be considered as a regular error. Instead it is more
// appropriate to retry, by calling into epoll_wait().
continue;
}
return Err(EpollHelperError::Wait(e));
}
return Err(EpollHelperError::Wait(e));
}
};
};
if num_events == 0 {
// This case happens when the timeout is reached before any of
// the registered events is triggered.
handler.handle_timeout(self)?;
continue;
}
if enable_event_list {
handler.event_list(self, &events[..num_events])?;
}
for event in events.iter().take(num_events) {
let ev_type = event.data as u16;