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

// Copyright 2022 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::sync::{Arc, Condvar, Mutex};

use crate::mojo_types::{HandleSignals, MojoHandle, MojoResult, SignalsState};
use crate::trap::{Trap, TrapEvent, TriggerCondition};

/// The result of `wait`ing on a handle. There are three possible outcomes:
///     * A requested signal was satisfied
///     * A requested signal became unsatisfiable due to a change on the handle
///     * The handle was closed
#[derive(Clone, Copy, Debug)]
pub enum WaitResult {
    /// The handle had a signal satisfied.
    Satisfied(SignalsState),
    /// A requested signal became unsatisfiable for the handle.
    Unsatisfiable(SignalsState),
    /// The handle was closed.
    Closed,
}

impl WaitResult {
    /// Returns the signals state if satisfied, and an Err with the original
    /// result otherwise.
    pub fn satisfied(self) -> Result<SignalsState, WaitResult> {
        match self {
            Satisfied(s) => Ok(s),
            _ => Err(self),
        }
    }

    /// Returns the signals state if unsatisfiable, and an Err with the original
    /// result otherwise.
    pub fn unsatisfiable(self) -> Result<SignalsState, WaitResult> {
        match self {
            Unsatisfiable(s) => Ok(s),
            _ => Err(self),
        }
    }

    /// Returns the signals state if the handle wasn't closed, and an Err with
    /// the original result otherwise.
    pub fn signals_state(self) -> Result<SignalsState, WaitResult> {
        match self {
            Satisfied(s) => Ok(s),
            Unsatisfiable(s) => Ok(s),
            Closed => Err(self),
        }
    }
}

use WaitResult::*;

/// Wait on `handle` until a signal in `signals` is satisfied or becomes
/// unsatisfiable.
#[must_use]
pub fn wait(handle: MojoHandle, signals: HandleSignals) -> WaitResult {
    // Mojo's mechanism for asynchronous event notification is traps: a trap
    // object contains a handler function and a mapping of handles to
    // interesting signals. Traps provide asynchronous notification.
    //
    // To implement synchronous waiting on a single handle, we use a trap along
    // with a mutex/condvar. Our handler function simply stores the event and
    // signals the condvar.
    //
    // In `wait`'s stack frame, we arm the trap then if successful wait on the
    // mutex/condvar pair. We then process the received event.

    let ctx = Arc::new(Context { signaled_event: Mutex::new(None), cond: Condvar::new() });

    let handler = |event: &TrapEvent, context: &Arc<Context>| {
        // We have no way to recover from a poisoned mutex so just unwrap.
        let mut e = context.signaled_event.lock().unwrap();
        *e = Some(HandleEventInfo { result: event.result(), signals_state: event.signals_state() });
        context.cond.notify_all();
    };

    let trap = Trap::new(handler).unwrap();
    trap.add_trigger(handle, signals, TriggerCondition::SignalsSatisfied, ctx.clone()).unwrap();

    let event: HandleEventInfo = match trap.arm() {
        MojoResult::Okay | MojoResult::FailedPrecondition => {
            // Wait until we get a trap event callback (this should return
            // immediately if there was a blocking event). Unwrap because we
            // cannot recover from a poisoned mutex.
            let event_slot: Option<HandleEventInfo> =
                *ctx.cond.wait_while(ctx.signaled_event.lock().unwrap(), |e| e.is_none()).unwrap();

            // We can unwrap because `Cond::wait_while` will only return when
            // the condition is true (or the mutex was poisoned, but the first
            // unwrap covers that).
            event_slot.unwrap()
        }
        e => {
            panic!("unexpected mojo error {:?}", e);
        }
    };

    match event.result {
        MojoResult::Okay => Satisfied(event.signals_state),
        MojoResult::FailedPrecondition => Unsatisfiable(event.signals_state),
        MojoResult::Cancelled => Closed,
        e => panic!("unexpected mojo error {:?}", e),
    }
}

#[derive(Clone, Copy, Debug)]
struct HandleEventInfo {
    result: MojoResult,
    signals_state: SignalsState,
}

struct Context {
    signaled_event: Mutex<Option<HandleEventInfo>>,
    cond: Condvar,
}