// 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 crate::ffi::{self, raw_ffi, types::MojoTriggerCondition};
use crate::handle::*;
use crate::mojo_types::*;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::mem;
use std::ptr;
use std::sync::{Arc, Mutex, Weak};
#[derive(Clone, Copy, Debug)]
pub enum TriggerCondition {
/// Trigger on a signal becoming unsatisfied (i.e. going low).
SignalsUnsatisfied = 0,
/// Trigger on a signal becoming satisfied (i.e. going high).
SignalsSatisfied = 1,
}
impl TriggerCondition {
fn to_raw(self) -> MojoTriggerCondition {
self as _
}
}
/// An event reported by `Trap`.
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct UnsafeTrapEvent(raw_ffi::MojoTrapEvent);
impl UnsafeTrapEvent {
/// The context provided in `Trap::add_trigger`.
pub fn trigger_context(&self) -> usize {
self.0.trigger_context
}
/// Why the trigger fired:
/// * Okay: a specified signal occurred.
/// * FailedPrecondition: a signal can no longer happen on the handle.
/// * Cancelled: the trigger was removed (explicitly or by closure).
pub fn result(&self) -> MojoResult {
MojoResult::from_code(self.0.result)
}
/// The handle's current and possible signals as of triggering.
pub fn signals_state(&self) -> SignalsState {
SignalsState(self.0.signals_state)
}
}
pub type EventHandler = extern "C" fn(&UnsafeTrapEvent);
/// The result of arming an `UnsafeTrap`.
pub enum ArmResult<'a> {
/// The trap was successfully armed with no blocking events.
Armed,
/// An event would have triggered immediately, blocking the arm. Contains
/// the event(s). The returned slice is a reborrow of the buffer passed to
/// `UnsafeTrap::arm`.
Blocked(&'a [UnsafeTrapEvent]),
/// Arming failed due to a different Mojo error. If no buffer was passed in
/// to `arm` but there were blocking events Failed(FailedPrecondition) will
/// be returned.
Failed(MojoResult),
}
/// A Mojo trap object provides notifications for specified changes on Mojo
/// handles. `UnsafeTrap` is a thin wrapper for Mojo traps. Each instance has an
/// associated `EventHandler` function which is called for each notification.
///
/// This is called "unsafe" because clients generally must use unsafe code.
/// There isn't inherent unsafety; each Mojo handle registered has a context
/// `usize` associated with it that is passed to the event handle. But in most
/// cases this context integer will be interpreted as a raw pointer, which is
/// dangerous.
pub struct UnsafeTrap {
handle: UntypedHandle,
}
impl UnsafeTrap {
/// Create a `Trap` that calls `handler` for each event.
///
/// Generally, `handler` will be called while the trap is armed. However,
/// it will be called while disarmed upon a removing a trigger which happens
/// in two cases:
/// * The trigger is explicitly removed with `remove_trigger`
/// * The trigger's handle is closed
pub fn new(handler: EventHandler) -> Result<UnsafeTrap, MojoResult> {
let handler_ptr = unsafe {
// SAFETY: *const T and &T are ABI compatible and we are assured
// that `handler` is passed a pointer that lives as long as the
// function call. The lifetime of `handler`'s argument precludes it
// from retaining the reference.
mem::transmute::<
extern "C" fn(&UnsafeTrapEvent),
extern "C" fn(*const raw_ffi::MojoTrapEvent),
>(handler)
};
let mut handle = UntypedHandle::invalid();
let result = unsafe {
// SAFETY:
// * MojoCreateTrap is given a valid function pointer (type checked thanks to
// bindgen)
// * `handle`'s pointer cast is OK since `UntypedHandle` is repr(transparent)
// for MojoHandle
MojoResult::from_code(ffi::MojoCreateTrap(
Some(handler_ptr),
ffi::MojoCreateTrapOptions::new(0).inner_ptr(),
handle.as_mut_ptr(),
))
};
match result {
MojoResult::Okay => Ok(UnsafeTrap { handle }),
e => Err(e),
}
}
/// Listen for `signals` on `handle` becoming satisfied or unsatisfied
/// based on `condition`. Once armed, the event handler may be called for
/// events on this handle.
///
/// The handler will be passed `context` when triggered for `handle`. This
/// is a pointer-size integer that can be interpreted in any way. However,
/// in almost all cases this will be used as an actual pointer.
///
/// The caller should take care that:
/// * the event handler safely uses this pointer.
/// * the pointer remains valid until `remove_trigger`, or until `self` is
/// dropped.
pub fn add_trigger(
&self,
handle: MojoHandle,
signals: HandleSignals,
condition: TriggerCondition,
context: usize,
) -> MojoResult {
unsafe {
MojoResult::from_code(ffi::MojoAddTrigger(
self.handle.get_native_handle(),
handle,
signals.bits(),
condition.to_raw(),
context,
ffi::MojoAddTriggerOptions::new(0).inner_ptr(),
))
}
}
/// Remove the handle associated with `context`. Note that, if successful,
/// this immediately results in a callback to the user handler with
/// `MojoResult::Cancelled`. No more callbacks will be issued for
/// `context`'s handle.
pub fn remove_trigger(&self, context: usize) -> MojoResult {
unsafe {
MojoResult::from_code(ffi::MojoRemoveTrigger(
self.handle.get_native_handle(),
context,
ffi::MojoRemoveTriggerOptions::new(0).inner_ptr(),
))
}
}
/// Arm the trap to invoke event handler on any trigger condition.
///
/// `blocking_events` is an optional buffer to hold events that would block
/// arming the trap, if any exist. If supplied and there were events
/// blocking the arm, a subslice with the actual events is returned. Its
/// length must be > 0 and < `u32::MAX`, otherwise this function will panic.
///
/// If arming was successful, the trap remains armed until an event is
/// received. At this point it is immediately disarmed.
pub fn arm<'a>(
&self,
blocking_events: Option<&'a mut [mem::MaybeUninit<UnsafeTrapEvent>]>,
) -> ArmResult<'a> {
// Initialized to the available space in `blocking_events` (or 0), then
// updated in-place by the Mojo FFI call.
let mut num_events = blocking_events
.as_ref()
.map_or(0, |b| u32::try_from(b.len()).expect("`blocking_events` too large"));
// Initialize `blocking_events` and set `struct_size` fields which are
// used by Mojo for struct versioning.
let mut blocking_events: Option<&'a mut [UnsafeTrapEvent]> =
blocking_events.map(|blocking_events| {
for uninit_event in blocking_events.iter_mut() {
// `UnsafeTrapEvent` wraps a C FFI struct that is POD and
// valid when zero-initialized.
let mut event: UnsafeTrapEvent = unsafe { mem::zeroed() };
event.0.struct_size = mem::size_of::<UnsafeTrapEvent>() as u32;
uninit_event.write(event);
}
// Now that all elements are initialized it is sound to
// assume_init.
unsafe { mem::MaybeUninit::slice_assume_init_mut(blocking_events) }
});
let (blocking_events_ptr, num_events_ptr) = match blocking_events.as_mut() {
// Casting `*mut TrapEvent` to `*mut raw_ffi::MojoTrapEvent` is
// sound because the former is a repr(transparent) wrapper for the
// latter.
Some(events) => {
(events.as_mut_ptr() as *mut raw_ffi::MojoTrapEvent, &mut num_events as *mut u32)
}
None => (ptr::null_mut(), ptr::null_mut()),
};
let result = unsafe {
MojoResult::from_code(ffi::MojoArmTrap(
self.handle.get_native_handle(),
ffi::MojoArmTrapOptions::new(0).inner_ptr(),
num_events_ptr,
blocking_events_ptr,
))
};
match (result, blocking_events) {
(MojoResult::Okay, _) => ArmResult::Armed,
(MojoResult::FailedPrecondition, None) => ArmResult::Failed(result),
(MojoResult::FailedPrecondition, Some(blocking_events)) => {
assert!(num_events > 0);
let result = blocking_events.split_at_mut(num_events as usize).0;
ArmResult::Blocked(result)
}
(e, _) => ArmResult::Failed(e),
}
}
}
/// Identifies a trigger added to a `Trap`.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct TriggerId(usize);
#[derive(Clone, Copy, Debug)]
pub struct TrapEvent {
signals_state: SignalsState,
handle: MojoHandle,
result: MojoResult,
}
impl TrapEvent {
/// The handle whose state changed.
pub fn handle(&self) -> MojoHandle {
self.handle
}
/// Why the trigger fired:
/// * Okay: a specified signal occurred.
/// * FailedPrecondition: a signal can no longer happen on the handle.
/// * Cancelled: the trigger was removed (explicitly or by closure).
pub fn result(&self) -> MojoResult {
self.result
}
/// The handle's current and possible signals as of triggering.
pub fn signals_state(&self) -> SignalsState {
self.signals_state
}
}
/// A wrapper around `UnsafeTrap` that provides safety for context objects in
/// exchange for some heap memory usage and indirection.'
///
/// `Context` is the client's data associated with each handle. `EventHandler`
/// is the `Fn` type that receives events.
///
/// `Context` must be `Send + Sync + Sized` but no other requirements are
/// imposed. `Handler` receives an immutable reference to `Context`.
pub struct Trap<Context, EventHandler> {
// The thin wrapper around the Mojo C API for traps.
trap: UnsafeTrap,
// The inner data used by the Rust wrapper. It is locked because it can be
// accessed on multiple threads: Mojo trap callbacks can come from any
// thread. Shared ownership is used via `Arc` because `Weak` refs are held
// by each `HandleData`. In turn each `HandleData` is referenced by both
// `inner` (with `Arc`) and the C side (with `Weak`).
inner: Arc<Mutex<TrapInner<Context, EventHandler>>>,
}
struct TrapInner<Context, EventHandler> {
// Triggers are identified by an ID: `add_trigger` returns the ID, and
// the client can later call `remove_trigger` on said ID to unsubscribe from
// events on the associated handle. To support removal we maintain a mapping
// from the client's IDs to our internal per-handle data.
//
// Each `HandleData` is owned through an `Arc` since we use `Weak` refs
// that we pass to the C side as the context usize. The `Weak` refs are
// converted to raw pointers with `Weak::into_raw()`, passed to the C API,
// and reconstituted by `Weak::from_raw()` when passed to us by callback.
context_map: HashMap<TriggerId, Arc<HandleData<Context, EventHandler>>>,
event_handler: EventHandler,
next_trigger_id: usize,
}
struct HandleData<Context, EventHandler> {
context: Context,
// This is a weak ref because `SafeTrap::inner` is the owner. We cannot rely
// on the `Weak` referent's existence for soundness since if `Trap` is
// partially forgotten the owned reference in `context_map` may be dropped
// while Mojo trap handle isn't. This is important since Rust code cannot
// rely on `drop` calls for soundness.
owner: Weak<Mutex<TrapInner<Context, EventHandler>>>,
handle: MojoHandle,
trigger_id: TriggerId,
}
// Assert our types have the necessary thread safety traits.
mod asserts {
use super::*;
pub fn assert_send<T: Send>() {}
pub fn assert_sync<T: Sync>() {}
pub fn assert_traits<Context: Send + Sync, EventHandler: Send>() {
assert_send::<Trap<Context, EventHandler>>();
assert_sync::<Trap<Context, EventHandler>>();
// `Arc<Mutex<TrapInner<Context, EventHandler>>>` is shared between
// threads by ref, so it must be `Sync`. Normal type checking doesn't
// catch this since the sharing happens through the C FFI.
assert_sync::<Arc<Mutex<TrapInner<Context, EventHandler>>>>();
// `TrapInner<...>` must be `Send` for the same reason as above.
assert_send::<TrapInner<Context, EventHandler>>();
// The raw event handler is passed `HandleData` pointers on any thread,
// so it must be `Sync`.
assert_sync::<HandleData<Context, EventHandler>>();
}
}
impl<Context, EventHandler> Trap<Context, EventHandler>
where
// Context: Sync because it is shared by
// reference through `raw_handler`
// calls (from any thread) and Send because
// it is transitively owned by a
// Mutex that must be Sync.
Context: Send + Sync,
// EventHandler: Send because it is shared by value through `raw_handler`
// calls, synchronized by a mutex.
EventHandler: Fn(&TrapEvent, &Context) + Send,
{
/// Create a `Trap` that calls `handler` upon an event. `handler` takes a
/// reference to the `Context` type associated with the handle in addition
/// to `TrapEvent`.
///
/// `handler` must not call `add_trigger` or `remove_trigger` or deadlock is
/// assured. `handler` may also be called from within an `arm` call.
///
/// `handler` may panic but if `panic_any` is used and the panic value is
/// not `&str` or `String`, the message may be lost.
pub fn new(handler: EventHandler) -> Result<Trap<Context, EventHandler>, MojoResult> {
asserts::assert_traits::<Context, EventHandler>();
Ok(Trap {
trap: UnsafeTrap::new(Self::raw_handler)?,
inner: Arc::new(Mutex::new(TrapInner {
context_map: HashMap::new(),
event_handler: handler,
next_trigger_id: 0,
})),
})
}
/// Listen for `signals` on `handle` becoming satisfied or unsatisfied
/// (based on `condition`). Once armed the event handler may be called for
/// events on this handle.
///
/// # Arguments
///
/// * `handle` - the handle whose signals are to be watched.
/// * `signals` - the signals to watch for on `handle`.
/// * `condition` - watch for signals going high or low.
/// * `context` - user's context for the handle, passed to the handler.
pub fn add_trigger(
&self,
handle: MojoHandle,
signals: HandleSignals,
condition: TriggerCondition,
context: Context,
) -> Result<TriggerId, MojoResult> {
let (handle_data_ptr, id): (*const HandleData<_, _>, _) = {
// Lock in scope so we unlock before calling into Mojo. If the trap
// was armed and the new handle has a specified signal, our handler
// will be called. Since the handler locks `self.inner` we must
// avoid a deadlock.
let mut inner = self.inner.lock().unwrap();
let id = TriggerId(inner.next_trigger_id);
inner.next_trigger_id += 1;
let handle_data = Arc::new(HandleData {
context,
owner: Arc::downgrade(&self.inner),
handle,
trigger_id: id,
});
if let Some(_) = inner.context_map.insert(id, handle_data.clone()) {
panic!("ID unexpectedly exists in context_map");
}
// Downgrade to a weak pointer which we logically pass through the C
// FFI. When Mojo calls back our C handler function, we get the weak
// pointer back.
(Arc::downgrade(&handle_data).into_raw(), id)
};
match self.trap.add_trigger(handle, signals, condition, handle_data_ptr as usize) {
MojoResult::Okay => Ok(id),
e => {
// Re-lock the mutex to clean up after an error. We know at this point
// `handle` is not being watched.
let mut inner = self.inner.lock().unwrap();
// Drop the `Weak` ref we tried to give to the C side since it did not
// take it.
let _: Weak<HandleData<_, _>> = unsafe { Weak::from_raw(handle_data_ptr) };
// Clean up the `HandleData` object in our map.
inner.context_map.remove(&id);
Err(e)
}
}
}
/// Remove the trigger identified by the `trigger_id` returned by
/// `add_trigger`.
pub fn remove_trigger(&self, trigger_id: TriggerId) -> MojoResult {
let handle_data_ptr: *const HandleData<_, _> = {
let inner = self.inner.lock().unwrap();
match inner.context_map.get(&trigger_id) {
// `Arc::as_ptr` will return the same pointer as
// `Weak::into_raw` for a `Weak` derived from this `Arc`. This
// pointer will identify the `HandleData` added to the C side.
Some(handle_data) => Arc::as_ptr(handle_data),
None => return MojoResult::NotFound,
}
};
// If successful, this will cause the handler to be called immediately.
// From the handler we remove the map entry.
self.trap.remove_trigger(handle_data_ptr as usize)
}
/// Arm the trap to invoke event handler on any trigger condition.
///
/// If arming was successful, the trap remains armed until an event is
/// received. At this point it is immediately disarmed.
///
/// One of three things can happen:
/// * The trap was armed successfully. Returns MojoResult::Okay.
/// * Failed because events would have triggered immediately. Calls the
/// handler with some or all of the events. Returns
/// MojoResult::FailedPrecondition.
/// * Failed for some other reason. Returns the error.
pub fn arm(&self) -> MojoResult {
const MAX_BLOCKING_EVENTS: usize = 16;
let mut buf = [mem::MaybeUninit::uninit(); MAX_BLOCKING_EVENTS];
// Try to arm the trap. If blocking events were returned handle them.
let blocking_events: &[UnsafeTrapEvent] = match self.trap.arm(Some(&mut buf)) {
ArmResult::Blocked(events) => events,
ArmResult::Armed => return MojoResult::Okay,
ArmResult::Failed(e) => return e,
};
// Panic on failure because a poisoned mutex is unrecoverable for us.
let mut inner = self.inner.lock().unwrap();
for blocking_event in blocking_events {
// Any error in the calls below is unrecoverable: either a mutex was
// poisoned, or a needed object no longer exists.
let handle_data = unsafe { Self::get_handle_data_from_event(blocking_event) };
Self::call_handler_and_maybe_delete_data(&mut *inner, handle_data, blocking_event);
}
MojoResult::FailedPrecondition
}
// Unsafe because this fn must only be called once for a given `event`.
unsafe fn get_handle_data_from_event(
event: &UnsafeTrapEvent,
) -> Arc<HandleData<Context, EventHandler>> {
// A raw pointer version of `Weak<HandleData<Context, Handler>>`,
// emulating a weak reference held by the C side.
let handle_data_ptr = event.trigger_context() as *const HandleData<Context, EventHandler>;
// We want to grab an actual `Weak<HandleData<Context, Handler>>`. But
// we must take care to maintain the weak count correctly. The C side
// still holds a reference unless the event type is Cancelled.
let handle_data: Weak<HandleData<Context, EventHandler>> =
if event.result() == MojoResult::Cancelled {
// The C side effectively drops its reference and never calls
// this again with `handle_data_ptr`. So we take its reference,
// later dropping it.
unsafe { Weak::from_raw(handle_data_ptr) }
} else {
// Otherwise, we must clone the weak pointer and then forget it:
// we reconstitute the C side's `Weak` ref, grab our own, then
// `forget` the original so the C side still holds its ref.
let c_handle_data = unsafe { Weak::from_raw(handle_data_ptr) };
let our_handle_data = c_handle_data.clone();
mem::forget(c_handle_data);
our_handle_data
};
// Return an `Arc` reference to the handle's data or panic if it no
// longer exists.
handle_data.upgrade().expect("could not upgrade handle_data pointer")
}
// Remove a handle for which we got a `Cancelled` event.
fn remove_cancelled_trigger(
inner: &mut TrapInner<Context, EventHandler>,
trigger_id: TriggerId,
) {
let handle_data: Arc<_> =
inner.context_map.remove(&trigger_id).expect("tried to remove handle not present");
// If the caller managed the ref counts correctly, `handle_data`'s inner
// data should be dropped after this call.
assert_eq!(1, Arc::strong_count(&handle_data), "unexpected strong ref");
assert_eq!(0, Arc::weak_count(&handle_data), "unexpected weak ref");
}
fn call_handler_and_maybe_delete_data(
inner: &mut TrapInner<Context, EventHandler>,
handle_data: Arc<HandleData<Context, EventHandler>>,
event: &UnsafeTrapEvent,
) {
let safe_event = TrapEvent {
signals_state: event.signals_state(),
handle: handle_data.handle,
result: event.result(),
};
// Call the handler.
(inner.event_handler)(&safe_event, &handle_data.context);
if safe_event.result() == MojoResult::Cancelled {
let trigger_id = handle_data.trigger_id;
// Drop our `handle_data` ref *before* calling so the assertions
// in `remove_cancelled_trigger` are correct.
drop(handle_data);
Self::remove_cancelled_trigger(inner, trigger_id);
}
}
// Unsafe because this fn must only be called once for a given `event`.
unsafe fn handle_event_from_callback(event: &UnsafeTrapEvent) {
let handle_data = unsafe { Self::get_handle_data_from_event(event) };
// Get ready to call the handler. First get an upgraded reference to
// `handle_data.owner`. Just like above, if we can't upgrade something
// is fishy. We should just return.
let owner = handle_data.owner.upgrade().expect("owning SafeTrapInner no longer exists");
// This should never deadlock: the lock is taken in `add_trigger` and
// `remove_trigger`, but `add_trigger` unlocks before calling
// MojoAddTrigger and only re-locks it if failed. MojoRemoveTrigger also
// fires an event but `remove_trigger` unlocks before calling it.
let mut owner = owner.lock().expect("SafeTrapInner lock poisoned");
Self::call_handler_and_maybe_delete_data(&mut *owner, handle_data, event);
}
extern "C" fn raw_handler(event: &UnsafeTrapEvent) {
// If an error occurred the thread holding `SafeTrap` likely
// panicked so we can't do much. Catch the panic, print the message,
// and abort.
if let Err(e) =
std::panic::catch_unwind(|| unsafe { Self::handle_event_from_callback(event) })
{
// Standard panic objects are a &str or String. Try downcasting to
// print the message.
let message: &str = match e.downcast_ref::<&str>() {
Some(m) => m,
None => match e.downcast_ref::<String>() {
Some(m) => m.as_str(),
None => "unknown panic type",
},
};
eprintln!("aborting after panic in C handler function:\n{}", message);
std::process::abort()
}
}
}