Skip to main content

nros_node/executor/
deferred.rs

1//! Issue 2146 (RFC-0112 D2, the **Defer** part of the Tier 0 contract) --
2//! deferred dispatch for a hand-written host.
3//!
4//! A host that schedules work by priority (RTIC, a work queue, an RTOS task
5//! per callback class) wants the executor to find out WHAT is ready, and to
6//! run it LATER, in a task the host picks. Until this module the only form of
7//! that was the per-node `__nros_node_<pkg>_on_callback` slot ABI that
8//! `nros::node!` emitted, so a caller that did not go through the macros could
9//! not use it. (Nothing ever filled or drained that registry; issue 2161
10//! retired it.)
11//!
12//! The shape is three calls on [`Executor`], with no macro-private type:
13//!
14//! 1. [`Executor::set_deferred`] marks one registered callback (by the
15//!    [`HandleId`] its registration returned) as deferred. The spin never runs
16//!    it inline again.
17//! 2. [`Executor::set_defer_notifier`] installs a plain
18//!    `fn(ctx, HandleId)`. When a deferred callback becomes ready, the spin
19//!    ENQUEUES it and calls the notifier once, so the host can pend the task
20//!    that will run it. The notifier runs on the spinning thread, inside the
21//!    pass: it must not block and cannot reach the executor.
22//! 3. The host drains from its own task: [`Executor::run_deferred`] runs every
23//!    queued callback, [`Executor::run_deferred_handle`] runs one (for a host
24//!    that maps callbacks onto tasks of different priorities).
25//!
26//! The queue is one bit per callback slot (the executor already caps its
27//! callback table at 64 slots, `MAX_CALLBACK_SLOTS`), so it allocates nothing
28//! and an enqueue of a callback that is already queued is a no-op: the
29//! callback runs once per drain, and takes what one dispatch takes. Data left
30//! behind is found by the next spin, which queues the callback again.
31//!
32//! Timers cannot be deferred. A timer's readiness is decided INSIDE its
33//! dispatch (it consumes the spin's elapsed time there), so there is nothing
34//! to enqueue ahead of running it; [`Executor::set_deferred`] refuses one.
35
36use super::{
37    arena::EntryKind,
38    spin::Executor,
39    types::{DeferNotify, HandleId, SpinOnceResult},
40};
41
42impl<'s> Executor<'s> {
43    /// Defer (or stop deferring) one registered callback.
44    ///
45    /// A deferred callback is never run by `spin_once` / `spin_some`: when it
46    /// is ready the spin queues it and calls the notifier from
47    /// [`set_defer_notifier`](Self::set_defer_notifier), and it runs when the
48    /// host calls [`run_deferred`](Self::run_deferred) or
49    /// [`run_deferred_handle`](Self::run_deferred_handle).
50    ///
51    /// Returns `false`, changing nothing, when `id` names no live callback or
52    /// names a timer (see the module docs). Turning deferral OFF also drops
53    /// the callback from the queue; it stays ready, so the next spin runs it
54    /// inline.
55    ///
56    /// A deferred callback takes no part in the spin's trigger
57    /// ([`set_trigger`](Self::set_trigger)): it is neither "ready" for
58    /// `Trigger::Any` nor required by `Trigger::All`.
59    pub fn set_deferred(&mut self, id: HandleId, deferred: bool) -> bool {
60        let Some(slot) = self.resolve_handle(id) else {
61            return false;
62        };
63        let Some(meta) = self.entries.get(slot).and_then(|e| e.as_ref()) else {
64            return false;
65        };
66        if matches!(meta.kind, EntryKind::Timer) {
67            return false;
68        }
69        let bit = 1u64 << slot;
70        if deferred {
71            self.deferred_mask |= bit;
72        } else {
73            self.deferred_mask &= !bit;
74            self.deferred_pending &= !bit;
75        }
76        true
77    }
78
79    /// Whether `id` names a deferred callback.
80    pub fn is_deferred(&self, id: HandleId) -> bool {
81        self.resolve_handle(id)
82            .is_some_and(|slot| self.deferred_mask & (1u64 << slot) != 0)
83    }
84
85    /// Install the notifier the spin calls when it queues a deferred
86    /// callback, with the context it is called with. Replaces any earlier
87    /// notifier.
88    ///
89    /// Called once per queueing: a callback that is already queued is not
90    /// queued (or notified) again until a drain has run it. With no notifier
91    /// the queue still fills, and the host can poll
92    /// [`has_deferred`](Self::has_deferred).
93    ///
94    /// `ctx` is handed back verbatim and never dereferenced by the executor;
95    /// whatever it points to must stay valid while the notifier is installed.
96    pub fn set_defer_notifier(&mut self, notify: DeferNotify, ctx: *mut core::ffi::c_void) {
97        self.defer_notify = Some((notify, ctx));
98    }
99
100    /// Remove the notifier. Deferred callbacks keep being queued.
101    pub fn clear_defer_notifier(&mut self) {
102        self.defer_notify = None;
103    }
104
105    /// Whether any deferred callback is queued and waiting for a drain.
106    pub fn has_deferred(&self) -> bool {
107        self.deferred_pending != 0
108    }
109
110    /// Whether the deferred callback `id` is queued.
111    pub fn is_deferred_pending(&self, id: HandleId) -> bool {
112        self.resolve_handle(id)
113            .is_some_and(|slot| self.deferred_pending & (1u64 << slot) != 0)
114    }
115
116    /// Run every queued deferred callback once, in slot (registration)
117    /// order, and empty the queue. The counts are `spin_once`'s.
118    ///
119    /// Call this from the host task that owns deferred work. It does no I/O
120    /// and never waits: a callback that finds no data (it was taken some
121    /// other way) is an `Ok(false)` dispatch, counted as nothing.
122    pub fn run_deferred(&mut self) -> SpinOnceResult {
123        let mut result = SpinOnceResult::new();
124        self.reap_deferred_releases();
125        let mut rest = self.deferred_pending;
126        while rest != 0 {
127            let slot = rest.trailing_zeros() as usize;
128            rest &= rest - 1;
129            self.dispatch_deferred_slot(slot, &mut result);
130        }
131        result
132    }
133
134    /// Run the deferred callback `id` once if it is queued. Returns whether it
135    /// ran (`false` when it was not queued or `id` names no live callback).
136    ///
137    /// For a host that maps deferred callbacks onto tasks of different
138    /// priorities: the notifier says which one is ready, and the task that
139    /// owns it runs exactly that one.
140    pub fn run_deferred_handle(&mut self, id: HandleId) -> bool {
141        let Some(slot) = self.resolve_handle(id) else {
142            return false;
143        };
144        self.reap_deferred_releases();
145        let mut result = SpinOnceResult::new();
146        self.dispatch_deferred_slot(slot, &mut result)
147    }
148
149    /// The spin's half: move every deferred bit of `ready` into the queue,
150    /// notify the newly queued ones, and return what is left for the spin to
151    /// dispatch inline.
152    pub(crate) fn defer_ready(&mut self, ready: u64) -> u64 {
153        let deferred = ready & self.deferred_mask;
154        if deferred == 0 {
155            return ready;
156        }
157        let fresh = deferred & !self.deferred_pending;
158        self.deferred_pending |= deferred;
159        if let Some((notify, ctx)) = self.defer_notify {
160            let mut rest = fresh;
161            while rest != 0 {
162                let slot = rest.trailing_zeros() as usize;
163                rest &= rest - 1;
164                notify(ctx, self.handle_for(slot));
165            }
166        }
167        ready & !self.deferred_mask
168    }
169
170    /// Forget a slot's deferral when its entry is released, so the slot's
171    /// next occupant starts inline and unqueued.
172    pub(crate) fn forget_deferred(&mut self, slot: usize) {
173        if slot < 64 {
174            let bit = 1u64 << slot;
175            self.deferred_mask &= !bit;
176            self.deferred_pending &= !bit;
177        }
178    }
179
180    fn dispatch_deferred_slot(&mut self, slot: usize, result: &mut SpinOnceResult) -> bool {
181        if slot >= 64 {
182            return false;
183        }
184        let bit = 1u64 << slot;
185        if self.deferred_pending & bit == 0 {
186            return false;
187        }
188        // Held: its release is pending (it never runs again) or its callback
189        // is already running below us. Either way not now; a held slot that
190        // still has data is queued again by the next spin.
191        if Self::slot_held(self.slot_tags, slot) {
192            return false;
193        }
194        self.deferred_pending &= !bit;
195        let Some(meta) = self.entries.get(slot).and_then(|e| e.as_ref()) else {
196            return false;
197        };
198        let kind = meta.kind;
199        let try_process = meta.try_process;
200        // SAFETY: `meta.offset` is this live entry's arena slot, written by its
201        // registration; `try_process` matches the entry's concrete type.
202        let data_ptr = unsafe { (self.arena.as_mut_ptr() as *mut u8).add(meta.offset) };
203        Self::begin_dispatch(self.slot_tags, slot);
204        // SAFETY: as the spin's own dispatch: `data_ptr` is the live entry
205        // `try_process` was registered for, and nothing else runs it (the slot
206        // is marked dispatching). `delta_us` is only read by a timer, which
207        // cannot be deferred, so 0 is never observed.
208        let processed = unsafe { try_process(data_ptr, 0, slot as u8) };
209        self.finish_dispatch(slot);
210        // One dispatch takes what one call takes; an attributed slot is asked
211        // `has_data` again next spin, exactly as the inline drain arranges.
212        #[cfg(all(feature = "alloc", feature = "rmw-cffi"))]
213        {
214            self.entity_recheck |= bit & self.entity_attributed;
215        }
216        match processed {
217            Ok(true) => match kind {
218                EntryKind::Subscription => result.subscriptions_processed += 1,
219                EntryKind::Service
220                | EntryKind::ServiceClient
221                | EntryKind::ActionServer
222                | EntryKind::ActionClient => result.services_handled += 1,
223                EntryKind::Timer => result.timers_fired += 1,
224                EntryKind::GuardCondition => {}
225            },
226            Ok(false) => {}
227            Err(_) => match kind {
228                EntryKind::Subscription => result.subscription_errors += 1,
229                EntryKind::Service
230                | EntryKind::ServiceClient
231                | EntryKind::ActionServer
232                | EntryKind::ActionClient => result.service_errors += 1,
233                EntryKind::Timer | EntryKind::GuardCondition => {}
234            },
235        }
236        true
237    }
238}