Skip to main content

nros_node/
c_waker.rs

1//! Phase 122.3.c.6.e — C-ABI-compatible `Waker` bridge.
2//!
3//! Rust's [`core::task::Waker`] is the trait-level event-driven
4//! primitive but it isn't a C-friendly handle (the `RawWaker` data
5//! pointer + vtable layout isn't exposed across the ABI boundary).
6//! Backends that wake via C function pointers can register a
7//! [`CWakeState`] here, and the [`make_waker`] helper builds a
8//! `Waker` that calls back into the C function on wake.
9//!
10//! Used by the L1 polling-mode C / C++ FFI to register
11//! event-driven callbacks for subscription / service-server /
12//! service-client / action server-channel and client-channel
13//! events. See `nros-c/src/action/server.rs::nros_action_server_set_*_wake_callback`
14//! for usage.
15//!
16//! # Safety
17//!
18//! [`CWakeState`] must remain at a stable address for the entire
19//! lifetime of any Waker (or clone of) built from it. Typical
20//! pattern: store it inline in the C handle's caller-provided
21//! `_opaque` storage so it lives as long as the entity itself.
22
23use core::{
24    ffi::c_void,
25    task::{RawWaker, RawWakerVTable, Waker},
26};
27
28/// C function-pointer signature for wake callbacks. Backends call
29/// this when the underlying entity has data / a reply / a request
30/// pending.
31pub type CWakeFn = unsafe extern "C" fn(*mut c_void);
32
33/// Stable-address storage for a C wake callback. The pointer to
34/// this struct is what the [`Waker`] holds in its
35/// [`RawWaker::data`] slot, so it MUST NOT move after a Waker has
36/// been built from it.
37#[repr(C)]
38pub struct CWakeState {
39    /// Function called when the Waker is woken. `None` disables.
40    pub fn_ptr: Option<CWakeFn>,
41    /// Opaque pointer passed to `fn_ptr` on wake.
42    pub ctx: *mut c_void,
43}
44
45impl CWakeState {
46    /// Empty / disabled state.
47    pub const fn empty() -> Self {
48        Self {
49            fn_ptr: None,
50            ctx: core::ptr::null_mut(),
51        }
52    }
53
54    /// Update the callback. Existing Wakers built from `self` pick
55    /// up the new value on their next wake — no re-registration
56    /// needed (the Waker's data pointer is unchanged).
57    pub fn set(&mut self, fn_ptr: Option<CWakeFn>, ctx: *mut c_void) {
58        self.fn_ptr = fn_ptr;
59        self.ctx = ctx;
60    }
61}
62
63/// SAFETY: `CWakeState` holds a function pointer (`Send`/`Sync`) +
64/// an opaque user `ctx` pointer (no auto-impl). For the wake path
65/// to be sound across threads the C caller must ensure the
66/// underlying object behind `ctx` is reachable from whatever thread
67/// the backend dispatches the wake callback on. By marking the
68/// state as Send+Sync we let backends (e.g. `AtomicWaker`) stash
69/// the Waker in shared state; the C contract is documented at the
70/// FFI surface ("the C `ctx` must outlive every wake-callback
71/// invocation and be safe to read from any thread the runtime
72/// dispatches wakes on").
73unsafe impl Send for CWakeState {}
74unsafe impl Sync for CWakeState {}
75
76/// Build a [`Waker`] that calls `state.fn_ptr(state.ctx)` on wake.
77///
78/// # Safety
79/// * `state` must point to a valid `CWakeState`.
80/// * `state` must remain at the same address for as long as the
81///   returned Waker (and any clones a backend stashes) lives.
82/// * If a backend dispatches wakes from another thread, `state`'s
83///   `ctx` must be safe to read from that thread (see [`CWakeState`]
84///   `Send` / `Sync` discussion).
85pub unsafe fn make_waker(state: *const CWakeState) -> Waker {
86    unsafe { Waker::from_raw(RawWaker::new(state as *const (), &VTABLE)) }
87}
88
89static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop_fn);
90
91/// # Safety
92///
93/// Invoked by the `RawWakerVTable` clone slot. `data` is the pointer
94/// passed to [`make_waker`] (i.e. a `*const CWakeState`); the runtime
95/// upholds [`make_waker`]'s contract (stable address, valid for the
96/// Waker's lifetime), so cloning is a trivial pointer copy.
97unsafe fn clone(data: *const ()) -> RawWaker {
98    RawWaker::new(data, &VTABLE)
99}
100
101/// # Safety
102///
103/// Invoked by the `RawWakerVTable` wake slot. `data` must be the
104/// `*const CWakeState` originally supplied to [`make_waker`] and must
105/// still point at a valid, stable-address `CWakeState`. Delegates to
106/// [`wake_by_ref`].
107unsafe fn wake(data: *const ()) {
108    unsafe { wake_by_ref(data) };
109}
110
111/// # Safety
112///
113/// Invoked by the `RawWakerVTable` wake-by-ref slot. `data` must be a
114/// live `*const CWakeState` as established by [`make_waker`].
115/// Dereferences the state and, if a callback is set, calls
116/// `fn_ptr(ctx)` — so `ctx` must satisfy the FFI contract documented
117/// on [`CWakeState`] (outlive every wake invocation, be safe to read
118/// from the dispatching thread).
119unsafe fn wake_by_ref(data: *const ()) {
120    let state = unsafe { &*(data as *const CWakeState) };
121    if let Some(f) = state.fn_ptr {
122        unsafe { f(state.ctx) };
123    }
124}
125
126/// # Safety
127///
128/// Invoked by the `RawWakerVTable` drop slot. The `CWakeState` is
129/// owned by the C caller, so this intentionally does nothing —
130/// callers do not need to uphold any invariant beyond passing the
131/// original `data` pointer.
132unsafe fn drop_fn(_data: *const ()) {
133    // `CWakeState` is owned by the caller; we never free.
134}