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}