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), QueueRingIndex(virtio_queue::Error),
#[error("Failed to handle virtio device events: {0}")] #[error("Failed to handle virtio device events: {0}")]
HandleEvent(anyhow::Error), HandleEvent(anyhow::Error),
#[error("Failed to handle timeout: {0}")]
HandleTimeout(anyhow::Error),
} }
pub const EPOLL_HELPER_EVENT_PAUSE: u16 = 0; 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 const EPOLL_HELPER_EVENT_LAST: u16 = 15;
pub trait EpollHelperHandler { 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( fn handle_event(
&mut self, &mut self,
helper: &mut EpollHelper, helper: &mut EpollHelper,
event: &epoll::Event, event: &epoll::Event,
) -> Result<(), EpollHelperError>; ) -> 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 { impl EpollHelper {
@ -88,6 +118,21 @@ impl EpollHelper {
.map_err(EpollHelperError::Ctl) .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( pub fn del_event_custom(
&mut self, &mut self,
fd: RawFd, fd: RawFd,
@ -108,6 +153,17 @@ impl EpollHelper {
paused: Arc<AtomicBool>, paused: Arc<AtomicBool>,
paused_sync: Arc<Barrier>, paused_sync: Arc<Barrier>,
handler: &mut dyn EpollHelperHandler, 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> { ) -> std::result::Result<(), EpollHelperError> {
const EPOLL_EVENTS_LEN: usize = 100; const EPOLL_EVENTS_LEN: usize = 100;
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
@ -121,22 +177,34 @@ impl EpollHelper {
} }
loop { loop {
let num_events = match epoll::wait(self.epoll_file.as_raw_fd(), -1, &mut events[..]) { let num_events =
Ok(res) => res, match epoll::wait(self.epoll_file.as_raw_fd(), timeout, &mut events[..]) {
Err(e) => { Ok(res) => res,
if e.kind() == std::io::ErrorKind::Interrupted { Err(e) => {
// It's well defined from the epoll_wait() syscall if e.kind() == std::io::ErrorKind::Interrupted {
// documentation that the epoll loop can be interrupted // It's well defined from the epoll_wait() syscall
// before any of the requested events occurred or the // documentation that the epoll loop can be interrupted
// timeout expired. In both those cases, epoll_wait() // before any of the requested events occurred or the
// returns an error of type EINTR, but this should not // timeout expired. In both those cases, epoll_wait()
// be considered as a regular error. Instead it is more // returns an error of type EINTR, but this should not
// appropriate to retry, by calling into epoll_wait(). // be considered as a regular error. Instead it is more
continue; // 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) { for event in events.iter().take(num_events) {
let ev_type = event.data as u16; let ev_type = event.data as u16;