chromium/mojo/public/rust/system/wait_set.rs

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::collections::{hash_map, HashMap};
use std::sync::{Arc, Condvar, Mutex};

use crate::handle::Handle;
use crate::mojo_types;
use crate::mojo_types::{MojoResult, SignalsState};
use crate::trap::{Trap, TrapEvent, TriggerCondition, TriggerId};

/// Identifies a handle added to `WaitSet`.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct WaitSetCookie(pub u64);

/// Represents a single ready handle.
#[derive(Clone, Copy, Debug)]
pub struct WaitSetResult {
    /// Cookie corresponding to a handle that is ready.
    pub cookie: WaitSetCookie,
    /// Wait result, like from `Trap`:
    /// * MojoResult::Okay: a signal was satisfied.
    /// * MojoResult::FailedPrecondition: a signal can never be satisfied again.
    /// * MojoResult::Cancelled: the handle was closed.
    pub wait_result: MojoResult,
    /// The latest known signals on the ready handle. if `wait_result` is
    /// `MojoResult::Okay` then it is guaranteed to contain at least one signal
    /// specified in `WaitSet::add`.
    pub signals_state: SignalsState,
}

/// Provides synchronous waiting on a dynamic set of handles.
///
/// Similar to `Trap`, a set of handles paired with signals to watch for can be
/// dynamically added and removed. Unlike `Trap`, which gives asynchronous
/// callbacks when an event happens, `WaitSet` provides a `wait_on_set` method
/// which synchronously returns events, waiting if necessary.
pub struct WaitSet {
    // The Mojo primitive that gives us notifications on handles. We register
    // handles paired with signals we're interested in and a context object.
    trap: Trap<Context, fn(event: &TrapEvent, context: &Context)>,
    // `trap` vends `TriggerId` to identify one (handle, trigger condition) pair
    // registered with it. We give a "cookie" to our client for each handle, so
    // we need to map this client-facing cookie to the trap's `TriggerId`.
    cookies: HashMap<WaitSetCookie, TriggerId>,
    // Contains state necessary to store received events and wait synchronously.
    // Ownership is shared with the `Context` objects we give to `trap`.
    state: Arc<State>,
}

struct State {
    // Maintain the most recent result for each signalled handle. Only contains
    // results of handles for which we've received a callback.
    results: Mutex<HashMap<WaitSetCookie, WaitSetResult>>,
    // Enables putting the thread to sleep while waiting for an event.
    cond: Condvar,
}

// Passed to the `Trap`. These exist 1:1 with handles added to the set.
struct Context {
    state: Arc<State>,
    cookie: WaitSetCookie,
}

impl WaitSet {
    /// Create a new WaitSet or returns an error code if the `Trap` could not be
    /// created.
    pub fn new() -> Result<WaitSet, MojoResult> {
        Ok(WaitSet {
            trap: Trap::new(Self::event_handler as _)?,
            cookies: HashMap::new(),
            state: Arc::new(State { results: Mutex::new(HashMap::new()), cond: Condvar::new() }),
        })
    }

    /// Add a handle to set, watching for `signals`.
    ///
    /// If the handle is closed it is implicitly removed from the set. A
    /// `WaitSetResult` of type `MojoResult::Cancelled` will be returned on the
    /// next call to `wait_on_set`.
    ///
    /// `cookie` must be a unique (but arbitrary) value to identify the handle.
    /// `WaitSetResult`s returned by `wait_on_set` contain the handle's cookie.
    pub fn add(
        &mut self,
        handle: &dyn Handle,
        signals: mojo_types::HandleSignals,
        cookie: WaitSetCookie,
    ) -> MojoResult {
        let cookie_entry = match self.cookies.entry(cookie) {
            hash_map::Entry::Occupied(_) => return MojoResult::AlreadyExists,
            hash_map::Entry::Vacant(e) => e,
        };

        let context = Context { state: self.state.clone(), cookie };

        match self.trap.add_trigger(
            handle.get_native_handle(),
            signals,
            TriggerCondition::SignalsSatisfied,
            context,
        ) {
            Ok(token) => {
                // Insert the cookie *after* adding the trigger. Only `remove`
                // and `wait_on_set` rely on the entry, not the handler function
                // which doesn't have access to `self` anyway. We take `&mut
                // self` so we are mutually exclusive with `wait_on_set` and
                // `remove`. Hence, it is OK to do this.
                cookie_entry.insert(token);
                MojoResult::Okay
            }
            Err(e) => e,
        }
    }

    /// Remove the handle identified by `cookie` from the set. No results for
    /// this handle will ever be returned by subsequent calls to `wait_on_set`.
    pub fn remove(&mut self, cookie: WaitSetCookie) -> MojoResult {
        let token = match self.cookies.remove(&cookie) {
            Some(token) => token,
            None => return MojoResult::NotFound,
        };

        match self.trap.remove_trigger(token) {
            // Gracefully handle if the trigger auto-deregistered (e.g. the
            // handle was closed).
            MojoResult::NotFound => MojoResult::Okay,
            // Removing a trigger will fire an event, even if the trap is not
            // armed. We filter in `wait_on_set` based on `self.cookies` to
            // avoid returning events for `cookie`.
            MojoResult::Okay => MojoResult::Okay,
            e => unreachable!("unexpected Mojo error {:?}", e),
        }
    }

    /// Wait on this wait set.
    ///
    /// The conditions for the wait to end include:
    /// * A handle has its requested signals satisfied.
    /// * A handle is determined to never be able to have its requested signals
    ///   satisfied.
    /// * A handle is closed.
    ///
    /// `output` is cleared, then populated with results for each ready handle
    /// on success. `MojoResult` is returned indicating success or failure
    /// (which can only happen if the `Trap` fails to arm for unknown reasons).
    pub fn wait_on_set(&self, output: &mut Vec<WaitSetResult>) -> MojoResult {
        output.clear();

        {
            // Take the lock for the results. Unwrap because we cannot recover
            // from a poisoned mutex.
            let mut results_guard = self.state.results.lock().unwrap();
            let results: &mut HashMap<WaitSetCookie, WaitSetResult> = &mut results_guard;

            // If there were already results (that weren't filtered out) we just
            // return them.
            if !results.is_empty() {
                self.process_results(results, output);
                if !output.is_empty() {
                    return MojoResult::Okay;
                }
            }
        }

        // Now we've unlocked the mutex and can arm the trap. We must loop: we
        // filter results for handles explicitly removed. If we are left with no
        // events, we still want to wait.
        //
        // NOTE: there's an edge case where we can receive a Cancelled event in
        // between our check above and arming. In this case we may leave the
        // trap armed after returning from this function. This is not ideal but
        // it is safe since our `event_handler` only pushes a new value to our
        // internal results list. The `Trap` type also safely handles if its
        // drop() is never called. There is no way to work around this edge case
        // with the Mojo trap API.

        assert!(output.is_empty());
        while output.is_empty() {
            match self.trap.arm() {
                MojoResult::Okay | MojoResult::FailedPrecondition => (),
                MojoResult::NotFound => return MojoResult::NotFound,
                e => unreachable!("unexpected Mojo error {:?}", e),
            };

            let mut results_guard = self
                .state
                .cond
                .wait_while(self.state.results.lock().unwrap(), |r| r.is_empty())
                .unwrap();
            let results: &mut HashMap<WaitSetCookie, WaitSetResult> = &mut results_guard;
            self.process_results(results, output);
        }

        MojoResult::Okay
    }

    // Helper function to report the latest `WaitSetResult` received by the trap
    // handler for each handle entry. Filters out events corresponding to
    // `remove` calls, which `Trap` sends for each `remove_trigger` call.
    fn process_results(
        &self,
        results: &mut HashMap<WaitSetCookie, WaitSetResult>,
        output: &mut Vec<WaitSetResult>,
    ) {
        // Report results, filtering those with no `self.cookies` entry which
        // means the handle was removed.
        output.extend(results.values().filter(|result| self.cookies.contains_key(&result.cookie)));
        results.clear();
    }

    fn event_handler(event: &TrapEvent, context: &Context) {
        let mut results_guard = context.state.results.lock().unwrap();
        let results: &mut HashMap<WaitSetCookie, WaitSetResult> = &mut results_guard;
        // Simply transcribe and insert the event. Removal events are processed
        // on the waiting task in a `wait_on_set` call. Note that the owning
        // `WaitSet` may not even contain the cookie: a handle may be closed
        // between `Trap::add_trigger` and inserting into `WaitSet::cookies`.
        // That is OK since only `wait_on_set` relies on the entry. `add` and
        // `wait_on_set` are mutually exclusive because `add` takes `&mut self`
        results.insert(
            context.cookie,
            WaitSetResult {
                cookie: context.cookie,
                wait_result: event.result(),
                signals_state: event.signals_state(),
            },
        );
        context.state.cond.notify_all();
    }
}