Fix a design flaw in handling events of host files

This commit is contained in:
Tate, Hongliang Tian 2020-11-18 06:06:12 +00:00 committed by Zongmin.Gu
parent 798cbfd843
commit a857cf9bfb
8 changed files with 103 additions and 67 deletions

@ -116,11 +116,11 @@ impl File for EventFile {
Some(&self.host_fd) Some(&self.host_fd)
} }
fn recv_host_events(&self, events: &IoEvents, trigger_notifier: bool) { fn update_host_events(&self, ready: &IoEvents, mask: &IoEvents, trigger_notifier: bool) {
self.host_events.store(*events, Ordering::Release); self.host_events.update(ready, mask, Ordering::Release);
if trigger_notifier { if trigger_notifier {
self.notifier.broadcast(events); self.notifier.broadcast(ready);
} }
} }

@ -1,3 +1,5 @@
use atomic::{Atomic, Ordering};
use crate::events::{Event, EventFilter, Notifier, Observer}; use crate::events::{Event, EventFilter, Notifier, Observer};
use crate::prelude::*; use crate::prelude::*;
@ -38,6 +40,32 @@ impl IoEvents {
impl Event for IoEvents {} impl Event for IoEvents {}
pub trait AtomicIoEvents {
/// Update the IoEvents atomically.
///
/// The update is equivalent to the following assignment
/// ```
/// self.store(self.load(Ordering::Relaxed) & !*mask | *ready, ordering)
/// ```
fn update(&self, ready: &IoEvents, mask: &IoEvents, ordering: Ordering);
}
impl AtomicIoEvents for Atomic<IoEvents> {
fn update(&self, ready: &IoEvents, mask: &IoEvents, ordering: Ordering) {
loop {
let old_val = self.load(Ordering::Relaxed);
let new_val = old_val & !*mask | *ready;
let success_ordering = ordering;
let failure_ordering = Ordering::Relaxed;
if let Ok(_) =
self.compare_exchange(old_val, new_val, success_ordering, failure_ordering)
{
return;
}
}
}
}
impl EventFilter<IoEvents> for IoEvents { impl EventFilter<IoEvents> for IoEvents {
fn filter(&self, events: &IoEvents) -> bool { fn filter(&self, events: &IoEvents) -> bool {
self.intersects(*events) self.intersects(*events)

@ -123,13 +123,13 @@ pub trait File: Debug + Sync + Send + Any {
return None; return None;
} }
/// Receive events from the host. /// Update the ready events of a host file.
/// ///
/// After calling this method, the `poll` method of the `File` trait will /// After calling this method, the `poll` method of the `File` trait will
/// return the latest events on the `HostFile`. /// return the latest event state on the `HostFile`.
/// ///
/// This method has no effect if the `host_fd` method returns `None`. /// This method has no effect if the `host_fd` method returns `None`.
fn recv_host_events(&self, events: &IoEvents, trigger_notifier: bool) {} fn update_host_events(&self, ready: &IoEvents, mask: &IoEvents, trigger_notifier: bool) {}
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
} }

@ -12,7 +12,7 @@ use untrusted::{SliceAsMutPtrAndLen, SliceAsPtrAndLen};
pub use self::dev_fs::AsDevRandom; pub use self::dev_fs::AsDevRandom;
pub use self::event_file::{AsEvent, EventCreationFlags, EventFile}; pub use self::event_file::{AsEvent, EventCreationFlags, EventFile};
pub use self::events::{IoEvents, IoNotifier}; pub use self::events::{AtomicIoEvents, IoEvents, IoNotifier};
pub use self::file::{File, FileRef}; pub use self::file::{File, FileRef};
pub use self::file_ops::{ pub use self::file_ops::{
occlum_ocall_ioctl, AccessMode, BuiltinIoctlNum, CreationFlags, FileMode, Flock, FlockType, occlum_ocall_ioctl, AccessMode, BuiltinIoctlNum, CreationFlags, FileMode, Flock, FlockType,

@ -6,11 +6,13 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Weak; use std::sync::Weak;
use std::time::Duration; use std::time::Duration;
use atomic::Atomic;
use super::epoll_waiter::EpollWaiter; use super::epoll_waiter::EpollWaiter;
use super::host_file_epoller::HostFileEpoller; use super::host_file_epoller::HostFileEpoller;
use super::{EpollCtl, EpollEvent, EpollFlags}; use super::{EpollCtl, EpollEvent, EpollFlags};
use crate::events::{Observer, Waiter, WaiterQueue}; use crate::events::{Observer, Waiter, WaiterQueue};
use crate::fs::{File, HostFd, IoEvents, IoNotifier}; use crate::fs::{AtomicIoEvents, File, HostFd, IoEvents, IoNotifier};
use crate::prelude::*; use crate::prelude::*;
// TODO: Prevent two epoll files from monitoring each other, which may cause // TODO: Prevent two epoll files from monitoring each other, which may cause
@ -54,6 +56,8 @@ pub struct EpollFile {
host_file_epoller: HostFileEpoller, host_file_epoller: HostFileEpoller,
// Any EpollFile is wrapped with Arc when created. // Any EpollFile is wrapped with Arc when created.
weak_self: Weak<Self>, weak_self: Weak<Self>,
// Host events
host_events: Atomic<IoEvents>,
} }
impl EpollFile { impl EpollFile {
@ -64,6 +68,7 @@ impl EpollFile {
let notifier = IoNotifier::new(); let notifier = IoNotifier::new();
let host_file_epoller = HostFileEpoller::new(); let host_file_epoller = HostFileEpoller::new();
let weak_self = Default::default(); let weak_self = Default::default();
let host_events = Atomic::new(IoEvents::empty());
Self { Self {
interest, interest,
@ -72,6 +77,7 @@ impl EpollFile {
notifier, notifier,
host_file_epoller, host_file_epoller,
weak_self, weak_self,
host_events,
} }
.wrap_self() .wrap_self()
} }
@ -119,7 +125,12 @@ impl EpollFile {
let waiter = EpollWaiter::new(&self.host_file_epoller); let waiter = EpollWaiter::new(&self.host_file_epoller);
loop { loop {
// Poll the latest states of the interested host files // Poll the latest states of the interested host files. If a host
// file is ready, then it will be pushed into the ready list. Note
// that this is the only way through which a host file can appear in
// the ready list. This ensures that only the host files whose
// events are update-to-date will be returned, reducing the chances
// of false positive results to the minimum.
self.host_file_epoller.poll_events(max_count); self.host_file_epoller.poll_events(max_count);
// Prepare for the waiter.wait_mut() at the end of the loop // Prepare for the waiter.wait_mut() at the end of the loop
@ -167,7 +178,11 @@ impl EpollFile {
.intersects(EpollFlags::EDGE_TRIGGER | EpollFlags::ONE_SHOT) .intersects(EpollFlags::EDGE_TRIGGER | EpollFlags::ONE_SHOT)
{ {
drop(inner); drop(inner);
reinsert.push_back(ep_entry);
// Host files should not be reinserted into the ready list
if ep_entry.file.host_fd().is_none() {
reinsert.push_back(ep_entry);
}
} }
} }
} }
@ -230,6 +245,7 @@ impl EpollFile {
if ep_entry.file.host_fd().is_some() { if ep_entry.file.host_fd().is_some() {
self.host_file_epoller self.host_file_epoller
.add_file(ep_entry.file.clone(), event, flags); .add_file(ep_entry.file.clone(), event, flags);
return Ok(());
} }
} }
@ -281,6 +297,7 @@ impl EpollFile {
if ep_entry.file.host_fd().is_some() { if ep_entry.file.host_fd().is_some() {
self.host_file_epoller self.host_file_epoller
.mod_file(&ep_entry.file, event, flags); .mod_file(&ep_entry.file, event, flags);
return Ok(());
} }
ep_entry ep_entry
@ -365,7 +382,11 @@ impl EpollFile {
impl File for EpollFile { impl File for EpollFile {
fn poll_new(&self) -> IoEvents { fn poll_new(&self) -> IoEvents {
if !self.host_file_epoller.poll().is_empty() { if self
.host_events
.load(Ordering::Acquire)
.contains(IoEvents::IN)
{
return IoEvents::IN; return IoEvents::IN;
} }
@ -385,11 +406,11 @@ impl File for EpollFile {
Some(self.host_file_epoller.host_fd()) Some(self.host_file_epoller.host_fd())
} }
fn recv_host_events(&self, events: &IoEvents, trigger_notifier: bool) { fn update_host_events(&self, ready: &IoEvents, mask: &IoEvents, trigger_notifier: bool) {
self.host_file_epoller.recv_host_events(events); self.host_events.update(ready, mask, Ordering::Release);
if trigger_notifier { if trigger_notifier {
self.notifier.broadcast(events); self.notifier.broadcast(ready);
} }
} }

@ -10,14 +10,12 @@ use crate::prelude::*;
#[derive(Debug)] #[derive(Debug)]
pub struct HostFileEpoller { pub struct HostFileEpoller {
/// A map from host fd to HostFile, which maintains the set of the interesting /// A map from host fd to HostFile, which maintains the set of the interesting
/// host files. /// host files and their interesting events.
host_files: SgxMutex<HashMap<FileDesc, FileRef>>, host_files_and_events: SgxMutex<HashMap<FileDesc, (FileRef, IoEvents)>>,
/// The number of the interesting host files. /// The number of the interesting host files.
count: AtomicUsize, count: AtomicUsize,
/// The host fd of the underlying host epoll file. /// The host fd of the underlying host epoll file.
host_epoll_fd: HostFd, host_epoll_fd: HostFd,
/// Whether the host epoll file has any events.
is_ready: AtomicBool,
} }
// TODO: the `add/mod/del_file` operation can be postponed until a `poll_files` operation, // TODO: the `add/mod/del_file` operation can be postponed until a `poll_files` operation,
@ -25,7 +23,7 @@ pub struct HostFileEpoller {
impl HostFileEpoller { impl HostFileEpoller {
pub fn new() -> Self { pub fn new() -> Self {
let host_files = Default::default(); let host_files_and_events = Default::default();
let count = Default::default(); let count = Default::default();
let host_epoll_fd = { let host_epoll_fd = {
let raw_host_fd = (|| -> Result<u32> { let raw_host_fd = (|| -> Result<u32> {
@ -36,19 +34,19 @@ impl HostFileEpoller {
HostFd::new(raw_host_fd) HostFd::new(raw_host_fd)
}; };
let is_ready = Default::default();
Self { Self {
host_files, host_files_and_events,
count, count,
host_epoll_fd, host_epoll_fd,
is_ready,
} }
} }
pub fn add_file(&self, host_file: FileRef, event: EpollEvent, flags: EpollFlags) -> Result<()> { pub fn add_file(&self, host_file: FileRef, event: EpollEvent, flags: EpollFlags) -> Result<()> {
let mut host_files = self.host_files.lock().unwrap(); let mut host_files_and_events = self.host_files_and_events.lock().unwrap();
let host_fd = host_file.host_fd().unwrap().to_raw(); let host_fd = host_file.host_fd().unwrap().to_raw();
let already_added = host_files.insert(host_fd, host_file.clone()).is_some(); let already_added = host_files_and_events
.insert(host_fd, (host_file.clone(), event.mask))
.is_some();
if already_added { if already_added {
// TODO: handle the case where one host file is somehow to be added more than once. // TODO: handle the case where one host file is somehow to be added more than once.
warn!( warn!(
@ -62,7 +60,7 @@ impl HostFileEpoller {
self.do_epoll_ctl(libc::EPOLL_CTL_ADD, &host_file, Some((event, flags))) self.do_epoll_ctl(libc::EPOLL_CTL_ADD, &host_file, Some((event, flags)))
// Concurrency note: // Concurrency note:
// The lock on self.host_files must be hold while invoking // The lock on self.host_files_and_events must be hold while invoking
// do_epoll_ctl to prevent race conditions that cause the OCall to fail. // do_epoll_ctl to prevent race conditions that cause the OCall to fail.
// This same argument applies to mod_file and del_file methods. // This same argument applies to mod_file and del_file methods.
} }
@ -70,23 +68,28 @@ impl HostFileEpoller {
pub fn mod_file( pub fn mod_file(
&self, &self,
host_file: &FileRef, host_file: &FileRef,
event: EpollEvent, new_event: EpollEvent,
flags: EpollFlags, new_flags: EpollFlags,
) -> Result<()> { ) -> Result<()> {
let host_files = self.host_files.lock().unwrap(); let mut host_files_and_events = self.host_files_and_events.lock().unwrap();
let host_fd = host_file.host_fd().unwrap().to_raw(); let host_fd = host_file.host_fd().unwrap().to_raw();
let not_added = !host_files.contains_key(&host_fd); let event = match host_files_and_events.get_mut(&host_fd) {
if not_added { None => return_errno!(ENOENT, "the host file must be added before modifying"),
return_errno!(ENOENT, "the host file must be added before modifying"); Some((_, event)) => event,
} };
*event = new_event.mask;
self.do_epoll_ctl(libc::EPOLL_CTL_MOD, &host_file, Some((event, flags))) self.do_epoll_ctl(
libc::EPOLL_CTL_MOD,
&host_file,
Some((new_event, new_flags)),
)
} }
pub fn del_file(&self, host_file: &FileRef) -> Result<()> { pub fn del_file(&self, host_file: &FileRef) -> Result<()> {
let mut host_files = self.host_files.lock().unwrap(); let mut host_files_and_events = self.host_files_and_events.lock().unwrap();
let host_fd = host_file.host_fd().unwrap().to_raw(); let host_fd = host_file.host_fd().unwrap().to_raw();
let not_added = !host_files.remove(&host_fd).is_some(); let not_added = !host_files_and_events.remove(&host_fd).is_some();
if not_added { if not_added {
return_errno!(ENOENT, "the host file must be added before deleting"); return_errno!(ENOENT, "the host file must be added before deleting");
} }
@ -153,13 +156,13 @@ impl HostFileEpoller {
// Use the polled events from the host to update the states of the // Use the polled events from the host to update the states of the
// corresponding host files // corresponding host files
let mut host_files = self.host_files.lock().unwrap(); let mut host_files_and_events = self.host_files_and_events.lock().unwrap();
for raw_event in &raw_events[..count] { for raw_event in &raw_events[..count] {
let raw_event = unsafe { raw_event.assume_init() }; let raw_event = unsafe { raw_event.assume_init() };
let io_events = IoEvents::from_raw(raw_event.events as u32); let io_events = IoEvents::from_raw(raw_event.events as u32);
let host_fd = raw_event.u64 as u32; let host_fd = raw_event.u64 as u32;
let host_file = match host_files.get(&host_fd) { let (host_file, mask) = match host_files_and_events.get(&host_fd) {
None => { None => {
count -= 1; count -= 1;
// The corresponding host file may be deleted // The corresponding host file may be deleted
@ -168,7 +171,7 @@ impl HostFileEpoller {
Some(host_file) => host_file, Some(host_file) => host_file,
}; };
host_file.recv_host_events(&io_events, true); host_file.update_host_events(&io_events, mask, true);
} }
count count
} }
@ -176,19 +179,6 @@ impl HostFileEpoller {
pub fn host_fd(&self) -> &HostFd { pub fn host_fd(&self) -> &HostFd {
&self.host_epoll_fd &self.host_epoll_fd
} }
pub fn poll(&self) -> IoEvents {
if self.is_ready.load(Ordering::Acquire) {
IoEvents::IN
} else {
IoEvents::empty()
}
}
pub fn recv_host_events(&self, events: &IoEvents) {
let is_ready = events.contains(IoEvents::IN);
self.is_ready.store(is_ready, Ordering::Release);
}
} }
impl Drop for HostFileEpoller { impl Drop for HostFileEpoller {

@ -4,7 +4,7 @@ use std::sync::Weak;
use std::time::Duration; use std::time::Duration;
use crate::events::{Observer, Waiter, WaiterQueueObserver}; use crate::events::{Observer, Waiter, WaiterQueueObserver};
use crate::fs::IoEvents; use crate::fs::{AtomicIoEvents, IoEvents};
use crate::prelude::*; use crate::prelude::*;
use crate::time::{timespec_t, TIMERSLACK}; use crate::time::{timespec_t, TIMERSLACK};
@ -41,10 +41,10 @@ impl EventMonitor {
} }
/// Returns an iterator for the host files in the set of the interesting files. /// Returns an iterator for the host files in the set of the interesting files.
pub fn host_files(&self) -> impl Iterator<Item = &FileRef> { pub fn host_files_and_events(&self) -> impl Iterator<Item = &(FileRef, IoEvents)> {
self.host_file_idxes self.host_file_idxes
.iter() .iter()
.map(move |idx| &self.files_and_events[*idx].0) .map(move |idx| &self.files_and_events[*idx])
} }
/// Reset the monitor so that it can wait for new events. /// Reset the monitor so that it can wait for new events.
@ -80,7 +80,7 @@ impl EventMonitor {
// 4. the time is up. // 4. the time is up.
let num_events = self.do_poll_ocall(&mut timeout)?; let num_events = self.do_poll_ocall(&mut timeout)?;
self.recv_host_file_events(num_events); self.update_host_file_events(num_events);
// Poll syscall does not treat timeout as error. So we need // Poll syscall does not treat timeout as error. So we need
// to distinguish the case by ourselves. // to distinguish the case by ourselves.
@ -104,7 +104,7 @@ impl EventMonitor {
Err(_) => return, Err(_) => return,
}; };
self.recv_host_file_events(num_events); self.update_host_file_events(num_events);
} }
fn do_poll_ocall(&mut self, timeout: &mut Option<&mut Duration>) -> Result<usize> { fn do_poll_ocall(&mut self, timeout: &mut Option<&mut Duration>) -> Result<usize> {
@ -148,22 +148,19 @@ impl EventMonitor {
Ok(num_events) Ok(num_events)
} }
fn recv_host_file_events(&self, num_events: usize) { fn update_host_file_events(&self, num_events: usize) {
if num_events == 0 { if num_events == 0 {
return; return;
} }
// According to the output pollfds, update the states of the corresponding host files // According to the output pollfds, update the states of the corresponding host files
let output_pollfds = self.ocall_pollfds[..self.ocall_pollfds.len() - 1].iter(); let output_pollfds = self.ocall_pollfds[..self.ocall_pollfds.len() - 1].iter();
for (pollfd, host_file) in output_pollfds.zip(self.host_files()) { for (pollfd, (host_file, mask)) in output_pollfds.zip(self.host_files_and_events()) {
let revents = { let revents = {
if pollfd.revents == 0 {
continue;
}
assert!((pollfd.revents & libc::POLLNVAL) == 0); assert!((pollfd.revents & libc::POLLNVAL) == 0);
IoEvents::from_raw(pollfd.revents as u32) IoEvents::from_raw(pollfd.revents as u32)
}; };
host_file.recv_host_events(&revents, false); host_file.update_host_events(&revents, mask, false);
} }
} }
} }

@ -5,8 +5,8 @@ use atomic::{Atomic, Ordering};
use super::*; use super::*;
use crate::fs::{ use crate::fs::{
occlum_ocall_ioctl, AccessMode, CreationFlags, File, FileRef, HostFd, IoEvents, IoctlCmd, occlum_ocall_ioctl, AccessMode, AtomicIoEvents, CreationFlags, File, FileRef, HostFd, IoEvents,
StatusFlags, IoctlCmd, StatusFlags,
}; };
//TODO: refactor write syscall to allow zero length with non-zero buffer //TODO: refactor write syscall to allow zero length with non-zero buffer
@ -89,11 +89,11 @@ impl File for HostSocket {
Some(&self.notifier) Some(&self.notifier)
} }
fn recv_host_events(&self, events: &IoEvents, trigger_notifier: bool) { fn update_host_events(&self, ready: &IoEvents, mask: &IoEvents, trigger_notifier: bool) {
self.host_events.store(*events, Ordering::Release); self.host_events.update(ready, mask, Ordering::Release);
if trigger_notifier { if trigger_notifier {
self.notifier.broadcast(events); self.notifier.broadcast(ready);
} }
} }