[libos] Implement edge/level triggering waiter and poller
This commit is contained in:
parent
bfc97f5ba2
commit
44eb5ca3fe
@ -19,6 +19,7 @@ mod event;
|
|||||||
mod host_event_fd;
|
mod host_event_fd;
|
||||||
mod notifier;
|
mod notifier;
|
||||||
mod observer;
|
mod observer;
|
||||||
|
mod poller;
|
||||||
mod waiter;
|
mod waiter;
|
||||||
mod waiter_queue;
|
mod waiter_queue;
|
||||||
mod waiter_queue_observer;
|
mod waiter_queue_observer;
|
||||||
@ -27,6 +28,7 @@ pub use self::event::{Event, EventFilter};
|
|||||||
pub use self::host_event_fd::HostEventFd;
|
pub use self::host_event_fd::HostEventFd;
|
||||||
pub use self::notifier::Notifier;
|
pub use self::notifier::Notifier;
|
||||||
pub use self::observer::Observer;
|
pub use self::observer::Observer;
|
||||||
pub use self::waiter::{Waiter, Waker};
|
pub use self::poller::{Pollee, Poller};
|
||||||
|
pub use self::waiter::{EdgeSync, LevelSync, Synchronizer, Waiter, Waker};
|
||||||
pub use self::waiter_queue::WaiterQueue;
|
pub use self::waiter_queue::WaiterQueue;
|
||||||
pub use self::waiter_queue_observer::WaiterQueueObserver;
|
pub use self::waiter_queue_observer::WaiterQueueObserver;
|
||||||
|
210
src/libos/src/events/poller.rs
Normal file
210
src/libos/src/events/poller.rs
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
use std::sync::Weak;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use super::{EdgeSync, Notifier, Observer, Waiter};
|
||||||
|
use crate::fs::{IoEvents, IoNotifier};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
/// A pollee maintains a set of active events, which can be polled with
|
||||||
|
/// pollers or be monitored with observers.
|
||||||
|
pub struct Pollee {
|
||||||
|
inner: Arc<PolleeInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PolleeInner {
|
||||||
|
// A table that maintains all interesting pollers
|
||||||
|
pollers: IoNotifier,
|
||||||
|
// For efficient manipulation, we use AtomicU32 instead of Atomic<Events>
|
||||||
|
events: AtomicU32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pollee {
|
||||||
|
/// Creates a new instance of pollee.
|
||||||
|
pub fn new(init_events: IoEvents) -> Self {
|
||||||
|
let inner = PolleeInner {
|
||||||
|
pollers: Notifier::new(),
|
||||||
|
events: AtomicU32::new(init_events.bits()),
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(inner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn notifier(&self) -> &IoNotifier {
|
||||||
|
&self.inner.pollers
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current events of the pollee given an event mask.
|
||||||
|
///
|
||||||
|
/// If no interesting events are polled and a poller is provided, then
|
||||||
|
/// the poller will start monitoring the pollee and receive event
|
||||||
|
/// notification once the pollee gets any interesting events.
|
||||||
|
///
|
||||||
|
/// This operation is _atomic_ in the sense that either some interesting
|
||||||
|
/// events are returned or the poller is registered (if a poller is provided).
|
||||||
|
pub fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents {
|
||||||
|
let mask = mask | IoEvents::ALWAYS_POLL;
|
||||||
|
|
||||||
|
// Fast path: return events immediately
|
||||||
|
if poller.is_none() {
|
||||||
|
let revents = self.events() & mask;
|
||||||
|
return revents;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slow path: connect the pollee with the poller
|
||||||
|
self.connect_poller(mask, poller.unwrap());
|
||||||
|
|
||||||
|
// It is important to check events again to handle race conditions
|
||||||
|
self.events() & mask
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect_poller(&self, mask: IoEvents, poller: &Poller) {
|
||||||
|
self.register_observer(poller.observer(), mask);
|
||||||
|
|
||||||
|
let mut pollees = poller.inner.pollees.lock();
|
||||||
|
pollees.push(Arc::downgrade(&self.inner).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add some events to the pollee's state.
|
||||||
|
///
|
||||||
|
/// This method wakes up all registered pollers that are interested in
|
||||||
|
/// the added events.
|
||||||
|
pub fn add_events(&self, events: IoEvents) {
|
||||||
|
self.inner.events.fetch_or(events.bits(), Ordering::Release);
|
||||||
|
self.inner.pollers.broadcast(&events);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove some events from the pollee's state.
|
||||||
|
///
|
||||||
|
/// This method will not wake up registered pollers even when
|
||||||
|
/// the pollee still has some interesting events to the pollers.
|
||||||
|
pub fn del_events(&self, events: IoEvents) {
|
||||||
|
self.inner
|
||||||
|
.events
|
||||||
|
.fetch_and(!events.bits(), Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the pollee's state.
|
||||||
|
///
|
||||||
|
/// Reset means removing all events on the pollee.
|
||||||
|
pub fn reset_events(&self) {
|
||||||
|
self.inner
|
||||||
|
.events
|
||||||
|
.fetch_and(!IoEvents::all().bits(), Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register an event observer.
|
||||||
|
///
|
||||||
|
/// A registered observer will get notified (through its `on_events` method)
|
||||||
|
/// every time new events specified by the `masks` argument happen on the
|
||||||
|
/// pollee (through the `add_events` method).
|
||||||
|
///
|
||||||
|
/// If the given observer has already been registered, then its registered
|
||||||
|
/// event mask will be updated.
|
||||||
|
///
|
||||||
|
/// Note that the observer will always get notified of the events in
|
||||||
|
/// `Events::ALWAYS_POLL` regardless of the value of `masks`.
|
||||||
|
///
|
||||||
|
/// # Memory leakage
|
||||||
|
///
|
||||||
|
/// Since an `Arc` for each observer is kept internally by a pollee,
|
||||||
|
/// it is important for the user to call the `unregister_observer` method
|
||||||
|
/// when the observer is no longer interested in the pollee. Otherwise,
|
||||||
|
/// the observer will not be dropped.
|
||||||
|
pub fn register_observer(&self, observer: Weak<dyn Observer<IoEvents>>, mask: IoEvents) {
|
||||||
|
let mask = mask | IoEvents::ALWAYS_POLL;
|
||||||
|
self.inner.pollers.register(observer, Some(mask), None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unregister an event observer.
|
||||||
|
///
|
||||||
|
/// If such an observer is found, then the registered observer will be
|
||||||
|
/// removed from the pollee and returned as the return value. Otherwise,
|
||||||
|
/// a `None` will be returned.
|
||||||
|
pub fn unregister_observer(&self, observer: &Weak<dyn Observer<IoEvents>>) {
|
||||||
|
self.inner.pollers.unregister(observer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn events(&self) -> IoEvents {
|
||||||
|
let event_bits = self.inner.events.load(Ordering::Relaxed);
|
||||||
|
unsafe { IoEvents::from_bits_unchecked(event_bits) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Pollee {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Pollee")
|
||||||
|
.field("events", &self.events())
|
||||||
|
.field("pollers", &"..")
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A poller gets notified when its associated pollees have interesting events.
|
||||||
|
pub struct Poller {
|
||||||
|
inner: Arc<PollerInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PollerInner {
|
||||||
|
// Use event counter to wait or wake up a poller
|
||||||
|
waiter: Waiter<EdgeSync>,
|
||||||
|
// All pollees that are interesting to this poller
|
||||||
|
pollees: Mutex<Vec<Weak<PolleeInner>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for PollerInner {}
|
||||||
|
unsafe impl Sync for PollerInner {}
|
||||||
|
|
||||||
|
impl Poller {
|
||||||
|
/// Constructs a new `Poller`.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let inner = PollerInner {
|
||||||
|
waiter: Waiter::<EdgeSync>::new(),
|
||||||
|
pollees: Mutex::new(Vec::new()),
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(inner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait until there are any interesting events happen since last `wait`.
|
||||||
|
pub fn wait(&self) -> Result<()> {
|
||||||
|
self.inner.waiter.wait(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait until there are any interesting events happen since last `wait`, or reach timeout.
|
||||||
|
pub fn wait_timeout(&self, timeout: Option<&mut Duration>) -> Result<()> {
|
||||||
|
self.inner.waiter.wait_mut(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn observer(&self) -> Weak<dyn Observer<IoEvents>> {
|
||||||
|
Arc::downgrade(&self.inner) as _
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Observer<IoEvents> for PollerInner {
|
||||||
|
fn on_event(
|
||||||
|
&self,
|
||||||
|
_event: &IoEvents,
|
||||||
|
_metadata: &Option<Weak<dyn core::any::Any + Send + Sync>>,
|
||||||
|
) -> () {
|
||||||
|
self.waiter.waker().wake();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Poller {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let mut pollees = self.inner.pollees.lock();
|
||||||
|
if pollees.len() == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let self_observer = self.observer();
|
||||||
|
for weak_pollee in pollees.drain(..) {
|
||||||
|
if let Some(pollee) = weak_pollee.upgrade() {
|
||||||
|
pollee.pollers.unregister(&self_observer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,212 +0,0 @@
|
|||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::sync::Weak;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use super::host_event_fd::HostEventFd;
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
/// A waiter enables a thread to sleep.
|
|
||||||
pub struct Waiter {
|
|
||||||
inner: Arc<Inner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Waiter {
|
|
||||||
/// Create a waiter for the current thread.
|
|
||||||
///
|
|
||||||
/// A `Waiter` is bound to the curent thread that creates it: it cannot be
|
|
||||||
/// sent to or used by any other threads as the type implements `!Send` and
|
|
||||||
/// `!Sync` traits. Thus, a `Waiter` can only put the current thread to sleep.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
inner: Arc::new(Inner::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return whether a waiter has been waken up.
|
|
||||||
///
|
|
||||||
/// Once a waiter is waken up, the `wait` or `wait_mut` method becomes
|
|
||||||
/// non-blocking.
|
|
||||||
pub fn is_woken(&self) -> bool {
|
|
||||||
self.inner.is_woken()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset a waiter.
|
|
||||||
///
|
|
||||||
/// After a `Waiter` being waken up, the `reset` method must be called so
|
|
||||||
/// that the `Waiter` can use the `wait` or `wait_mut` methods to sleep the
|
|
||||||
/// current thread again.
|
|
||||||
pub fn reset(&self) {
|
|
||||||
self.inner.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Put the current thread to sleep until being waken up by a waker.
|
|
||||||
///
|
|
||||||
/// The method has three possible return values:
|
|
||||||
/// 1. `Ok(())`: The `Waiter` has been waken up by one of its `Waker`;
|
|
||||||
/// 2. `Err(e) if e.errno() == Errno::ETIMEDOUT`: Timeout.
|
|
||||||
/// 3. `Err(e) if e.errno() == Errno::EINTR`: Interrupted by a signal.
|
|
||||||
///
|
|
||||||
/// If the `timeout` argument is `None`, then the second case won't happen,
|
|
||||||
/// i.e., the method will block indefinitely.
|
|
||||||
pub fn wait(&self, timeout: Option<&Duration>) -> Result<()> {
|
|
||||||
self.inner.wait(timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Put the current thread to sleep until being waken up by a waker.
|
|
||||||
///
|
|
||||||
/// This method is similar to the `wait` method except that the `timeout`
|
|
||||||
/// argument will be updated to reflect the remaining timeout.
|
|
||||||
pub fn wait_mut(&self, timeout: Option<&mut Duration>) -> Result<()> {
|
|
||||||
self.inner.wait_mut(timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a waker that can wake up this waiter.
|
|
||||||
///
|
|
||||||
/// `WaiterQueue` maintains a list of `Waker` internally to wake up the
|
|
||||||
/// enqueued `Waiter`s. So, for users that uses `WaiterQueue`, this method
|
|
||||||
/// does not need to be called manually.
|
|
||||||
pub fn waker(&self) -> Waker {
|
|
||||||
Waker {
|
|
||||||
inner: Arc::downgrade(&self.inner),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expose the internal host eventfd.
|
|
||||||
///
|
|
||||||
/// This host eventfd should be used by an external user carefully.
|
|
||||||
pub fn host_eventfd(&self) -> &HostEventFd {
|
|
||||||
self.inner.host_eventfd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl !Send for Waiter {}
|
|
||||||
impl !Sync for Waiter {}
|
|
||||||
|
|
||||||
/// A waker can wake up the thread that its waiter has put to sleep.
|
|
||||||
pub struct Waker {
|
|
||||||
inner: Weak<Inner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Waker {
|
|
||||||
/// Wake up the waiter that creates this waker.
|
|
||||||
pub fn wake(&self) {
|
|
||||||
if let Some(inner) = self.inner.upgrade() {
|
|
||||||
inner.wake()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wake up waiters in batch, more efficient than waking up one-by-one.
|
|
||||||
pub fn batch_wake<'a, I: Iterator<Item = &'a Waker>>(iter: I) {
|
|
||||||
Inner::batch_wake(iter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Instruction rearrangement about control dependency
|
|
||||||
///
|
|
||||||
/// Such as the following code:
|
|
||||||
/// fn function(flag: bool, a: i32, b: i32) {
|
|
||||||
/// if flag { // 1
|
|
||||||
/// let i = a * b; // 2
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// Guidelines for compilation optimization:without changing the single-threaded semantics
|
|
||||||
/// of the program, the execution order of statements can be rearranged. There is a control
|
|
||||||
/// dependency between flag and i. When the instruction is reordered, step 2 will write the
|
|
||||||
/// result value to the hardware cache, and when judged to be true, the result value will be
|
|
||||||
/// written to the variable i. Therefore, controlling dependency does not prevent compiler
|
|
||||||
/// optimizations
|
|
||||||
///
|
|
||||||
/// Note about memory ordering:
|
|
||||||
/// Here is_woken needs to be synchronized with host_eventfd. The read operation of
|
|
||||||
/// is_woken needs to see the change of the host_eventfd field. Just `Acquire` or
|
|
||||||
/// `Release` needs to be used to make all the change of the host_eventfd visible to us.
|
|
||||||
///
|
|
||||||
/// The ordering in CAS operations can be `Relaxed`, `Acquire`, `AcqRel` or `SeqCst`,
|
|
||||||
/// The key is to consider the specific usage scenario. Here fail does not synchronize other
|
|
||||||
/// variables in the CAS operation, which can use `Relaxed`, and the host_enent needs
|
|
||||||
/// to be synchronized in success, so `Acquire` needs to be used so that we can see all the
|
|
||||||
/// changes in the host_eventfd after that.
|
|
||||||
///
|
|
||||||
/// Although it is correct to use AcqRel, here I think it is okay to use Acquire, because
|
|
||||||
/// you don't need to synchronize host_event before is_woken, only later.
|
|
||||||
struct Inner {
|
|
||||||
is_woken: AtomicBool,
|
|
||||||
host_eventfd: Arc<HostEventFd>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Inner {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let is_woken = AtomicBool::new(false);
|
|
||||||
let host_eventfd = current!().host_eventfd().clone();
|
|
||||||
Self {
|
|
||||||
is_woken,
|
|
||||||
host_eventfd,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_woken(&self) -> bool {
|
|
||||||
self.is_woken.load(Ordering::Acquire)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(&self) {
|
|
||||||
self.is_woken.store(false, Ordering::Release);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wait(&self, timeout: Option<&Duration>) -> Result<()> {
|
|
||||||
while !self.is_woken() {
|
|
||||||
self.host_eventfd.poll(timeout)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wait_mut(&self, timeout: Option<&mut Duration>) -> Result<()> {
|
|
||||||
let mut remain = timeout.as_ref().map(|d| **d);
|
|
||||||
|
|
||||||
// Need to change timeout from `Option<&mut Duration>` to `&mut Option<Duration>`
|
|
||||||
// so that the Rust compiler is happy about using the variable in a loop.
|
|
||||||
let ret = self.do_wait_mut(&mut remain);
|
|
||||||
|
|
||||||
if let Some(timeout) = timeout {
|
|
||||||
*timeout = remain.unwrap();
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
fn do_wait_mut(&self, remain: &mut Option<Duration>) -> Result<()> {
|
|
||||||
while !self.is_woken() {
|
|
||||||
self.host_eventfd.poll_mut(remain.as_mut())?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wake(&self) {
|
|
||||||
if self
|
|
||||||
.is_woken
|
|
||||||
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
self.host_eventfd.write_u64(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn batch_wake<'a, I: Iterator<Item = &'a Waker>>(iter: I) {
|
|
||||||
let host_eventfds = iter
|
|
||||||
.filter_map(|waker| waker.inner.upgrade())
|
|
||||||
.filter(|inner| {
|
|
||||||
inner
|
|
||||||
.is_woken
|
|
||||||
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
|
|
||||||
.is_ok()
|
|
||||||
})
|
|
||||||
.map(|inner| inner.host_eventfd.host_fd())
|
|
||||||
.collect::<Vec<FileDesc>>();
|
|
||||||
unsafe {
|
|
||||||
HostEventFd::write_u64_raw_and_batch(&host_eventfds, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn host_eventfd(&self) -> &HostEventFd {
|
|
||||||
&self.host_eventfd
|
|
||||||
}
|
|
||||||
}
|
|
85
src/libos/src/events/waiter/edge.rs
Normal file
85
src/libos/src/events/waiter/edge.rs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
use atomic::Ordering;
|
||||||
|
use std::{sync::atomic::AtomicU32, time::Duration};
|
||||||
|
|
||||||
|
use super::{HostEventFd, Synchronizer};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
const WAIT: u32 = u32::MAX;
|
||||||
|
const INIT: u32 = 0;
|
||||||
|
const NOTIFIED: u32 = 1;
|
||||||
|
|
||||||
|
pub struct EdgeSync {
|
||||||
|
state: AtomicU32,
|
||||||
|
host_eventfd: Arc<HostEventFd>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Synchronizer for EdgeSync {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
state: AtomicU32::new(INIT),
|
||||||
|
host_eventfd: current!().host_eventfd().clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait(&self, timeout: Option<&Duration>) -> Result<()> {
|
||||||
|
if self.state.fetch_sub(1, Ordering::Acquire) == NOTIFIED {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
self.host_eventfd.poll(timeout)?;
|
||||||
|
if self
|
||||||
|
.state
|
||||||
|
.compare_exchange(NOTIFIED, INIT, Ordering::Acquire, Ordering::Acquire)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
// Spurious wake up. We loop to try again.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_mut(&self, timeout: Option<&mut Duration>) -> Result<()> {
|
||||||
|
if self.state.fetch_sub(1, Ordering::Acquire) == NOTIFIED {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let mut remain = timeout.as_ref().map(|d| **d);
|
||||||
|
// Need to change timeout from `Option<&mut Duration>` to `&mut Option<Duration>`
|
||||||
|
// so that the Rust compiler is happy about using the variable in a loop.
|
||||||
|
let ret = self.host_eventfd.poll_mut(remain.as_mut());
|
||||||
|
// Wait for something to happen, assuming it's still set to PARKED.
|
||||||
|
// futex_wait(&self.state, PARKED, Some(timeout));
|
||||||
|
// This is not just a store, because we need to establish a
|
||||||
|
// release-acquire ordering with unpark().
|
||||||
|
if self.state.swap(INIT, Ordering::Acquire) == NOTIFIED {
|
||||||
|
// Woke up because of unpark().
|
||||||
|
} else {
|
||||||
|
// Timeout or spurious wake up.
|
||||||
|
// We return either way, because we can't easily tell if it was the
|
||||||
|
// timeout or not.
|
||||||
|
}
|
||||||
|
if let Some(timeout) = timeout {
|
||||||
|
*timeout = remain.unwrap();
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&self) {
|
||||||
|
// do nothing for edge trigger
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake(&self) {
|
||||||
|
if self.wake_cond() {
|
||||||
|
self.host_eventfd.write_u64(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn host_eventfd(&self) -> &HostEventFd {
|
||||||
|
&self.host_eventfd
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_cond(&self) -> bool {
|
||||||
|
self.state.swap(NOTIFIED, Ordering::Release) == WAIT
|
||||||
|
}
|
||||||
|
}
|
68
src/libos/src/events/waiter/level.rs
Normal file
68
src/libos/src/events/waiter/level.rs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
use atomic::Ordering;
|
||||||
|
use std::{sync::atomic::AtomicBool, time::Duration};
|
||||||
|
|
||||||
|
use super::{HostEventFd, Synchronizer};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
pub struct LevelSync {
|
||||||
|
is_woken: AtomicBool,
|
||||||
|
host_eventfd: Arc<HostEventFd>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Synchronizer for LevelSync {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
is_woken: AtomicBool::new(false),
|
||||||
|
host_eventfd: current!().host_eventfd().clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&self) {
|
||||||
|
self.is_woken.store(false, Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait(&self, timeout: Option<&Duration>) -> Result<()> {
|
||||||
|
while !self.is_woken() {
|
||||||
|
self.host_eventfd.poll(timeout)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_mut(&self, timeout: Option<&mut Duration>) -> Result<()> {
|
||||||
|
let mut remain = timeout.as_ref().map(|d| **d);
|
||||||
|
// Need to change timeout from `Option<&mut Duration>` to `&mut Option<Duration>`
|
||||||
|
// so that the Rust compiler is happy about using the variable in a loop.
|
||||||
|
|
||||||
|
while !self.is_woken() {
|
||||||
|
self.host_eventfd.poll_mut(remain.as_mut())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(timeout) = timeout {
|
||||||
|
*timeout = remain.unwrap();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake(&self) {
|
||||||
|
if self.wake_cond() {
|
||||||
|
self.host_eventfd.write_u64(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_cond(&self) -> bool {
|
||||||
|
self.is_woken
|
||||||
|
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn host_eventfd(&self) -> &HostEventFd {
|
||||||
|
&self.host_eventfd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LevelSync {
|
||||||
|
#[inline(always)]
|
||||||
|
fn is_woken(&self) -> bool {
|
||||||
|
self.is_woken.load(Ordering::Acquire)
|
||||||
|
}
|
||||||
|
}
|
113
src/libos/src/events/waiter/mod.rs
Normal file
113
src/libos/src/events/waiter/mod.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
mod edge;
|
||||||
|
mod level;
|
||||||
|
mod synchronizer;
|
||||||
|
|
||||||
|
pub use self::edge::EdgeSync;
|
||||||
|
pub use self::level::LevelSync;
|
||||||
|
pub use self::synchronizer::Synchronizer;
|
||||||
|
|
||||||
|
use super::HostEventFd;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use std::{sync::Weak, time::Duration};
|
||||||
|
|
||||||
|
/// A waiter enables a thread to sleep.
|
||||||
|
pub struct Waiter<Sync = LevelSync>
|
||||||
|
where
|
||||||
|
Sync: Synchronizer,
|
||||||
|
{
|
||||||
|
inner: Arc<Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Sync: Synchronizer> Waiter<Sync> {
|
||||||
|
/// Create a waiter for the current thread.
|
||||||
|
///
|
||||||
|
/// A `Waiter` is bound to the curent thread that creates it: it cannot be
|
||||||
|
/// sent to or used by any other threads as the type implements `!Send` and
|
||||||
|
/// `!Sync` traits. Thus, a `Waiter` can only put the current thread to sleep.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(Sync::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset a waiter.
|
||||||
|
///
|
||||||
|
/// After a `Waiter` being waken up, the `reset` method must be called so
|
||||||
|
/// that the `Waiter` can use the `wait` or `wait_mut` methods to sleep the
|
||||||
|
/// current thread again.
|
||||||
|
pub fn reset(&self) {
|
||||||
|
self.inner.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Put the current thread to sleep until being waken up by a waker.
|
||||||
|
///
|
||||||
|
/// The method has three possible return values:
|
||||||
|
/// 1. `Ok(())`: The `Waiter` has been waken up by one of its `Waker`;
|
||||||
|
/// 2. `Err(e) if e.errno() == Errno::ETIMEDOUT`: Timeout.
|
||||||
|
/// 3. `Err(e) if e.errno() == Errno::EINTR`: Interrupted by a signal.
|
||||||
|
///
|
||||||
|
/// If the `timeout` argument is `None`, then the second case won't happen,
|
||||||
|
/// i.e., the method will block indefinitely.
|
||||||
|
pub fn wait(&self, timeout: Option<&Duration>) -> Result<()> {
|
||||||
|
self.inner.wait(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Put the current thread to sleep until being waken up by a waker.
|
||||||
|
///
|
||||||
|
/// This method is similar to the `wait` method except that the `timeout`
|
||||||
|
/// argument will be updated to reflect the remaining timeout.
|
||||||
|
pub fn wait_mut(&self, timeout: Option<&mut Duration>) -> Result<()> {
|
||||||
|
self.inner.wait_mut(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a waker that can wake up this waiter.
|
||||||
|
///
|
||||||
|
/// `WaiterQueue` maintains a list of `Waker` internally to wake up the
|
||||||
|
/// enqueued `Waiter`s. So, for users that uses `WaiterQueue`, this method
|
||||||
|
/// does not need to be called manually.
|
||||||
|
pub fn waker(&self) -> Waker<Sync> {
|
||||||
|
Waker {
|
||||||
|
inner: Arc::downgrade(&self.inner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expose the internal host eventfd.
|
||||||
|
///
|
||||||
|
/// This host eventfd should be used by an external user carefully.
|
||||||
|
pub fn host_eventfd(&self) -> &HostEventFd {
|
||||||
|
self.inner.host_eventfd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Synchronizer> !Send for Waiter<S> {}
|
||||||
|
impl<S: Synchronizer> !Sync for Waiter<S> {}
|
||||||
|
|
||||||
|
/// A waker can wake up the thread that its waiter has put to sleep.
|
||||||
|
pub struct Waker<S = LevelSync>
|
||||||
|
where
|
||||||
|
S: Synchronizer,
|
||||||
|
{
|
||||||
|
inner: Weak<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Synchronizer> Waker<S> {
|
||||||
|
/// Wake up the waiter that creates this waker.
|
||||||
|
pub fn wake(&self) {
|
||||||
|
if let Some(inner) = self.inner.upgrade() {
|
||||||
|
inner.wake()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wake up waiters in batch, more efficient than waking up one-by-one.
|
||||||
|
pub fn batch_wake<'a, W: 'a + Synchronizer, I: Iterator<Item = &'a Waker<W>>>(iter: I) {
|
||||||
|
let host_eventfds = iter
|
||||||
|
.filter_map(|waker| waker.inner.upgrade())
|
||||||
|
.filter(|inner| inner.wake_cond())
|
||||||
|
.map(|inner| inner.host_eventfd().host_fd())
|
||||||
|
.collect::<Vec<FileDesc>>();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
HostEventFd::write_u64_raw_and_batch(&host_eventfds, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
src/libos/src/events/waiter/synchronizer.rs
Normal file
31
src/libos/src/events/waiter/synchronizer.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use super::HostEventFd;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
/// This trait abstracts over the synchronization mechanism to allow for different implementations that can
|
||||||
|
/// interact with the host's file descriptor based event notification mechanisms or other kinds of notification facilities.
|
||||||
|
|
||||||
|
pub trait Synchronizer {
|
||||||
|
/// Creates and returns a new instance of a synchronization primitive.
|
||||||
|
fn new() -> Self;
|
||||||
|
|
||||||
|
/// Resets the synchronization primitive state.
|
||||||
|
fn reset(&self);
|
||||||
|
|
||||||
|
/// Waits for the synchronization event to occur until an optional `timeout` duration has elapsed.
|
||||||
|
fn wait(&self, timeout: Option<&Duration>) -> Result<()>;
|
||||||
|
|
||||||
|
/// Similar to `wait` but allows a mutable `timeout` parameter that can be adjusted to reflect the remaining
|
||||||
|
/// time for the wait operation.
|
||||||
|
fn wait_mut(&self, timeout: Option<&mut Duration>) -> Result<()>;
|
||||||
|
|
||||||
|
/// Wakes one or more threads waiting on this synchronization primitive
|
||||||
|
fn wake(&self);
|
||||||
|
|
||||||
|
/// Returns a reference to the `host_eventfd`, an object tied to a file descriptor used for event notifications.
|
||||||
|
fn host_eventfd(&self) -> &HostEventFd;
|
||||||
|
|
||||||
|
/// Determines the condition under which a wake event should be triggered.
|
||||||
|
fn wake_cond(&self) -> bool;
|
||||||
|
}
|
@ -1,8 +1,7 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use super::{Waiter, Waker};
|
use super::{LevelSync, Synchronizer, Waiter, Waker};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// A queue for waiters.
|
/// A queue for waiters.
|
||||||
@ -26,12 +25,12 @@ use crate::prelude::*;
|
|||||||
/// In this code snippet, the count variable is synchronized with the wakers field.
|
/// In this code snippet, the count variable is synchronized with the wakers field.
|
||||||
/// In this case, we only need to ensure that waker.lock() occurs before count.
|
/// In this case, we only need to ensure that waker.lock() occurs before count.
|
||||||
/// Although it is safer to use AcqRel,here using `Release` would be enough.
|
/// Although it is safer to use AcqRel,here using `Release` would be enough.
|
||||||
pub struct WaiterQueue {
|
pub struct WaiterQueue<Sync: Synchronizer = LevelSync> {
|
||||||
count: AtomicUsize,
|
count: AtomicUsize,
|
||||||
wakers: SgxMutex<VecDeque<Waker>>,
|
wakers: SgxMutex<VecDeque<Waker<Sync>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WaiterQueue {
|
impl<Sync: Synchronizer> WaiterQueue<Sync> {
|
||||||
/// Creates an empty queue for `Waiter`s.
|
/// Creates an empty queue for `Waiter`s.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -52,7 +51,7 @@ impl WaiterQueue {
|
|||||||
/// It is allowed to enqueue a waiter more than once before it is dequeued.
|
/// It is allowed to enqueue a waiter more than once before it is dequeued.
|
||||||
/// But this is usually not a good idea. It is the callers' responsibility
|
/// But this is usually not a good idea. It is the callers' responsibility
|
||||||
/// to use the API properly.
|
/// to use the API properly.
|
||||||
pub fn reset_and_enqueue(&self, waiter: &Waiter) {
|
pub fn reset_and_enqueue(&self, waiter: &Waiter<Sync>) {
|
||||||
waiter.reset();
|
waiter.reset();
|
||||||
|
|
||||||
let mut wakers = self.wakers.lock().unwrap();
|
let mut wakers = self.wakers.lock().unwrap();
|
||||||
@ -81,13 +80,13 @@ impl WaiterQueue {
|
|||||||
let to_wake = {
|
let to_wake = {
|
||||||
let mut wakers = self.wakers.lock().unwrap();
|
let mut wakers = self.wakers.lock().unwrap();
|
||||||
let max_count = max_count.min(wakers.len());
|
let max_count = max_count.min(wakers.len());
|
||||||
let to_wake: Vec<Waker> = wakers.drain(..max_count).collect();
|
let to_wake: Vec<Waker<Sync>> = wakers.drain(..max_count).collect();
|
||||||
self.count.fetch_sub(to_wake.len(), Ordering::Release);
|
self.count.fetch_sub(to_wake.len(), Ordering::Release);
|
||||||
to_wake
|
to_wake
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wake in batch
|
// Wake in batch
|
||||||
Waker::batch_wake(to_wake.iter());
|
Waker::<Sync>::batch_wake(to_wake.iter());
|
||||||
to_wake.len()
|
to_wake.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user