pub struct Executor<'s> { /* private fields */ }Implementations§
Source§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub unsafe fn open_in(
config: &ExecutorConfig<'_>,
backing: &'s mut [MaybeUninit<u64>],
sizing: ExecutorSizing,
) -> Result<Executor<'s>, NodeError>
pub unsafe fn open_in( config: &ExecutorConfig<'_>, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Result<Executor<'s>, NodeError>
phase-271 — open a new executor session over caller-supplied backing,
sized by sizing (per-entry sizing). The core, non-generic sized entry
point: the alloc open convenience leaks a default
backing and delegates here, and the nros::main! macro emits a backing
sized to the entry’s own entity count.
Phase 115.M.4 — auto-registers the cffi vtable for whichever
backend the build was configured for, mirroring the C++ side’s
#ifdef NROS_RMW_<NAME> fan-out in <rclcpp/node.hpp>. The
runtime’s atomic vtable slot is idempotent: a re-call of any
backend’s register() is a no-op, so the fan-out below is safe
to invoke on every Executor::open (cheaper than a Once and
doesn’t pull in std::sync for no_std targets).
Connects to the middleware at the locator specified in config.
§Safety
backing must be ≥ sizing.u64_len() words, live for 's, and be
otherwise untouched while the executor lives (see
from_session_in).
Source§impl Executor<'static>
impl Executor<'static>
Sourcepub fn open(config: &ExecutorConfig<'_>) -> Result<Executor<'static>, NodeError>
pub fn open(config: &ExecutorConfig<'_>) -> Result<Executor<'static>, NodeError>
Open a new executor session using the active RMW backend, at the
build-time default sizing. Convenience over
open_in: leaks a default-sized backing (executor-
lifetime) so existing callers keep the zero-storage-arg signature.
Per-entry sizing goes through open_in / the nros::main! macro.
§Example
let config = ExecutorConfig::from_env().node_name("my_node");
let mut executor = Executor::open(&config)?;Sourcepub fn open_sized(
config: &ExecutorConfig<'_>,
sizing: ExecutorSizing,
) -> Result<Executor<'static>, NodeError>
pub fn open_sized( config: &ExecutorConfig<'_>, sizing: ExecutorSizing, ) -> Result<Executor<'static>, NodeError>
phase-271 — like open but sized to a caller-supplied
sizing (its own declared topology) instead of the build-time default.
The alloc entry point the nros::main! macro’s native board path uses
to size a fat entry (>default MAX_CBS callbacks) without a
workspace-global NROS_EXECUTOR_MAX_CBS. Leaks a sizing-sized backing
(executor-lifetime); no-alloc entries use open_in with their own static.
Sourcepub fn open_multi(
specs: &[SessionSpec<'_>],
) -> Result<Executor<'static>, NodeError>
pub fn open_multi( specs: &[SessionSpec<'_>], ) -> Result<Executor<'static>, NodeError>
Phase 128.F.1 — explicit per-backend session declaration for
bridge mode. specs[0] becomes the primary session; specs[1..]
open as extras keyed by RMW name. After construction, every
create_node_on(name, rmw) call dispatches to whichever
session was opened under that RMW name (or, when the rmw name
matches the primary, the primary session itself).
Single-backend callers should keep using
open — this entry costs an extra
open_with_rmw per spec and adds no value when only one
backend is linked.
$NROS_RMW env is ignored: bridge mode wants explicit names.
Default-sized alloc convenience over
open_multi_in (leaks a default backing).
Sourcepub fn open_with_rmw(
rmw_name: &str,
config: &ExecutorConfig<'_>,
) -> Result<Executor<'static>, NodeError>
pub fn open_with_rmw( rmw_name: &str, config: &ExecutorConfig<'_>, ) -> Result<Executor<'static>, NodeError>
Phase 104.C.1 — open the Executor against a specific RMW
backend by name. Selects from the named registry (Phase
104.B.2). rmw_name must match one of the names a backend
registered under ("zenoh", "cyclonedds", "xrce", …).
Equivalent to Executor::open when the registry has exactly
one backend (the default-backend fast path). Use this entry
point in multi-backend builds where Executor::open would
pick the first-registered slot.
Single-Executor multi-Node multi-RMW (the long-term Design X
from docs/roadmap/phase-104-multi-backend-bridges.md) is
follow-up work — Phase 104.C.2 + C.3.
Default-sized alloc convenience over
open_with_rmw_in (leaks a default backing).
Source§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub unsafe fn open_with_rmw_in(
rmw_name: &str,
config: &ExecutorConfig<'_>,
backing: &'s mut [MaybeUninit<u64>],
sizing: ExecutorSizing,
) -> Result<Executor<'s>, NodeError>
pub unsafe fn open_with_rmw_in( rmw_name: &str, config: &ExecutorConfig<'_>, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Result<Executor<'s>, NodeError>
Per-entry-sized open_with_rmw: carves backing
for the executor’s tables instead of leaking a default one.
§Safety
backing/sizing as in from_session_in.
Source§impl Executor<'static>
impl Executor<'static>
Sourcepub fn from_session(session: CffiSession) -> Executor<'static>
pub fn from_session(session: CffiSession) -> Executor<'static>
Create an executor from an already-opened session, using the build-time
default sizing (MAX_CBS/MAX_SC/ARENA_SIZE). Convenience for
std/alloc callers that don’t size per-entry: it leaks a default-sized
backing (executor-lifetime, one-time) and calls
from_session_in. Per-entry sizing goes through
the macro / open_in instead.
Sourcepub fn from_session_with(
session: CffiSession,
config: &ExecutorConfig<'_>,
) -> Executor<'static>
pub fn from_session_with( session: CffiSession, config: &ExecutorConfig<'_>, ) -> Executor<'static>
from_session with an ExecutorConfig, so a
caller that brings its own session can also bring its own clock.
issue 0709 / issue 0687 — from_session takes no config, and that is
the path the no-port population uses: it accepts any Session, so a
consumer with a non-cffi backend reaches the executor through it and had
NO way to install clock_us. phase-359 W10 argued the std-without-a-
port clock fallbacks could go because “a caller with a clock installs it
through ExecutorConfig::clock_us” — true for open, false here, which
is half of why that deletion was reverted.
Only the timing sources are read from config; identity (locator,
domain, names) belongs to the session the caller already opened. As in
open_in, a None field does NOT clobber the
platform default — it means “not specified” (the bug issue 0671
records).
Source§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub unsafe fn open_with_session(session: *mut CffiSession) -> Executor<'static>
pub unsafe fn open_with_session(session: *mut CffiSession) -> Executor<'static>
Phase 228.B (RFC-0015) — construct a tier task’s executor that shares
a session opened once by the orchestration main().
In the per-tier execution model main() opens one RMW session, then
spawns one RTOS task per priority tier; each task calls this to get an
Executor over the same session (the Borrowed session store — this
executor neither owns nor closes it), registers its tier’s callback
groups, and spins. Thin alias over ExecutorFfi::from_session_ptr.
§Safety
session must outlive every executor/task built from it (the
orchestration main() holds it and never returns / WFIs), and must not
be mutated except through these executors’ spin calls.
Sourcepub fn session_ptr(&mut self) -> *mut CffiSession
pub fn session_ptr(&mut self) -> *mut CffiSession
Raw pointer to this executor’s RMW session, for the per-tier model:
the boot task opens the one session via Executor::open (the RMW
session is a process-wide singleton — opening twice fails), then hands
this pointer to each spawned tier task’s
Executor::open_with_session. The boot task’s executor owns the
session and outlives every borrower, so the pointer stays valid for the
program’s life. Works for both Owned and Borrowed stores.
§Safety
The returned pointer aliases self.session. Callers must keep self
alive (not moved/dropped) for as long as any tier executor uses the
pointer, and must only touch the session through executor spin calls
(the RMW backend serializes concurrent access through its own locks).
Sourcepub fn session_handle(&mut self) -> SessionHandle
pub fn session_handle(&mut self) -> SessionHandle
Opaque, Send form of session_ptr — the per-tier
model hands this to each spawned tier task (it can cross the RTOS task /
thread boundary, which a bare *mut cannot). See SessionHandle.
§Safety
Same contract as session_ptr: self (the session
owner) must outlive every executor built from the handle.
Sourcepub unsafe fn open_with_session_handle(
handle: SessionHandle,
) -> Executor<'static>
pub unsafe fn open_with_session_handle( handle: SessionHandle, ) -> Executor<'static>
Open an Executor over the session a SessionHandle refers to (the
Borrowed store — neither owns nor closes it). The tier-task counterpart
to session_handle.
§Safety
The handle’s session must still be alive (its owning executor not moved or dropped); access only through executor spin calls.
Sourcepub fn set_active_groups(
&mut self,
groups: &[(&str, &str, &str)],
) -> Result<(), BindError>
pub fn set_active_groups( &mut self, groups: &[(&str, &str, &str)], ) -> Result<(), BindError>
Phase 228.C — set this tier executor’s active callback-group filter. The
generated per-tier task calls this before registering nodes; afterwards
only callbacks whose .callback_group() is in groups register here.
An empty slice (or never calling it) leaves the wildcard — register all
callbacks (the single-tier degenerate case + today’s behaviour).
Sourcepub fn group_active(
&self,
node_name: &str,
node_namespace: &str,
group: &str,
) -> bool
pub fn group_active( &self, node_name: &str, node_namespace: &str, group: &str, ) -> bool
Phase 228.C — whether a callback in group should register in this
executor under the current filter. The wildcard accepts everything.
Sourcepub fn set_domain_id(&mut self, domain_id: u32)
pub fn set_domain_id(&mut self, domain_id: u32)
TopicInfo/ServiceInfo so the zenoh backend can declare liveliness.
Issue 0656 — set the ROS domain for entities this executor declares.
For bindings that build an executor from an existing session
(from_session_ptr_in), where no ExecutorConfig is available and the
domain would otherwise stay at its 0 floor.
pub fn set_node_identity(&mut self, node_name: &str, namespace: &str)
Sourcepub fn declare_remap(
&mut self,
node_name: &str,
namespace: &str,
from: &str,
to: &str,
) -> Result<(), RemapRefused>
pub fn declare_remap( &mut self, node_name: &str, namespace: &str, from: &str, to: &str, ) -> Result<(), RemapRefused>
Record one launch <remap from= to=/> rule for the node identified by
(node_name, namespace). Rules are matched in declaration order (first
wins) by Self::resolve_entity_name_for. Errors when a string
overflows its slot or the table is at MAX_REMAPS — callers surface
this rather than silently dropping a routing rule.
This declares an AUTHORITATIVE rule. Declaration order settles
which of two launch rules for one name wins; it is not how an argv rule
is ranked against a launch rule. The --ros-args rules share this table
but are tagged as the fallback tier, and resolution reads both tiers
through crate::names::resolve_name_layered — so an argv rule
installed BEFORE a launch rule still loses to it.
Issue 1971 – a GENERATED entry no longer calls this: its launch rules
are the flash table (super::launch_remap::set_launch_remaps). This
writes the RUNTIME table, for hand-written callers; on bare metal that
table is 0 slots by default and the call is refused by name
(RemapRefused::NoRuntimeTable).
Sourcepub fn resolve_entity_name_for(
&self,
node_name: &str,
namespace: &str,
source: &str,
) -> Result<String<nros_node::::names::ResolvedName::{constant#0}>, ()>
pub fn resolve_entity_name_for( &self, node_name: &str, namespace: &str, source: &str, ) -> Result<String<nros_node::::names::ResolvedName::{constant#0}>, ()>
Resolve a source-level entity name for the node identified by
(node_name, namespace): ROS 2 name expansion (~/relative → FQN)
plus this node’s declared remap rules (exact-FQN match, first rule
wins). Nodes with no rules still get expansion. Errors on an
unexpandable name (see crate::names::expand_name).
Two tiers, through crate::names::resolve_name_layered: the rules
Self::declare_remap recorded for this node are AUTHORITATIVE (the
generated entry declares the launch projection there), and the
--ros-args rules crate::ros_args::install_argv_remaps put in the
same table are the FALLBACK, consulted only when no launch rule
matched. rcl does the same with its local and global arguments
(rcl_remap_topic_name, Humble rcl/remap.h).
Sourcepub fn set_launch_remaps(&mut self, table: &'static [LaunchRemap])
pub fn set_launch_remaps(&mut self, table: &'static [LaunchRemap])
Issue 1971 – give this executor its own launch remap table instead of
the image’s (super::launch_remap::set_launch_remaps). A borrow of
.rodata; nothing is copied.
Sourcepub fn resolve_entity_name(
&self,
source: &str,
) -> Result<String<nros_node::::names::ResolvedName::{constant#0}>, ()>
pub fn resolve_entity_name( &self, source: &str, ) -> Result<String<nros_node::::names::ResolvedName::{constant#0}>, ()>
Self::resolve_entity_name_for against the executor’s CURRENT node
identity (set_node_identity) — the nros-c registration sites set that
identity per node immediately before registering each entity.
Sourcepub fn bind_node_name_sched(
&mut self,
name: &str,
namespace: &str,
sc: SchedContextId,
) -> Result<(), BindError>
pub fn bind_node_name_sched( &mut self, name: &str, namespace: &str, sc: SchedContextId, ) -> Result<(), BindError>
Seed a config-resolved tier binding by (name, namespace) before the
node is built. NodeBuilder::build consults this table when no
explicit .sched() override is given — the table entry then wins over
the SchedContextId(0) default (precedence: explicit > table > 0).
Call BEFORE node_builder(name).build(). An existing entry for the
same (name, namespace) key is overwritten (last-write wins). Overflow
past MAX_NODES is an Err, never a dropped binding — issue 1172. An
empty namespace is normalised
to "/" to match what NodeBuilder::build computes for a root-NS node.
Sourcepub fn bind_node_rank(
&mut self,
name: &str,
namespace: &str,
rank: u8,
) -> Result<(), BindError>
pub fn bind_node_rank( &mut self, name: &str, namespace: &str, rank: u8, ) -> Result<(), BindError>
phase-509 P2 (RFC-0108 D2) – seed the contract rank of the node
(name, namespace) before it is built: 0 is the most urgent, as codegen
normalises the derivation’s ranks. Every callback the node registers
then dispatches at that rank inside its sched context’s bucket
(Priority::dispatch_level).
A node with no seeded rank holds 0.
Shares the node-name table with Self::bind_node_name_sched: one entry
per node carries both, so neither call disturbs the other’s half. Same
error contract: a binding that does not fit is refused, never dropped.
Sourcepub fn bind_group_sched(
&mut self,
name: &str,
namespace: &str,
group: &str,
sc: SchedContextId,
) -> Result<(), BindError>
pub fn bind_group_sched( &mut self, name: &str, namespace: &str, group: &str, sc: SchedContextId, ) -> Result<(), BindError>
Seed a config-resolved tier binding by (name, namespace, group) before
entities are registered. apply_node_default_sched consults this table
first (group table > node default > SchedContextId(0)).
Call BEFORE entity creation. An existing entry for the same
(name, namespace, group) key is overwritten (last-write wins). Overflow
past MAX_CBS is an Err, never a dropped binding — issue 1172. An
empty namespace is normalised to
"/" to match NodeBuilder::build. Mirror of bind_node_name_sched.
Sourcepub fn default_sched_context_id(&self) -> SchedContextId
pub fn default_sched_context_id(&self) -> SchedContextId
Identifier of the auto-created default Fifo-class scheduling
context. Every callback registered without an explicit
Self::bind_handle_to_sched_context binds to this SC.
Sourcepub fn create_sched_context(
&mut self,
sc: SchedContext,
) -> Result<SchedContextId, NodeError>
pub fn create_sched_context( &mut self, sc: SchedContext, ) -> Result<SchedContextId, NodeError>
Register a new scheduling context. Returns a SchedContextId
callers pass to Self::bind_handle_to_sched_context to attach
callbacks. Phase 110.B.
Sourcepub fn epoch_now_us(&self) -> Option<u64>
pub fn epoch_now_us(&self) -> Option<u64>
RFC-0052 W3b.2 — wall-clock µs since the UNIX epoch, when this
target has an epoch source (config epoch_us, defaulted from
SystemTime on hosted configs). None = no wall clock; age
monitors must not have been baked (the emitter refuses).
Sourcepub fn set_monitor_table(&mut self, table: &'static [MonitorSpec])
pub fn set_monitor_table(&mut self, table: &'static [MonitorSpec])
RFC-0052 W3b.4 — install the baked contract-monitor table. Call
BEFORE entity creation so create_publisher can attach each
contracted endpoint’s counter cell. Mirrors set_qos_overrides:
&'static, codegen-baked, empty by default.
phase-496 W1 – the executor watches at most
MAX_MONITORS rows
(NROS_EXECUTOR_MAX_MONITORS). A generated entry cannot reach this with
more: its table carries a compile-time assertion that names the knob,
and the C++ installer refuses at runtime. A hand-written longer table is
a debug-build panic naming the knob, because in release the spin loop
would inspect only the first MAX_MONITORS and leave the rest silently
unwatched.
Sourcepub fn try_set_monitor_tables(
&mut self,
table: &'static [MonitorSpec],
ages: &'static [AgeMonitorSpec],
) -> Result<(), MonitorTableFull>
pub fn try_set_monitor_tables( &mut self, table: &'static [MonitorSpec], ages: &'static [AgeMonitorSpec], ) -> Result<(), MonitorTableFull>
phase-496 W1 – install BOTH baked monitor tables, or neither.
The refusing installer: a table longer than this executor watches
(MAX_MONITORS / MAX_AGE_MONITORS) is Err, and its Display names
the knob to raise, before anything is installed. Nothing is truncated:
the spin loop inspects only the first MAX_* specs, so accepting a
longer table would boot with its tail silently unwatched. The C ABI’s
nros_cpp_install_monitors refuses on the same check.
Sourcepub fn monitor_table(&self) -> &'static [MonitorSpec]
pub fn monitor_table(&self) -> &'static [MonitorSpec]
The installed monitor table (empty unless the entry set one).
Sourcepub fn set_age_table(&mut self, table: &'static [AgeMonitorSpec])
pub fn set_age_table(&mut self, table: &'static [AgeMonitorSpec])
W3b.5 — install the baked subscriber age-contract table. Call
BEFORE entity creation so create_subscription can attach each
contracted endpoint’s age cell (needs an epoch source — see
ExecutorConfig::epoch_us; without one the take path records
nothing and age monitors stay silent).
phase-496 W1 – at most
MAX_AGE_MONITORS rows
(NROS_EXECUTOR_MAX_AGE_MONITORS), on the same terms as
Self::set_monitor_table.
Sourcepub fn age_table(&self) -> &'static [AgeMonitorSpec]
pub fn age_table(&self) -> &'static [AgeMonitorSpec]
The installed age table (empty unless the entry set one).
Sourcepub fn set_hazard_table(&mut self, table: &'static [HazardSpec])
pub fn set_hazard_table(&mut self, table: &'static [HazardSpec])
phase-462 W3b — install the baked hazard table. Each row’s guards name
rows of the tables Self::set_monitor_table and
Self::set_age_table install, so install those first; a guard whose
row is absent never faults. The observer runs after the monitor rows on
every monitor tick and reports hazard-detected, hazard-reaction and
hazard-recovered on the same drain.
Sourcepub fn set_mode_tables(
&mut self,
modes: &'static [ModeTables],
) -> Result<(), ModeTablesMismatch>
pub fn set_mode_tables( &mut self, modes: &'static [ModeTables], ) -> Result<(), ModeTablesMismatch>
phase-462 W4 – install the contract’s mode variants of the two monitor
tables; entry i is mode i, and entry 0 must be the base tables
already installed. A switch is asked for with
super::monitor::request_mode and applied at the next spin.
§Errors
super::monitor::ModeTablesMismatch when an entry’s tables are not the base’s
length: a variant shares the base’s cells and the per-row monitor
state by index, so a variant of another length would judge one row
by another’s requirement. Nothing is installed.
Sourcepub fn current_mode(&self) -> u16
pub fn current_mode(&self) -> u16
The mode whose tables this executor judges by (0 = the base).
Sourcepub fn hazard_table(&self) -> &'static [HazardSpec]
pub fn hazard_table(&self) -> &'static [HazardSpec]
The installed hazard table (empty unless the entry set one).
Sourcepub fn set_epoch_clock(&mut self, epoch_us: fn() -> u64)
pub fn set_epoch_clock(&mut self, epoch_us: fn() -> u64)
W3b.5 — override the wall-clock (epoch µs) source age monitors take
message stamps against. Hosted builds default to SystemTime; a
board with a synced RTC installs its own here (or via
ExecutorConfig::epoch_us). Call BEFORE entity creation — the age
hook captures this at create_subscription time.
Sourcepub fn bind_raw_subscription_stamp(
&mut self,
handle: HandleId,
topic: &str,
stamp_offset: u16,
) -> bool
pub fn bind_raw_subscription_stamp( &mut self, handle: HandleId, topic: &str, stamp_offset: u16, ) -> bool
Issue 2215 — bind a C/C++ callback subscription to its topic’s
contracted age row, so its stamped takes feed the silence rule (and,
with an epoch source, the age rule) as the Rust typed paths’ do.
stamp_offset is the CDR byte offset of the type’s stamp.sec
(encapsulation header included; the C++ message type states it as
STAMP_OFFSET). Returns true when a row was bound; false when the
topic has no age row or handle is not a C callback subscription,
which is not an error: such a subscription has nothing to feed.
Sourcepub fn skipped_releases(&self) -> u32
pub fn skipped_releases(&self) -> u32
phase-474 I11 – grid releases skipped since the last
clear_release_jitter_stats, because the loop came back after they
were due. Zero for a declared-cadence or zero-timeout caller.
Sourcepub fn release_jitter(&self) -> (u64, u32, u32)
pub fn release_jitter(&self) -> (u64, u32, u32)
Release-jitter statistics from the spin loop: worst lateness in microseconds, the number of wakes that were already late, and the number of wakes total.
The maximum is the figure of merit, and the ratio is what tells the two failures apart: one late wake in ten thousand is a glitch, ten thousand in ten thousand means the period cannot be met at all.
Zero on a build with no clock – spin_period refuses to run at all
there (issue 0709), so there is nothing to have measured.
Sourcepub fn late_wakes_beyond_granularity(&self) -> u32
pub fn late_wakes_beyond_granularity(&self) -> u32
phase-436 A2c – of the late wakes release_jitter counts, those
late by more than Self::release_jitter_granularity_us.
A wake is late at one microsecond past its release. A loop paced by a
tick-granular park comes back up to a tick past it on every wake, by
design, so late approaches total on a loop that is keeping its
period perfectly well. This count leaves that rounding out: it is the
lateness the measurement can actually resolve. Cleared with the rest.
Sourcepub fn clear_release_jitter_stats(&mut self)
pub fn clear_release_jitter_stats(&mut self)
Reset the release-jitter statistics. For monitoring code that logs and clears per window, so a single early outlier does not pin the maximum for the life of the process.
pub fn set_fault_handler(&mut self, f: fn(&Violation))
Sourcepub fn set_reaction_hook(
&mut self,
hook: Option<(unsafe extern "C" fn(*const ReactionEvent, *mut c_void), *mut c_void)>,
)
pub fn set_reaction_hook( &mut self, hook: Option<(unsafe extern "C" fn(*const ReactionEvent, *mut c_void), *mut c_void)>, )
phase-503 — install the REACTION hook: f(event, ctx) is called on the
spin thread when a supervised route passes its deadline
(route-deadline-runtime), with the row’s reaction id, row index and
mechanism. It also stands in for Self::set_fault_handler on a
DeadlineAction::Fault when no fault handler is set (reaction id 0,
the sched-context slot as the row), so a C or C++ image has a fault
path that does not panic. Replaces any earlier hook; None removes it.
The hook must not block and must not re-enter this executor: it starts the fallback, or hands it to another thread, and returns.
Sourcepub fn set_exec_lock(&mut self, lock: Option<ExecLock>)
pub fn set_exec_lock(&mut self, lock: Option<ExecLock>)
phase-509 P9b (RFC-0108 “As built in P9b”) – install the lock every
FFI entry and every worker pass takes on this executor (None removes
it). Call it while no second thread can reach the executor: a runner
installs it before it spawns a level.
Sourcepub fn set_registering_level(&mut self, level: u8)
pub fn set_registering_level(&mut self, level: u8)
phase-509 P9b item 4 – the callbacks registered from now on belong
to level level: its worker runs them, and no other does. The first
call switches the executor to levels that own their slots by
registration (a level runner sets each level up in turn); until then,
and on every image that never calls it, a worker’s band of dispatch
levels decides.
Sourcepub fn set_level_sched_context(
&mut self,
sc: SchedContext,
) -> Result<SchedContextId, NodeError>
pub fn set_level_sched_context( &mut self, sc: SchedContext, ) -> Result<SchedContextId, NodeError>
Issue 2018 (RFC-0108 As-built) – give the registering level its own
scheduling context: the one its tier’s policy lowers to
(SchedContext::from_tier_policy).
Callbacks registered from now on, until the registering level
changes, bind to it unless a node or callback-group binding names
another. A level’s budget is then charged to that level alone, never
to the executor’s default context, which every level shares.
A Sporadic context is created with the executor’s cooperative
budget (it skips the level’s callbacks once the budget is spent, until
the period refills it). A runner whose kernel enforces the same
budget on the level’s thread hands it to the kernel with
Self::defer_budget_to_kernel, so the budget is enforced exactly
once.
Sourcepub fn defer_budget_to_kernel(&mut self, sc: SchedContextId) -> bool
pub fn defer_budget_to_kernel(&mut self, sc: SchedContextId) -> bool
Issue 2018 – the kernel enforces sc’s budget on the level’s thread
(NuttX SCHED_SPORADIC, Linux SCHED_DEADLINE), so the executor
stops enforcing it: the context keeps its class, budget, period and
deadline, and its callbacks are no longer skipped for want of budget.
One enforcer per level, with one set of numbers. false when sc
carries no cooperative budget (nothing to hand over).
Sourcepub fn enforces_budget(&self, sc: SchedContextId) -> bool
pub fn enforces_budget(&self, sc: SchedContextId) -> bool
Issue 2018 – whether sc’s budget is enforced by this executor
(a cooperative Sporadic context the kernel was not handed).
Sourcepub fn registering_level(&self) -> Option<u8>
pub fn registering_level(&self) -> Option<u8>
Issue 2020 – the level that owns what is registered now, None until
a level runner first calls Self::set_registering_level.
Sourcepub fn pass_ticks_owner(
&self,
band_level: Option<u8>,
owner: Option<u8>,
) -> bool
pub fn pass_ticks_owner( &self, band_level: Option<u8>, owner: Option<u8>, ) -> bool
Issue 2020 – whether a pass ticks a component (or anything else
registered) owned by owner, the level recorded at registration
(None: no level owned it).
band_level is the level of a band pass (Self::spin_band_once),
None for a pass of the executor’s own worker. When the levels own
their slots by registration, a band pass ticks only its level’s, and
the executor’s own worker (the boot level) ticks its own level’s and
any registered before a level was set. Otherwise the executor’s own
worker ticks everything and a band pass nothing, as before levels.
Sourcepub fn set_worker_band(&mut self, band: Band)
pub fn set_worker_band(&mut self, band: Band)
phase-509 P9b item 2 – the band the executor’s own worker runs:
every level by default. With band workers over the same executor
(Self::spin_band_once), the bands must partition the levels, so
each callback has one worker.
phase-509 P9b item 2 – one pass of the executor’s own worker, through
a raw pointer: spin_once, except that with a lock that has a thread
identity installed (super::exec_lock::ExecLock::with_thread_id) the
lock is released around each user callback, so other threads (band
workers, FFI callers) reach the executor while it runs.
§Safety
this points at a live executor. If any other thread can reach it, a
lock is installed and the calling thread holds it exactly once, and
every other thread takes it before touching the executor. No reference
to the executor is live in the caller across this call.
Sourcepub unsafe fn spin_band_once(
this: *mut Executor<'s>,
worker: &mut BandWorker,
timeout: Duration,
) -> SpinOnceResult
pub unsafe fn spin_band_once( this: *mut Executor<'s>, worker: &mut BandWorker, timeout: Duration, ) -> SpinOnceResult
phase-509 P9b item 2 – one pass of a band worker over an executor it
shares: the readiness of worker‘s band, then its callbacks, most
urgent level first, the lock released around each one as in
Self::spin_once_shared. A band pass does not park, drive the
transport or run the executor-wide chores (parameter and lifecycle
services, the monitors’ rules): the executor’s own worker does.
Issue 2020 – it does tick the components its level registered
(Self::pass_ticks_owner), and the executor’s own worker then does
not.
phase-509 P9b item 3 – a worker with a waiter
(super::scheduler::BandWorker::with_waiter) first parks, the lock
released, for at most timeout: until its band’s next timer is due or
an arrival in its band signals it. Without one, timeout is unused
and the pass polls.
§Safety
Sourcepub fn attach_band_worker(&mut self, worker: &BandWorker)
pub fn attach_band_worker(&mut self, worker: &BandWorker)
phase-509 P9b item 3 – link worker’s waiter into this executor’s
arrival routing (RFC-0108 D7): an arrival attributed to a slot of its
band then wakes it alone, an unattributed one wakes it with the rest.
A worker with no waiter polls; this is then a no-op, as on a build
with no arrival path. Attach each waiter to one executor, once.
Sourcepub fn route_next_due_us(&self) -> Option<u64>
pub fn route_next_due_us(&self) -> Option<u64>
phase-503 — microseconds from now to the earliest deadline of a pending
supervised route (0 when one is due), or None when none is waiting or
the executor has no clock. A port that supervises with its own timer
(MECHANISM_TIMER) re-arms it from this after each spin.
Sourcepub fn route_watch(&self) -> Option<RouteWatch>
pub fn route_watch(&self) -> Option<RouteWatch>
phase-503 W7 – a RouteWatch over this
executor’s supervised routes, which a port’s timer or reaction thread
reads from outside the spin thread: at its expiry it sees a route the
running spin has already answered, without waiting for the spin to
return. None without a monitor table or a clock. Take it after
set_monitor_table and any clock change.
Sourcepub fn register_wake_source(
&mut self,
next: unsafe extern "C" fn(*mut c_void) -> u64,
ctx: *mut c_void,
) -> Result<WakeSourceId, NodeError>
pub fn register_wake_source( &mut self, next: unsafe extern "C" fn(*mut c_void) -> u64, ctx: *mut c_void, ) -> Result<WakeSourceId, NodeError>
phase-436 W6 — contribute a deadline source.
The core never names an RTOS: a port registers k_poll, epoll, a
queue set or a device ISR here, and the executor only ever asks “when
will you next need me?”.
Refuses past capacity rather than dropping silently. A source that was quietly discarded would leave the executor sleeping past a deadline it had been told about, which is the failure this whole phase exists to remove.
Sourcepub fn level(&self) -> u8
pub fn level(&self) -> u8
phase-509 P7 (RFC-0108 D8.1) – the level this executor runs, 0 the
most urgent. An executor’s runner sets it (Self::set_level); one it
never set is level 0, which is every executor of a one-level image.
It matches the generated NROS_ENTRY_LEVEL_OF_<node> of the nodes the
executor hosts.
Sourcepub fn set_level(&mut self, level: u8) -> bool
pub fn set_level(&mut self, level: u8) -> bool
phase-509 P7 – set the level this executor runs; its spins check in
as that level. false, and nothing changed, at or past
MAX_LEVELS.
Sourcepub fn has_park_primitive(&self) -> bool
pub fn has_park_primitive(&self) -> bool
Whether a platform park primitive is installed.
Sourcepub fn last_park(&self) -> (u64, WakeSourceId)
pub fn last_park(&self) -> (u64, WakeSourceId)
phase-436 — the bound the last park used, and which source won it.
Sourcepub fn last_park_achieved_us(&self) -> u64
pub fn last_park_achieved_us(&self) -> u64
phase-436 W2 — the park the platform was actually asked for, after rounding the request up to an expressible value.
Differs from last_park().0 exactly when the request did not land on
the platform’s grid. Both are reported because a cadence that cannot be
waited in must not be presented as if it were met.
Sourcepub fn release_jitter_granularity_us(&self) -> u64
pub fn release_jitter_granularity_us(&self) -> u64
phase-436 W3 (issue 1194) — the granularity the release-jitter measurement was actually made at, in microseconds.
The jitter statistic is reported in microseconds, but the mechanism that PACES the loop decides how fine it can honestly be, and there are two mechanisms:
- No declared cadence — the loop is paced by the blocking wait itself, so it cannot come round more precisely than one park granule. Reporting µs there is precision the wait cannot deliver.
- A declared cadence (
set_spin_nominal_us) — the caller paces itself, as nros-cpp’s tiers do withplatform_sleep_usbetween spins. That sleep is finer than the wait, so the measurement really is good to a microsecond and coarsening it would understate it.
Reported rather than applied: this does not change what the monitor judges, because silently coarsening a bound could mask real lateness. It says what the number is worth.
Sourcepub fn park_granularity_us(&self) -> u64
pub fn park_granularity_us(&self) -> u64
The finest park this build can actually express, in microseconds.
Comes from the INSTALLED primitive, because only the port knows. The exported ABI signature is not the limit in either direction (issue 1242):
- an RTOS tick is a COARSER limit — ThreadX pins
TX_TIMER_TICKS_PER_SECOND = 100, a 10 ms tick, so a 1 ms park is not achievable there however the ABI is spelled; - a timespec primitive is a FINER one — POSIX’s
sem_timedwaitandpthread_cond_timedwaitare nanosecond-native, and only theuint32_t timeout_mssignature floors them at a millisecond.
This was a hardcoded 1_000 justified by “every nros_platform_wake_*
port takes uint32_t timeout_ms”. That was right for FreeRTOS by
coincidence and wrong for the other two, and it made the W3 jitter
report over-claim on ThreadX — the failure W3 exists to prevent,
reproduced one layer below W3 in its own dependency.
With nothing installed the fallback is 1 ms, which IS the honest floor
of the wake_wait_ms ABI every port exports today.
The declared granularity counts only while the park is USABLE. An
executor whose backend has no live wake (XRCE, or a session whose
callback is gone) never parks: spin_once waits in the transport’s
own receive instead, whose timeout is whole milliseconds. Claiming a
POSIX park’s 1 us there over-claimed the jitter report, and worse,
rounded the bound to microseconds that the millisecond wait then
truncated, so a sub-millisecond deadline became a non-blocking receive
and the loop spun hot until it passed.
Sourcepub fn entry_spin_budget(&self) -> Duration
pub fn entry_spin_budget(&self) -> Duration
phase-521 W6 (RFC-0113 D2) — the budget an entry’s spin loop passes to
spin_once.
An entry loop is loop { spin_once(budget) }, and each park already
ends at the next deadline the executor knows (a timer, a backend’s next
internal event, a platform source) or on the wake object. So the
budget is not a cadence: it bounds only a park nothing else bounds.
Every entry used to pass 10 ms, which woke an idle tier 100 times a
second whatever its park could do.
ENTRY_IDLE_BUDGET when every session can end the wait itself: it
states Notify with its wake callback live, or Waitable (whose
handle the port multiplexes with the wake object). A cancel and a
hosted termination post the same wake object. Otherwise
ENTRY_POLL_BUDGET: what is left can wait on the wake object without
its data posting it (a wake fan-out with no free member, more waitable
sessions than one park multiplexes, a port with no wake object), so the
short budget bounds how long its data waits. A session with no
readiness form never gets here: it is refused at open (RFC-0113 D5).
An enrolled component whose tick does work (it polls a service reply
or completes action goals once per spin) also keeps the slice, so its
tick rate does not change under it.
Sourcepub fn set_report_violations(&mut self, enabled: bool)
pub fn set_report_violations(&mut self, enabled: bool)
Issue #514 — whether the executor logs each violation as it is detected (the default).
Turn this off in an application that reports violations its own
way via Self::drain_violations; the ring is unaffected
either way.
Sourcepub fn set_violation_drain_report(&mut self, enabled: bool)
pub fn set_violation_drain_report(&mut self, enabled: bool)
phase-474 I1 – the drain-and-report hook: at the end of every spin,
drain the ring and log each entry with its sequence number and the
total / dropped counters (contract violation #N: <rule> <fqn> measured=.. declared=.. (total=.. dropped=..)).
While on it REPLACES the log line at detection, so one verdict is one
line, and an application that wanted to drain the ring itself sees an
empty one. The generated C/C++ entries turn it on when the image sets
CONFIG_NROS_VIOLATION_DRAIN_REPORT (off by default). The trace marker
(callback_trace::MARKER_VIOLATION) and the SWD record do not depend
on it: both are written at detection.
Sourcepub fn violations_dropped(&self) -> u32
pub fn violations_dropped(&self) -> u32
Issue #514 / phase-474 I1 – violations evicted from the full ring before any drain took them.
The ring keeps the LATEST MAX_VIOLATIONS, so non-zero means the
OLDEST verdicts were lost, never the newest. (Before phase-474 a full
ring refused the push, which kept the first verdicts since boot and
lost every later one.)
Sourcepub fn violations_total(&self) -> u32
pub fn violations_total(&self) -> u32
phase-474 I1 – contract violations this executor stored since boot
(wrapping). The newest entry in the ring is number violations_total();
total - dropped - pending is how many drains have taken.
Sourcepub fn drain_violations(&mut self, f: impl FnMut(&Violation))
pub fn drain_violations(&mut self, f: impl FnMut(&Violation))
RFC-0052 W3b.4 – drain pending contract violations, OLDEST first. Draining clears the ring. Call between spins, never from a callback.
Sourcepub fn drain_violations_numbered(&mut self, f: impl FnMut(u32, &Violation))
pub fn drain_violations_numbered(&mut self, f: impl FnMut(u32, &Violation))
Self::drain_violations with each entry’s sequence number (1-based,
per executor; the same number the drain-and-report line and the trace
marker carry), so a reader can tell a gap from a quiet interval.
Sourcepub fn set_monitor_arming(&mut self, on_call: bool, grace_ms: u32)
pub fn set_monitor_arming(&mut self, on_call: bool, grace_ms: u32)
phase-474 I2 – choose when this executor’s contract monitors arm,
overriding the build default (NROS_MONITOR_ARM_ON_CALL,
NROS_MONITOR_ARM_GRACE_MS). Call before the first spin (an
arm_monitors made before it is undone by this); after the first spin
an executor that already armed stays armed.
on_call: wait for Self::arm_monitors or
super::monitor::request_monitor_arming (rclc_monitors_arm()).
grace_ms: arm anyway that long after the first spin (0 = never by
deadline; with on_call == false, 0 means “at the first spin”).
Sourcepub fn arm_monitors(&mut self)
pub fn arm_monitors(&mut self)
phase-474 I2 – the application says its start-up is over: this executor’s monitors start judging now. Idempotent.
Judgement restarts at arming: every rate/age/silence window reopens, a latency or age measured before arming is discarded, the release- jitter statistics are cleared (so the figure after RUN is RUN’s), and a stack low-water mark already past the declared minimum is reported again, because stack depth reached at start-up is a real fault, not start-up noise.
Sourcepub fn monitors_armed(&self) -> bool
pub fn monitors_armed(&self) -> bool
phase-474 I2 – whether this executor’s monitors are armed.
Sourcepub fn violations_suppressed_before_arm(&self) -> u32
pub fn violations_suppressed_before_arm(&self) -> u32
phase-474 I2 – verdicts reached before the monitors armed: counted, never stored, logged, sunk or traced.
Sourcepub unsafe fn set_violation_sink(
&mut self,
sink: Option<(unsafe fn(*mut c_void, &[Violation]) -> usize, *mut c_void)>,
)
pub unsafe fn set_violation_sink( &mut self, sink: Option<(unsafe fn(*mut c_void, &[Violation]) -> usize, *mut c_void)>, )
Issue 1635 – hand every stored violation to sink, beside the log line
(issue 0514’s floor) and the ring push.
phase-474 I7 – not at detection any more: the verdict is queued and
leaves from Executor::flush_violation_reports at the end of a spin with
slack, so the report never costs the dispatch it judges.
Before this nothing in a generated image drained the ring, so the log
line was the only output and /diagnostics stayed silent on every C
and C++ road. A report point the RUNTIME owns reaches every road at
once — every board’s spin loop goes through the executor — where one
per entry template would have to be rebuilt for each board runner. The
ring is still filled, so an application (or a fixture) that drains it
with Self::drain_violations sees exactly what it did before.
None removes the sink.
§Safety
ctx must stay valid for every call of the sink until it is replaced
or this executor is dropped (see super::monitor::ViolationSink).
Sourcepub fn violation_report_cost_us(&self) -> (u32, u32, usize, u32)
pub fn violation_report_cost_us(&self) -> (u32, u32, usize, u32)
phase-474 I7 – what handing verdicts to the violation sink has cost
this executor: (last_us, max_us, pending, overflowed). overflowed
counts verdicts evicted from the report queue by newer ones before a
slot came (they are still in the ring and the SWD record).
Sourcepub fn set_spin_nominal_us(&mut self, us: u64)
pub fn set_spin_nominal_us(&mut self, us: u64)
Issue #505 — report activations dropped by
TimerOverrunPolicy::Skip
since the last check.
Unlike the rate/age/latency rules this needs no baked spec table:
every periodic timer counts its own overruns, and a dropped
activation is a contract failure for any declared period. It runs
on the same tick so violations land in the same ring the entry
glue drains.
Declare the minimum stack headroom this executor’s thread must keep,
in bytes. 0 (the default) disables the stack-headroom-runtime
rule.
Set by the entry that spawned the thread, because it is the only
party that knows what stack it handed over: the executor never sees
stack_bytes, and no portable query returns a task’s total stack, so
neither an absolute floor nor a percentage can be inferred here.
Declare how often this executor’s loop intends to come round, in
microseconds. 0 (the default) falls back to the spin_once timeout.
Needed because a driver’s pacing and its blocking bound are separate
numbers. nros-cpp’s tier loops sleep spin_period_us between spins but
pass a fixed 10 ms timeout, so the release-jitter rule judged every
tier against 10 ms regardless of what the contract declared – too lax
for a 1 ms tier and far too strict for a 30 ms one.
This also says the CALLER paces the loop (a sleep finer than the
park), so the measurement is reported at 1 us. A loop paced by the
spin_once wait itself declares with
Self::set_spin_nominal_wait_paced_us instead.
Sourcepub fn set_spin_nominal_wait_paced_us(&mut self, us: u64)
pub fn set_spin_nominal_wait_paced_us(&mut self, us: u64)
phase-436 A2b – declare the loop’s cadence, in microseconds, for a
loop paced by the spin_once wait itself. 0 withdraws it.
Self::set_spin_nominal_us couples two facts: the period to judge
against, and that the caller paces the loop with its own sleep. A loop
like for (;;) spin_once(period_ms) has the first without the second.
Declaring through the coupled setter would report its jitter at 1 us
when the park it waits on is tick-granular (issue 1194’s failure); not
declaring at all makes the timeout the period, which is wrong whenever
the timeout is not the period (a cadence that is not whole
milliseconds, or one raised to a floor).
Here the cadence is the period of the release grid that a
spin_once(timeout) loop already runs on, and the timeout stays a
bound on each wait. The jitter is judged at grid releases and reported
at the park granularity.
pub fn set_min_stack_headroom_bytes(&mut self, bytes: usize)
Sourcepub fn stack_headroom_blind(&self) -> bool
pub fn stack_headroom_blind(&self) -> bool
phase-436 E3 — whether stack-headroom-runtime is armed on a port that
cannot measure, and so will never report a violation.
true means the rule’s silence is not evidence of headroom. Zephyr
answers only with CONFIG_INIT_STACKS && CONFIG_THREAD_STACK_INFO;
POSIX never does.
Sourcepub fn set_default_sched_context(&mut self, sc: SchedContext)
pub fn set_default_sched_context(&mut self, sc: SchedContext)
RFC-0052 / phase-296 W3a — replace the DEFAULT scheduling context (slot 0, the SC every unbound callback dispatches through).
The run_tiers model runs one Executor per tier, so a tier-wide
scheduling policy ([tasks.<t>] class/budget_us/period_us and the
TT window) is exactly “this executor’s default SC”. Boards call
this once, before entity creation; explicit per-handle/per-group
bindings still take precedence (they never resolve to slot 0).
Sporadic-class SCs get the same sibling SporadicState the
create_sched_context path builds, so budget refill/exhaustion
applies to the default queue too.
Sourcepub fn bind_handle_to_sched_context(
&mut self,
handle: HandleId,
sc_id: SchedContextId,
) -> Result<(), NodeError>
pub fn bind_handle_to_sched_context( &mut self, handle: HandleId, sc_id: SchedContextId, ) -> Result<(), NodeError>
Bind a registered callback to a scheduling context. The next
spin_once cycle dispatches the callback through that SC’s
queue (FIFO bitmap or EDF heap). Phase 110.B.
Sourcepub fn register_time_triggered_dispatcher(&mut self, major_frame_us: u32)
pub fn register_time_triggered_dispatcher(&mut self, major_frame_us: u32)
Phase 110.G — enable time-triggered dispatch by setting the
executor’s major-frame length. Once set, every spin_once
cycle gates dispatch through each entry’s bound SC’s
tt_window_offset_us / tt_window_duration_us fields:
dispatch only fires when the current monotonic time falls
inside the window [off, off + duration) mod major_frame.
major_frame_us = 0 disables the TT gate (default state).
Setting a non-zero major frame after callbacks are already
registered is allowed — TT gates take effect on the next
spin_once cycle.
Sourcepub fn apply_time_triggered_schedule<const N: usize>(
&mut self,
schedule: &TimeTriggeredSchedule<N>,
) -> Result<[SchedContextId; N], TimeTriggeredScheduleError>
pub fn apply_time_triggered_schedule<const N: usize>( &mut self, schedule: &TimeTriggeredSchedule<N>, ) -> Result<[SchedContextId; N], TimeTriggeredScheduleError>
Phase 110.G — apply a declarative cyclic schedule.
One-shot helper that wraps the underlying primitives:
validates the schedule (major_frame > 0, no overlapping
windows, every window fits inside the major frame), sets the
executor’s major-frame length, then materialises one
SchedContext per window with class = TimeTriggered +
the window’s offset / duration. Returns the per-window
SchedContextId array so callers can immediately
bind_handle_to_sched_context(handle, sc_id) for their
subscription / timer handles.
N is the schedule’s declared maximum window count;
schedule.window_count gates how many SCs are actually
created. Unused trailing slots return
SchedContextId::default() (sentinel — callers must respect
window_count).
Sourcepub fn register_sporadic_timer(
&mut self,
sc_id: SchedContextId,
timer: OpaqueTimerHandle,
) -> Result<Arc<AtomicSporadicState>, NodeError>
pub fn register_sporadic_timer( &mut self, sc_id: SchedContextId, timer: OpaqueTimerHandle, ) -> Result<Arc<AtomicSporadicState>, NodeError>
Phase 110.E.b — register an ISR-driven refill timer for an
already-created Sporadic SC. The caller invokes their
platform’s PlatformTimer::create_periodic with the returned
Arc<AtomicSporadicState> as user_data and the
atomic_sporadic_refill_thunk as the callback, then hands
the resulting platform handle to this method via
OpaqueTimerHandle::new(handle, destroy_fn).
The Executor stores both the Arc and the handle so Drop can
clean them up. Calling this on a non-Sporadic SC returns
Err(InvalidSchedContextBinding).
Sourcepub fn sched_context(&self, sc_id: SchedContextId) -> Option<&SchedContext>
pub fn sched_context(&self, sc_id: SchedContextId) -> Option<&SchedContext>
Inspect a registered scheduling context. Phase 110.B.
Sourcepub fn node_builder<'a, 'cfg>(
&'a mut self,
name: &'cfg str,
) -> NodeBuilder<'a, 'cfg, 's>
pub fn node_builder<'a, 'cfg>( &'a mut self, name: &'cfg str, ) -> NodeBuilder<'a, 'cfg, 's>
Phase 104.C.2 — start a rclcpp-style Node builder for this
Executor. The returned NodeBuilder
is chainable:
let id = exec.node_builder("ingress")
.rmw("zenoh")
.locator("tcp/127.0.0.1:7447")
.sched(my_sc_id)
.build()?;In Phase 104.C.2 the Node table is storage-only — all registered Nodes share the Executor’s primary session. Per- Node session binding (the bridge feature) lands in Phase 104.C.3 when the session cache is wired.
Sourcepub fn nodes(&self) -> &[NodeRecord]
pub fn nodes(&self) -> &[NodeRecord]
Return the Node table — Phase 104.C.2 read accessor.
Sourcepub fn node(&self, id: NodeId) -> Option<&NodeRecord>
pub fn node(&self, id: NodeId) -> Option<&NodeRecord>
Borrow a Node’s metadata by id, returning None if the id
is out of range.
pub fn node_mut(&mut self, id: NodeId) -> Node<'_, 's>
Sourcepub fn set_node_qos_overrides(
&mut self,
node_id: NodeId,
overrides: &'static [(&'static str, u8, u8, u32)],
)
pub fn set_node_qos_overrides( &mut self, node_id: NodeId, overrides: &'static [(&'static str, u8, u8, u32)], )
Issue #52 — install the baked QoS-override table on one node. Every
entity created on it AFTERWARDS folds the matching (topic, role)
entries into its QoS, before the backend-compat check — so an override
the active RMW cannot honour still errors loudly rather than silently
downgrading.
Called by the generated entry (nros::main! → the register seam) right
after the node is created and before the component declares entities.
Unknown node_id is a no-op.
Sourcepub fn create_publisher_on<M>(
&mut self,
node_id: NodeId,
topic_name: &str,
qos: QoSProfile,
) -> Result<EmbeddedPublisher<M>, NodeError>where
M: MessageForRmw,
pub fn create_publisher_on<M>(
&mut self,
node_id: NodeId,
topic_name: &str,
qos: QoSProfile,
) -> Result<EmbeddedPublisher<M>, NodeError>where
M: MessageForRmw,
Phase 189.M1 — create a typed publisher bound to a node’s session.
Backs node.publisher(t).typed::<M>().build() on the
executor-borrowing Node; the returned
handle is owned and outlives the Node.
Sourcepub fn create_publisher_raw_on(
&mut self,
node_id: NodeId,
topic_name: &str,
type_name: &str,
type_hash: &str,
qos: QoSProfile,
) -> Result<EmbeddedRawPublisher, NodeError>
pub fn create_publisher_raw_on( &mut self, node_id: NodeId, topic_name: &str, type_name: &str, type_hash: &str, qos: QoSProfile, ) -> Result<EmbeddedRawPublisher, NodeError>
Phase 189.M1 — create a generic (type-erased) publisher bound to a
node’s session. Backs node.publisher(t).generic(ty, hash).build();
the bridge re-publishes through this handle on the dest session.
Sourcepub fn with_node_try<R>(
&mut self,
id: NodeId,
f: impl FnOnce(&mut NodeHandle<'_>) -> Result<R, NodeError>,
) -> Result<R, NodeError>
pub fn with_node_try<R>( &mut self, id: NodeId, f: impl FnOnce(&mut NodeHandle<'_>) -> Result<R, NodeError>, ) -> Result<R, NodeError>
Phase 104.C.3.2 — scoped Node-handle access. The closure
receives a NodeHandle bound to the
requested NodeId’s
session + identity. Use the standard Node::create_publisher,
create_subscription, etc. APIs inside.
rclcpp-aligned bridge pattern:
let node_in = exec.node_builder("ingress").rmw("zenoh").build()?;
let node_out = exec.node_builder("egress").rmw("xrce").build()?;
let pub_out = exec.with_node(node_out, |n| {
n.create_publisher::<Int32>("/fwd")
})??;
exec.with_node(node_in, |n| {
n.create_subscription_buffered::<Int32, _, 1024>(
"/src", qos(), move |m| { let _ = pub_out.publish(m); }
)
})??;The closure can return any type; double-? unwraps the
outer Result<R, NodeError> from with_node and the inner
result returned by the closure.
Phase 104.C.3.3.d — flat-Result variant of
with_node. When the closure already
returns Result<R, NodeError>, this avoids the double-?:
// Without `with_node_try`:
let pub_ = exec.with_node(id, |n| n.create_publisher(...))??;
// With `with_node_try`:
let pub_ = exec.with_node_try(id, |n| n.create_publisher(...))?;pub fn with_node<R>( &mut self, id: NodeId, f: impl FnOnce(&mut NodeHandle<'_>) -> R, ) -> Result<R, NodeError>
Sourcepub fn node_id_by_name(&self, name: &str, namespace: &str) -> Option<NodeId>
pub fn node_id_by_name(&self, name: &str, namespace: &str) -> Option<NodeId>
Find a registered executor node by final name and namespace.
Sourcepub fn create_node<'o>(
&mut self,
options: impl IntoNodeOptions<'o>,
) -> Result<Node<'_, 's>, NodeError>
pub fn create_node<'o>( &mut self, options: impl IntoNodeOptions<'o>, ) -> Result<Node<'_, 's>, NodeError>
Create a node on this executor.
Registers the node in the executor’s table, deduplicating on
(name, namespace) — phase-376 W5/B1.
§This is rclrs::Executor::create_node
phase-427 W10 (RFC-0089 “Context and init, settled”). Same name, same
place in the sequence — Context → executor → node — so a ported rclrs
main lands here unchanged:
let context = nros::Context::default_from_env()?;
let mut executor = context.create_executor()?;
let mut node = executor.create_node("talker")?;Two differences from upstream, both structural and both named in the
ledger row (rust:Executor::create_node): ours takes &mut self,
because the node is registered in the executor’s compile-time-sized
table rather than allocated, and it hands back a borrowing
NodeHandle rather than an Arc<Node>, because there is no
allocator. Neither is silent — the compiler names both.
The name comes from HERE, not from
ExecutorConfig::node_name, whose
builder spelling is deprecated for exactly this reason: one executor
holds several named nodes (RFC-0047), so a name on the session config
can only ever describe the first of them.
Until 2026-08-24 this path registered NOTHING. It built a NodeHandle
and returned it, so node_id_by_name could not find a node the caller
had just created, and two calls with one name handed out two handles the
executor had never heard of. create_node_on_with_domain had the dedup
(phase-267 added it there when N bridge endpoints overflowed the table);
the plain path never got it.
That is a prerequisite for the create_node vtable slot, whose contract
is that the runtime calls it ONCE per distinct (name, namespace):
without a registry to check, the runtime would call it once per
create_node and every backend would need its own dedup — which is the
registry the slot exists to delete (zenoh’s ensure_node_liveliness
linear-scans per_node_liveliness for exactly this reason).
The table is bounded by MAX_NODES (NROS_EXECUTOR_MAX_NODES, default
4), so a caller creating a fifth DISTINCT node now gets
NodeError::NodeTableFull where it previously got a handle. That is the
bound doing its job: a node the executor does not know about cannot
carry a sched context, a QoS override, or a graph identity. Repeated
calls with the SAME name are free.
phase-515 R2 – takes rclrs’s impl IntoNodeOptions: a bare name
(create_node("talker")) or a NodeOptions
(create_node("talker".namespace("/robot"))). Here the options’
DEFAULTS mean “the executor’s”: namespace / places the node in the
executor’s own namespace and domain 0 on its own session, as a bare
name always did. A non-default value is applied, so an explicit /
under an executor configured with another namespace, or an explicit
domain 0 under another domain, is not expressible here; open a node on
another domain with create_node_on_with_domain.
Sourcepub fn create_node_on(
&mut self,
name: &str,
rmw: &str,
) -> Result<Node<'_, 's>, NodeError>
pub fn create_node_on( &mut self, name: &str, rmw: &str, ) -> Result<Node<'_, 's>, NodeError>
Phase 128.F.2 — bridge-mode node factory. Registers a Node
bound to the named RMW backend by opening (or reusing) an
extra session via node_builder().rmw(rmw).build(), then
returns a NodeHandle borrowing that
session. Use when the
binary intentionally links more than one backend and a Node
must speak a specific one.
The single-backend common case should keep using
create_node — this entry costs an
extra session lookup and serves no purpose when only one
backend is registered.
Sourcepub fn create_node_on_with_domain(
&mut self,
name: &str,
rmw: &str,
domain_id: Option<u32>,
locator: Option<&str>,
) -> Result<Node<'_, 's>, NodeError>
pub fn create_node_on_with_domain( &mut self, name: &str, rmw: &str, domain_id: Option<u32>, locator: Option<&str>, ) -> Result<Node<'_, 's>, NodeError>
Like create_node_on but pins the extra
session’s domain id. Required for a multi-domain config-driven bridge:
an extra RMW session’s participant domain follows the node builder’s
domain_id (resolve_session_slot → domain_id.unwrap_or(0)), NOT the
SessionSpec’s — so without this an egress on a non-zero domain silently
opens on domain 0 and never matches its receiver (phase-267 issue 0109).
None domain preserves the legacy domain-0 default. locator pins the
extra session’s address — REQUIRED for an agent-based backend (xrce: the
Micro-XRCE-DDS Agent addr) whose session can’t be opened locator-less;
None keeps the rmw-default (cyclonedds is domain-discovered, no locator).
Sourcepub fn close(&mut self) -> Result<(), NodeError>
pub fn close(&mut self) -> Result<(), NodeError>
Close the underlying session, running the shutdown hooks around it.
Issue 0790. The order is the feature:
- every registered PRE-shutdown hook, while the session is still open and every entity still works — this is where a node publishes a final state, answers a last request, parks an actuator or releases a bus;
- the session close;
- every registered ON-shutdown hook.
A hook runs EXACTLY ONCE: each phase table is emptied before its first
hook is invoked, so a second close() — or the Drop sweep after one
— finds nothing left to run. Step 2 runs even
if a pre-shutdown hook was registered and step 3 even if the close
failed — a hook cannot strand the session, and a dead session must not
strand the hooks.
§This is a CLEAN-STOP facility and nothing more
A watchdog reset, a hard fault or a panic does not come through here, so these hooks do not run then. Nothing in a fixed static table can promise otherwise, and an API that implied it would be worse than none: hardware that must be safe across an abnormal stop needs a hardware answer (a pull-down, a watchdog-driven output disable), not a callback.
Sourcepub fn shutdown_callback_count(&self, phase: ShutdownPhase) -> usize
pub fn shutdown_callback_count(&self, phase: ShutdownPhase) -> usize
How many hooks are currently registered for phase. Diagnostic /
test surface, and what a “did my registration land?” assertion reads.
Sourcepub fn has_ticking_component(&self) -> bool
pub fn has_ticking_component(&self) -> bool
phase-521 W6 — whether an enrolled component polls from its tick.
Sourcepub fn session(&self) -> &CffiSession
pub fn session(&self) -> &CffiSession
Get a reference to the underlying session.
Sourcepub fn session_mut(&mut self) -> &mut CffiSession
pub fn session_mut(&mut self) -> &mut CffiSession
Get a mutable reference to the underlying session.
Sourcepub fn ping(&mut self, timeout_ms: i32) -> Result<(), NodeError>
pub fn ping(&mut self, timeout_ms: i32) -> Result<(), NodeError>
Phase 124.F.3 — session-level connectivity probe. Wire-level round-trip “is the peer / agent / router still reachable?” — cheaper than the service-availability probe (no discovery state required).
Returns Ok(()) on reply within timeout_ms,
Err(NodeError::Transport(Timeout)) on no reply,
Err(NodeError::Transport(Unsupported)) when the active
backend can’t probe.
Mirrors micro-ROS’s rmw_uros_ping_agent. Useful for
reconnect-on-link-loss patterns: bare-metal code can call
ping(100) periodically and tear down / re-open the session
on timeout.
Sourcepub fn get_node_names(
&mut self,
visit: &mut dyn FnMut(&str, &str, Option<&str>) -> bool,
) -> Result<(), NodeError>
pub fn get_node_names( &mut self, visit: &mut dyn FnMut(&str, &str, Option<&str>) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — every node on the graph, with its namespace.
visit(name, namespace, enclave) is called once per node and returns
false to stop early. A VISITOR rather than a returned collection
because there is no allocator at this layer and the graph has no bound
the caller can know; peak extra memory is one entry. Every string is
BORROWED for the duration of the call.
enclave is None where the backend does not track one, which is what
lets one call answer both rmw_get_node_names and
rmw_get_node_names_with_enclaves.
This reports what has already been DISCOVERED, and never blocks. The
first call after startup legitimately sees a partial graph — the backend
keeps a standing query fed by spin, so the view fills in over
successive calls. Code that waits for a peer should poll, not call once
and conclude. An empty result is “nobody seen yet”, never “nobody
exists”.
Err(Transport(Unsupported)) from a backend with no graph — distinct
from an empty graph, deliberately.
Sourcepub fn get_topic_names_and_types(
&mut self,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), NodeError>
pub fn get_topic_names_and_types( &mut self, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — every topic on the graph, with the types on it.
visit(topic_name, types) is called once per distinct TOPIC — a topic
carrying two types is one call with two entries, not two calls. types
may legitimately be empty on a partially discovered graph: reporting the
name without a type beats dropping it.
Same discovery caveat as Self::get_node_names.
Sourcepub fn get_service_names_and_types(
&mut self,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), NodeError>
pub fn get_service_names_and_types( &mut self, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — every service on the graph, with its types.
As Self::get_topic_names_and_types, over servers and clients.
Sourcepub fn count_publishers(&mut self, topic_name: &str) -> Result<usize, NodeError>
pub fn count_publishers(&mut self, topic_name: &str) -> Result<usize, NodeError>
phase-381 W4 — how many publishers are visible on topic_name.
topic_name is a ROS name ("/chatter"). A count reflects what has
been DISCOVERED, so it can be low right after startup and is never a
proof of absence — see Self::get_node_names.
Sourcepub fn count_subscribers(
&mut self,
topic_name: &str,
) -> Result<usize, NodeError>
pub fn count_subscribers( &mut self, topic_name: &str, ) -> Result<usize, NodeError>
phase-381 W4 — how many subscribers are visible on topic_name.
See Self::count_publishers for the caveats.
Sourcepub fn count_clients(&mut self, service_name: &str) -> Result<usize, NodeError>
pub fn count_clients(&mut self, service_name: &str) -> Result<usize, NodeError>
Issue 1978 — how many service CLIENTS are visible on service_name,
upstream (Jazzy) rcl_count_clients. service_name is a ROS name
("/add_two_ints"); same discovery caveats as
Self::count_publishers, and a backend with no graph answers
Unsupported, never 0.
Sourcepub fn count_services(&mut self, service_name: &str) -> Result<usize, NodeError>
pub fn count_services(&mut self, service_name: &str) -> Result<usize, NodeError>
Issue 1978 — how many service SERVERS are visible on service_name,
upstream (Jazzy) rcl_count_services. See Self::count_clients.
Sourcepub fn get_publisher_names_and_types_by_node(
&mut self,
node_name: &str,
node_namespace: &str,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), NodeError>
pub fn get_publisher_names_and_types_by_node( &mut self, node_name: &str, node_namespace: &str, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — what one named node PUBLISHES, with the types.
visit(topic_name, types) per distinct topic. A node the graph has not
discovered yields no visits, which is not an error — see
Self::get_node_names for why an empty answer means “not seen yet”.
Sourcepub fn get_subscription_names_and_types_by_node(
&mut self,
node_name: &str,
node_namespace: &str,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), NodeError>
pub fn get_subscription_names_and_types_by_node( &mut self, node_name: &str, node_namespace: &str, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — what one named node SUBSCRIBES to, with the types.
subscription, not subscriber — this is rclrs’s spelling
(get_subscription_names_and_types_by_node), and the Rust surface takes
its vocabulary from rclrs so a user porting Rust ROS 2 code types what
they already know. The C surface says subscriber because rcl does, and
the vtable slot says subscriber because upstream rmw does. Three
layers, three upstreams, one word each — not drift. Issue 0788 owns the
wider verb sweep.
Sourcepub fn get_service_names_and_types_by_node(
&mut self,
node_name: &str,
node_namespace: &str,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), NodeError>
pub fn get_service_names_and_types_by_node( &mut self, node_name: &str, node_namespace: &str, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — what services one named node SERVES, with the types.
Sourcepub fn get_client_names_and_types_by_node(
&mut self,
node_name: &str,
node_namespace: &str,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), NodeError>
pub fn get_client_names_and_types_by_node( &mut self, node_name: &str, node_namespace: &str, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — what services one named node CALLS, with the types.
Sourcepub fn get_publishers_info_by_topic(
&mut self,
topic_name: &str,
visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool,
) -> Result<(), NodeError>
pub fn get_publishers_info_by_topic( &mut self, topic_name: &str, visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — the publishers on topic_name, one visit each.
Each GraphEndpointInfo BORROWS its strings for the duration of the
visit; copy anything kept.
It carries no QoS. The granted profile is what would answer “why is nothing arriving”, and no backend can read one back yet — reporting the remote’s DECLARED profile instead would be a confident wrong answer, so the field is absent rather than misleading.
Sourcepub fn get_subscriptions_info_by_topic(
&mut self,
topic_name: &str,
visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool,
) -> Result<(), NodeError>
pub fn get_subscriptions_info_by_topic( &mut self, topic_name: &str, visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — the subscriptions on topic_name, one visit each.
See Self::get_publishers_info_by_topic.
Sourcepub fn wait_for_publishers(
&mut self,
topic_name: &str,
count: usize,
timeout: Duration,
) -> Result<bool, NodeError>
pub fn wait_for_publishers( &mut self, topic_name: &str, count: usize, timeout: Duration, ) -> Result<bool, NodeError>
phase-444 — block until at least count publishers are visible on
topic_name, or timeout elapses. Upstream’s rcl_wait_for_publishers.
Returns Ok(true) when the graph showed at least count inside the
budget, Ok(false) on timeout. The startup-ordering primitive one
entity kind over from EmbeddedServiceClient::wait_for_service,
and built the same way: a spin on a discovery read, so other
subscriptions and timers keep making progress while it waits.
This is the one blocking member of the graph family, and it does not
weaken the family’s contract — it composes it. The query underneath
(count_publishers) still reports only what
has been DISCOVERED and still never blocks; what waits is this loop,
and it waits by POLLING that query. RFC-0021 is the rule that governs
it, and that rule does not forbid blocking: it requires a blocking
helper to take an executor and drive it, which is what makes the
timeout reliable on a single-threaded transport and what makes the
reentrancy visible at the call site.
Unsupported is propagated IMMEDIATELY, not waited out — and this
is the one place it deliberately differs from wait_for_service, which
treats a backend that cannot answer as “keep waiting” and reports
Ok(false) at the deadline (issue 1087). That is right for a service
probe, where not-yet-answerable is a transient. It is wrong here: a
backend with no graph (XRCE) can never answer, so spending the budget
to return Ok(false) would report “no publishers appeared” for a
backend that cannot see publishers at all — collapsing “cannot tell
you” into “not there”, which is exactly the distinction RFC-0036 exists
to keep and which every other method in this family promises to keep.
count == 0 is satisfied immediately and without asking the backend —
including on a backend with no graph, because “at least zero” is true
of every graph and needs no discovery to establish.
Sourcepub fn wait_for_subscribers(
&mut self,
topic_name: &str,
count: usize,
timeout: Duration,
) -> Result<bool, NodeError>
pub fn wait_for_subscribers( &mut self, topic_name: &str, count: usize, timeout: Duration, ) -> Result<bool, NodeError>
phase-444 — block until at least count subscribers are visible on
topic_name. Upstream’s rcl_wait_for_subscribers. See
wait_for_publishers for the contract,
and for why Unsupported returns rather than waits.
Sourcepub fn set_trigger(&mut self, trigger: Trigger)
pub fn set_trigger(&mut self, trigger: Trigger)
Set the executor-level trigger condition.
Controls which handles must be ready before spin_once dispatches
callbacks. Defaults to Trigger::Any.
Sourcepub fn set_semantics(&mut self, semantics: ExecutorSemantics)
pub fn set_semantics(&mut self, semantics: ExecutorSemantics)
Set the executor data communication semantics.
Choose between Direct (process in place) and LET
(snapshot-then-process) semantics. See ExecutorSemantics.
Sourcepub fn set_invocation(&mut self, id: HandleId, mode: InvocationMode)
pub fn set_invocation(&mut self, id: HandleId, mode: InvocationMode)
Sourcepub fn arena_used(&self) -> usize
pub fn arena_used(&self) -> usize
Arena bytes claimed by registered entities so far.
EXACT, not a worst case: the arena is a bump allocator and every
arena_alloc charges size_of::<T>(), so nothing is reserved per slot.
ARENA_SIZE itself is derived the other way — every slot budgeted at
the ActionClient worst case — which is issue 0900, and this accessor is
how an image can find out what it actually needs.
Sourcepub fn arena_capacity(&self) -> usize
pub fn arena_capacity(&self) -> usize
Total arena bytes this executor was given.
The arena is a borrowed slice; where its storage lives is the caller’s
choice, not a property of this type. Three answers are real — a .bss
static (the C/C++ entries, and the alloc constructors since phase-392
W6), the heap (those constructors’ fallback), and a stack-declared
rclc_executor_t. See super::backing and report_arena_headroom.
Sourcepub fn arena_released(&self) -> usize
pub fn arena_released(&self) -> usize
Issue 1496 — bytes released back to the arena and not yet reused.
Bytes handed back to the bump pointer are not counted: arena_used
already went down by them.
Sourcepub fn arena_holes(&self) -> usize
pub fn arena_holes(&self) -> usize
Issue 1667 — how many separate holes the free list holds. For tests and diagnostics.
Sourcepub fn set_reprobe_each_callback(&mut self, on: bool)
pub fn set_reprobe_each_callback(&mut self, on: bool)
phase-509 P3 (RFC-0108 D2) – probe the slots whose backend cannot
attribute an arrival (XRCE, uORB) after every callback, so one made
ready by a callback runs in the same pass, in rank order, instead of
at the next pass’s scan. Pushed readiness joins mid-pass either way.
The default is REPROBE_EACH_CALLBACK_DEFAULT (in executor/spin.rs); the phase-509 P3
host measurement that chose it is recorded beside that constant.
Sourcepub fn resolve_handle(&self, id: HandleId) -> Option<usize>
pub fn resolve_handle(&self, id: HandleId) -> Option<usize>
phase-476 W0 (issue 1667) — the slot id names, if and only if id was
issued for the entry that occupies it NOW. A handle whose entry was
released, or whose slot has since been taken by another registration of
any kind, answers None, never the next occupant.
Sourcepub fn register_subscription_buffered_raw_info_on<F, const RX_BUF: usize>(
&mut self,
node_id: NodeId,
topic_name: &str,
type_name: &str,
type_hash: &str,
qos: QoSProfile,
callback: F,
) -> Result<HandleId, NodeError>
pub fn register_subscription_buffered_raw_info_on<F, const RX_BUF: usize>( &mut self, node_id: NodeId, topic_name: &str, type_name: &str, type_hash: &str, qos: QoSProfile, callback: F, ) -> Result<HandleId, NodeError>
Register a raw (type-erased) buffered subscription whose callback
also receives a RawMessageInfo
carrying the sample’s wire attachment (Phase 189.M1).
Backs the node.subscription(t).generic(..).message_info().build(cb)
builder — the cross-RMW bridge reads the bridge_origin tag from
info.attachment() for echo suppression. One sample per
spin_once; the attachment is staged in a flat per-entry buffer
(cap RAW_INFO_ATT_CAP).
Sourcepub fn add_arena_subscription_callback<F, const RX_BUF: usize>(
&mut self,
handle: <CffiSession as Session>::SubscriptionHandle,
qos: QoSProfile,
callback: F,
) -> Result<HandleId, NodeError>
pub fn add_arena_subscription_callback<F, const RX_BUF: usize>( &mut self, handle: <CffiSession as Session>::SubscriptionHandle, qos: QoSProfile, callback: F, ) -> Result<HandleId, NodeError>
Register a raw byte-shaped callback against a pre-built
RmwSubscriber handle.
Backend-agnostic primitive — the caller is responsible for obtaining the handle by whatever route the active backend supports:
- Generic ROS-typed flow: call
Session::create_subscriptiononself.session_mut()with aTopicInfo. Thenode_mut(id).subscription(t).generic(ty, hash)builder is the convenience wrapper for this path. - Backend-specific flow (e.g. uORB needs
&'static orb_metadata): reach into the concrete session viaSelf::session_mutand call its backend-specific create method, then hand the handle here.nros-px4::uorb::create_subscription_with_callbackis the example.
The arena-store + vtable wiring is identical to
register_subscription_buffered_raw_on; the only thing that varies is
where the handle came from. Callback fires on every message
delivery during spin_once; bytes are
passed as &[u8].
Sourcepub fn register_service<Svc, F>(
&mut self,
service_name: &str,
callback: F,
) -> Result<HandleId, NodeError>where
Svc: RosService + 'static,
<Svc as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
pub fn register_service<Svc, F>(
&mut self,
service_name: &str,
callback: F,
) -> Result<HandleId, NodeError>where
Svc: RosService + 'static,
<Svc as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
Register a service callback with the default buffer size.
The callback is stored in the arena and invoked during spin_once().
Sourcepub fn register_service_sized<Svc, F, const REQ_BUF: usize, const REPLY_BUF: usize>(
&mut self,
service_name: &str,
callback: F,
) -> Result<HandleId, NodeError>where
Svc: RosService + 'static,
<Svc as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
pub fn register_service_sized<Svc, F, const REQ_BUF: usize, const REPLY_BUF: usize>(
&mut self,
service_name: &str,
callback: F,
) -> Result<HandleId, NodeError>where
Svc: RosService + 'static,
<Svc as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
Register a service callback with custom request/reply buffer sizes.
Sourcepub fn register_service_sized_on<Svc, F, const REQ_BUF: usize, const REPLY_BUF: usize>(
&mut self,
node_id: NodeId,
service_name: &str,
qos: QoSProfile,
callback: F,
) -> Result<HandleId, NodeError>where
Svc: RosService + 'static,
<Svc as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
pub fn register_service_sized_on<Svc, F, const REQ_BUF: usize, const REPLY_BUF: usize>(
&mut self,
node_id: NodeId,
service_name: &str,
qos: QoSProfile,
callback: F,
) -> Result<HandleId, NodeError>where
Svc: RosService + 'static,
<Svc as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
Phase 104.C.3.3.a — Node-aware variant of
register_service_sized.
Sourcepub fn register_service_with_info_sized_on<Svc, F, const REQ_BUF: usize, const REPLY_BUF: usize>(
&mut self,
node_id: NodeId,
service_name: &str,
qos: QoSProfile,
callback: F,
) -> Result<HandleId, NodeError>where
Svc: RosService + 'static,
<Svc as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request, &ServiceInfo) -> <Svc as RosService>::Reply + 'static,
pub fn register_service_with_info_sized_on<Svc, F, const REQ_BUF: usize, const REPLY_BUF: usize>(
&mut self,
node_id: NodeId,
service_name: &str,
qos: QoSProfile,
callback: F,
) -> Result<HandleId, NodeError>where
Svc: RosService + 'static,
<Svc as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request, &ServiceInfo) -> <Svc as RosService>::Reply + 'static,
Issue 1942 — register_service_sized_on
for a callback that also receives the request’s
ServiceInfo — rclrs’s (request, ServiceInfo)
service callback. A field the backend cannot report arrives as None.
Sourcepub fn register_service_on<Svc, F>(
&mut self,
node_id: NodeId,
service_name: &str,
callback: F,
) -> Result<HandleId, NodeError>where
Svc: RosService + 'static,
<Svc as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
pub fn register_service_on<Svc, F>(
&mut self,
node_id: NodeId,
service_name: &str,
callback: F,
) -> Result<HandleId, NodeError>where
Svc: RosService + 'static,
<Svc as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
Phase 104.C.3.3.a — Node-aware variant of
register_service.
Sourcepub fn register_timer<F>(
&mut self,
period: TimerDuration,
callback: F,
) -> Result<HandleId, NodeError>where
F: FnMut() + 'static,
pub fn register_timer<F>(
&mut self,
period: TimerDuration,
callback: F,
) -> Result<HandleId, NodeError>where
F: FnMut() + 'static,
Register a repeating timer callback.
The callback fires every period milliseconds during spin_once().
The timer delta is approximated by the timeout_ms argument to spin_once.
Sourcepub fn register_timer_on_clock<F>(
&mut self,
period: TimerDuration,
source: TimerClockSource,
callback: F,
) -> Result<HandleId, NodeError>where
F: FnMut() + 'static,
pub fn register_timer_on_clock<F>(
&mut self,
period: TimerDuration,
source: TimerClockSource,
callback: F,
) -> Result<HandleId, NodeError>where
F: FnMut() + 'static,
Register a repeating timer driven by a CLOCK rather than by the spin
delta — phase-425 W4, the shape rclcpp spells
create_timer(node, clock, period, cb).
register_timer is the wall timer: it consumes
the executor’s monotonic spin delta and no simulator can slow it down.
This one reads source on every poll and advances by the difference, so
a TimerClockSource::Ros timer follows /clock — it stops while the
simulator is paused, halves with a bag replayed at 0.5x, and restarts
its period on a backwards jump instead of stalling for the length of it.
With no /clock source installed, a Ros timer reads system time and
behaves like a wall timer with NTP steps, which is the same fallback
rclcpp::Clock has: a node written for simulation still runs standalone.
The node-level spellings of this are Node::create_timer_on_clock
and the declarative DeclaredNode::create_timer_on_clock (phase-430 W4);
all of them reach register_timer_entry, which is the only place a
timer entry is built.
Sourcepub fn register_timer_oneshot<F>(
&mut self,
delay: TimerDuration,
callback: F,
) -> Result<HandleId, NodeError>where
F: FnMut() + 'static,
pub fn register_timer_oneshot<F>(
&mut self,
delay: TimerDuration,
callback: F,
) -> Result<HandleId, NodeError>where
F: FnMut() + 'static,
Register a one-shot timer callback.
The callback fires once after delay milliseconds, then becomes inert.
Sourcepub fn register_timer_with_info<F>(
&mut self,
period: TimerDuration,
source: TimerClockSource,
autostart: bool,
callback: F,
) -> Result<HandleId, NodeError>where
F: FnMut(&TimerCallInfo) + 'static,
pub fn register_timer_with_info<F>(
&mut self,
period: TimerDuration,
source: TimerClockSource,
autostart: bool,
callback: F,
) -> Result<HandleId, NodeError>where
F: FnMut(&TimerCallInfo) + 'static,
Register a repeating timer whose callback receives the dispatcher’s
TimerCallInfo — issue 1974, the Rust counterpart of Jazzy’s
rclcpp::TimerInfoCallbackType / rcl_timer_call_info_t.
source picks the clock both the period and the two call times are
measured on. autostart == false creates the timer CANCELLED (rcl’s
rcl_timer_init2(..., autostart)): it does not fire until
reset_timer starts it, and its first period
counts from that reset.
executor.register_timer_with_info(
TimerDuration::from_millis(10),
TimerClockSource::Steady,
true,
|info| jitter.record(info.lateness_ns()),
)?;Sourcepub fn register_timer_with_info_on<F>(
&mut self,
node_id: Option<NodeId>,
period: TimerDuration,
source: TimerClockSource,
autostart: bool,
group: Option<&str>,
callback: F,
) -> Result<HandleId, NodeError>where
F: FnMut(&TimerCallInfo) + 'static,
pub fn register_timer_with_info_on<F>(
&mut self,
node_id: Option<NodeId>,
period: TimerDuration,
source: TimerClockSource,
autostart: bool,
group: Option<&str>,
callback: F,
) -> Result<HandleId, NodeError>where
F: FnMut(&TimerCallInfo) + 'static,
register_timer_with_info bound to a
node and an optional callback group — the executor primitive behind
Node::create_timer_with_info and the C rcl_timer_init2 / rclc_timer_init_default2 /
rclc_timer_init_with_info timers (issue 1974). node_id/group feed
the sched binding exactly as register_timer_on.
Sourcepub fn register_timer_on<F>(
&mut self,
node_id: Option<NodeId>,
period: TimerDuration,
callback: F,
group: Option<&str>,
) -> Result<HandleId, NodeError>where
F: FnMut() + 'static,
pub fn register_timer_on<F>(
&mut self,
node_id: Option<NodeId>,
period: TimerDuration,
callback: F,
group: Option<&str>,
) -> Result<HandleId, NodeError>where
F: FnMut() + 'static,
Phase 273 (RFC-0047) — register a repeating timer callback bound to a
specific node and optional callback group. The group name is threaded to
apply_node_default_sched so the seeded group_sched_table assigns
the timer’s callback to the group’s SchedContext. When group is
None the node’s default_sched applies (phase-272 behavior).
This is the executor-level primitive called by the Rust _in_group API
(Node::create_timer_in_group) and the C/C++ group-aware timer FFI.
Sourcepub fn add_arena_subscription_msg_info_closure<F>(
&mut self,
node_id: Option<NodeId>,
topic_name: &str,
type_name: &str,
type_hash: &str,
qos: QoSProfile,
rx_buffer_hint: usize,
callback: F,
) -> Result<HandleId, NodeError>
pub fn add_arena_subscription_msg_info_closure<F>( &mut self, node_id: Option<NodeId>, topic_name: &str, type_name: &str, type_hash: &str, qos: QoSProfile, rx_buffer_hint: usize, callback: F, ) -> Result<HandleId, NodeError>
Issue 2036 — register a raw subscription whose closure receives each
sample’s MessageInfo (None from a backend
that reports none): the raw twin of the typed with_info path, for a
caller that deserializes itself. Backs the C++
(const M&, const rclcpp::MessageInfo&) subscription callback
(nros_cpp_subscription_register_message_info).
rx_buffer_hint sizes both the backend’s payload class and the arena
slot, as on add_arena_subscription_c_info_callback;
0 falls back to DEFAULT_RX_BUF_SIZE.
Sourcepub fn register_service_raw_with_request_id_sized<const REQ_BUF: usize, const REPLY_BUF: usize>(
&mut self,
node_id: Option<NodeId>,
service_name: &str,
service_type: &str,
service_hash: &str,
qos: QoSProfile,
callback: unsafe extern "C" fn(*const u8, usize, *mut RawRequestId, *mut u8, usize, *mut usize, *mut c_void) -> bool,
context: *mut c_void,
capture: Option<&[u8]>,
capture_drop: Option<unsafe extern "C" fn(*mut c_void)>,
) -> Result<HandleId, NodeError>
pub fn register_service_raw_with_request_id_sized<const REQ_BUF: usize, const REPLY_BUF: usize>( &mut self, node_id: Option<NodeId>, service_name: &str, service_type: &str, service_hash: &str, qos: QoSProfile, callback: unsafe extern "C" fn(*const u8, usize, *mut RawRequestId, *mut u8, usize, *mut usize, *mut c_void) -> bool, context: *mut c_void, capture: Option<&[u8]>, capture_drop: Option<unsafe extern "C" fn(*mut c_void)>, ) -> Result<HandleId, NodeError>
Issue 2081 — a raw service whose callback also receives the request’s
id (client writer GUID + client sequence number), rclc’s
rclc_service_callback_with_request_id_t shape in CDR bytes. The id is
the one the backend reported for the request being dispatched
(ServiceTrait::request_info),
all zeros when it reported none.
One entry point for every registration shape: node_id as
register_service_raw_sized_on (or None), and capture as
register_service_raw_capturing (or None, with context
passed through instead).
Sourcepub fn register_guard_condition<F>(
&mut self,
callback: F,
) -> Result<(HandleId, GuardCondition), NodeError>where
F: FnMut() + 'static,
pub fn register_guard_condition<F>(
&mut self,
callback: F,
) -> Result<(HandleId, GuardCondition), NodeError>where
F: FnMut() + 'static,
Register a guard condition with a callback, on no particular node.
Returns both the HandleId for trigger configuration and a
GuardCondition for triggering from other threads.
This is the ours-only PRIMITIVE, not the verb a user reaches for.
phase-417 W4.e settled that the NODE owns a guard condition in all three
languages, so the spelling an application writes is
Node::create_guard_condition
— which is this function with a node bound, so the guard inherits that
node’s SchedContext like every other entity the node creates. This one
stays because the executor is what owns the arena the flag lives in, and
because an image with no node at all (a bare Executor::open plus a
wake source) still has to be able to make one.
Sourcepub fn register_guard_condition_on<F>(
&mut self,
node_id: Option<NodeId>,
callback: F,
) -> Result<(HandleId, GuardCondition), NodeError>where
F: FnMut() + 'static,
pub fn register_guard_condition_on<F>(
&mut self,
node_id: Option<NodeId>,
callback: F,
) -> Result<(HandleId, GuardCondition), NodeError>where
F: FnMut() + 'static,
The node-bound form of register_guard_condition
— phase-417 W4.e.
node_id == None is the legacy nodeless path and behaves exactly as
register_guard_condition always did. With a node, the registered slot
takes that node’s default SchedContext, which is what “the node owns
it” has to mean for a callback that shares one executor with every other
tier in the image (RFC-0047).
Sourcepub fn register_graph_change_guard_on<F>(
&mut self,
node_id: Option<NodeId>,
callback: F,
) -> Result<(HandleId, GuardCondition), NodeError>where
F: FnMut() + 'static,
pub fn register_graph_change_guard_on<F>(
&mut self,
node_id: Option<NodeId>,
callback: F,
) -> Result<(HandleId, GuardCondition), NodeError>where
F: FnMut() + 'static,
phase-467 Row 8 — a guard condition the BACKEND triggers when the ROS
graph changes. Upstream’s rcl_node_get_graph_guard_condition, under
this project’s guard-condition shape.
The verb is upstream’s, the shape is ours, and the difference is
stated rather than silent. rcl_node_get_graph_guard_condition
RETURNS a borrowed handle to a guard the node owns, which the caller
then adds to a wait set. RFC-0018/0019 forbids returning an owned
handle across the C seam, we decline wait sets (RFC-0002: one executor
per RTOS task), and a guard condition here is created into CALLER
storage with its callback bound at creation. So this CREATES one —
exactly as register_guard_condition_on does — and differs from it
only in who pulls the trigger.
Order matters and is the reason this is one verb rather than two. The
backend is asked whether it HAS a graph before a handle slot is
consumed, so a caller on XRCE or uORB gets Unsupported and an
unchanged executor, never a guard condition nothing will ever fire.
Sourcepub fn register_graph_change_guard<F>(
&mut self,
callback: F,
) -> Result<(HandleId, GuardCondition), NodeError>where
F: FnMut() + 'static,
pub fn register_graph_change_guard<F>(
&mut self,
callback: F,
) -> Result<(HandleId, GuardCondition), NodeError>where
F: FnMut() + 'static,
The nodeless form of
register_graph_change_guard_on,
for an image with a session and no node — the same pairing
register_guard_condition has.
Sourcepub fn cancel_timer(&mut self, id: HandleId) -> Result<(), NodeError>
pub fn cancel_timer(&mut self, id: HandleId) -> Result<(), NodeError>
Cancel a timer. A cancelled timer will not fire but still accumulates
elapsed time. The timer can be restarted with reset_timer().
phase-509 P9b item 2 – while the timer’s callback runs in another thread’s window, the cancel is recorded and applied when it returns.
Sourcepub fn reset_timer(&mut self, id: HandleId) -> Result<(), NodeError>
pub fn reset_timer(&mut self, id: HandleId) -> Result<(), NodeError>
Reset a timer. Clears the cancelled state and resets the elapsed time to zero, so the timer starts a fresh period.
phase-509 P9b item 2 – deferred like Self::cancel_timer.
Sourcepub fn timer_is_canceled(&self, id: HandleId) -> bool
pub fn timer_is_canceled(&self, id: HandleId) -> bool
Check if a timer is cancelled.
phase-509 P9b item 2 – a cancel or reset still pending behind the timer’s running callback is the answer, as if it had applied.
Sourcepub fn timer_period_ms(&self, id: HandleId) -> Option<u64>
pub fn timer_period_ms(&self, id: HandleId) -> Option<u64>
Get the period of a timer in milliseconds (truncated — see
Self::timer_period_us), or None if the handle is not a
valid timer.
Sourcepub fn timer_period_us(&self, id: HandleId) -> Option<u64>
pub fn timer_period_us(&self, id: HandleId) -> Option<u64>
Get the period of a timer in microseconds, or None if the
handle is not a valid timer.
Sourcepub fn timer_clock_source(&self, id: HandleId) -> Option<TimerClockSource>
pub fn timer_clock_source(&self, id: HandleId) -> Option<TimerClockSource>
Which clock advances this timer, or None if the handle is not a valid
timer.
issue 1975 — the state rclcpp::TimerBase::get_clock() reports. The
header field is the one arena::timer_try_process reads, so the answer
is the dispatcher’s own rather than a copy a wrapper kept beside it: a
wall timer answers TimerClockSource::Steady, a clock timer the
source it was registered with.
Sourcepub fn set_timer_overrun_policy(
&mut self,
id: HandleId,
policy: TimerOverrunPolicy,
) -> Result<(), NodeError>
pub fn set_timer_overrun_policy( &mut self, id: HandleId, policy: TimerOverrunPolicy, ) -> Result<(), NodeError>
Set a timer’s overrun policy (issue #505). Timers default to
TimerOverrunPolicy::Skip; switch to
TimerOverrunPolicy::CatchUp for timers whose every activation
is a unit of work that must not be lost.
Sourcepub fn timer_overruns(&self, id: HandleId) -> Option<u32>
pub fn timer_overruns(&self, id: HandleId) -> Option<u32>
Periods this timer missed and dropped under
TimerOverrunPolicy::Skip (issue #505), or None if the handle
is not a valid timer.
Monotonic and saturating. A growing count is the on-target signal that a tier is not keeping up with its declared cadence — the symptom an external observer would otherwise have to infer by differencing timestamps.
Sourcepub fn timer_time_until_next_call_ns(&self, id: HandleId) -> Option<i64>
pub fn timer_time_until_next_call_ns(&self, id: HandleId) -> Option<i64>
Nanoseconds until this timer next fires — NEGATIVE when it is overdue.
None if the handle is not a valid timer.
phase-417 G6. THE computation, and it has to be: rcl’s own pair are
next_call_time and next_call_time - now, so upstream cannot have
them disagree either. Before this it lived in nros-c
( + remaining_period_nstime_until_next_call_ns_of), and the C++
half of cpp:Timer::time_until_trigger would have been a second
derivation of the same subtraction in a second crate — the shape
CLAUDE.md’s “fix the CLASS, add ONE shared helper rather than a second
spelling” rule names. nros-c forwards here now; so does nros-cpp.
The unit is nanoseconds and the RESOLUTION is microseconds. The arena’s timer accounting is microsecond-based (issue #505), so this is a microsecond quantity scaled by 1000. The unit is rcl’s; the resolution is ours.
Saturating rather than wrapping: period_us and elapsed_us are both
u64, so their difference in nanoseconds can leave i64 in either
direction, and a wrap would turn “overdue by a lot” into “due in a
long time” — the sign is the whole answer here.
Sourcepub fn session_io_healthy(&self) -> bool
pub fn session_io_healthy(&self) -> bool
Whether the primary session drove I/O successfully on the last spin.
Convenience over ExecutorFfi::session_io_failures for the common
“is my transport alive?” check.
Sourcepub fn spin_once(&mut self, timeout: Duration) -> SpinOnceResult
pub fn spin_once(&mut self, timeout: Duration) -> SpinOnceResult
Drive I/O once, then dispatch EVERY callback that is ready — the tick primitive the rest of the family is built from.
OURS-ONLY (RFC-0089): rclcpp’s spin_once executes the NEXT ready item
and has no timeout; rclpy’s is spin_once(node, timeout_sec). Ours is a
blocking wait with a budget, which an RTOS task that must not spin-poll
needs and which neither upstream has a verb for. What it DOES when work
is ready is rclcpp’s spin_some — the whole ready set, one pass
(RFC-0002 §3) — so spin_some is the drain verb and
this is the waiting one.
timeout is the upper bound on the I/O wait. Since phase-436 W2 it is
carried in MICROSECONDS all the way to the park (issue 1193 — an
as_millis() here truncated from_micros(500) to 0, and a zero
timeout selects the non-blocking path, so a sub-millisecond spin()
was a 100 % CPU loop with no warning); the conversion to whatever the
platform primitive accepts happens once, at the park, and rounds UP.
Duration has no negative sentinel, which is what the pre-phase-84
timeout_ms: i32 signature used to silently mean “freeze the timers
and poll I/O forever”.
Sourcepub fn spin_forever(&mut self, opts: SpinOptions) -> !
pub fn spin_forever(&mut self, opts: SpinOptions) -> !
Drive I/O and dispatch callbacks forever — the body of an RTOS task.
EXTENSION (RFC-0089). Not a rename of upstream’s spin: the ! is the
point. A bare-metal entry has no shutdown source to return TO, and
declaring that lets the compiler drop the epilogue and lets the caller
write it as the last expression of a -> ! task body with no
unreachable!(). spin is the verb that can end, and it
is upstream’s.
phase-427 — was spin(Duration) -> !, which squatted on rclrs’s name
with a different contract (a ported executor.spin(SpinOptions) line
had to be renamed by hand, the one edit on the tutorial’s happy path
that no real difference forced). went with it: it named
a 50 ms poll interval and had zero call sites, and the interval is now
spin_default()SpinOptions::poll_interval, defaulting to
DEFAULT_SPIN_POLL_INTERVAL.
Envelope. Only poll_interval and stop_on_first_error can mean
anything here; timeout, only_next and max_callbacks are ENDING
conditions and this verb does not end. Passing one is a caller error and
says so at LOG_ERR on entry rather than being quietly dropped — a
discarded ending condition is precisely the “compiles and differs” case
RFC-0089 exists to make loud, and a -> ! signature has no other
channel. stop_on_first_error likewise cannot RETURN the error; it
logs it and keeps spinning, because a task body has nowhere to put it.
let mut executor = Executor::open(&config)?;
executor.register_subscription::<Int32, _>("/topic", |msg| { /* ... */ })?;
executor.spin_forever(SpinOptions::default()); // never returnsSourcepub fn spin_some(&mut self, max: Duration) -> Result<(), NodeError>
pub fn spin_some(&mut self, max: Duration) -> Result<(), NodeError>
Execute the work that is ready NOW and return — rclcpp’s
Executor::spin_some(max_duration).
ADOPT-BOUNDED (RFC-0089). Upstream’s drain verb: collect what is ready,
execute it, come back. It never waits for new work — that is
spin_once, which takes a budget and blocks on the
transport until it expires.
Envelope, two ways it is weaker than rclcpp’s:
- rclcpp collects the ready set ONCE and executes it. One
spin_once(0)here is already that whole set (RFC-0002 §3), so this would be a single call — except a callback may enqueue work for another entity, which rclcpp’sspin_allre-collects and itsspin_someleaves for the next call. Ours re-drains until a pass does NO work, which isspin_all’s answer, because the alternative is work sitting in the arena until someone spins again. Bounded bymax, so a callback that feeds itself cannot wedge the caller. maxis checked BETWEEN passes, never inside one, so it is a floor on when this returns and not a ceiling: a single long callback overruns it. There is no preemption here to make it a ceiling.
max of Duration::ZERO means no limit —
rclcpp’s own default — and is the spelling a build with no clock must
use. A non-zero max on a clockless build is an ERROR rather than an
unbounded drain (issue 0709, the same rule as spin’s timeout).
The Result is about that PRECONDITION and nothing else. A failed
callback is counted in each pass’s SpinOnceResult and does not end the
drain, matching spin’s default (SpinOptions::stop_on_first_error is
false); rclcpp’s spin_some returns void and reports callback
failures nowhere at all.
§Example
// Drain the ready set, then get on with the caller's own work.
executor.spin_some(core::time::Duration::ZERO)?;Sourcepub fn spin_one_period(
&mut self,
period_ms: u64,
elapsed_ms: u64,
) -> SpinPeriodPollingResult
pub fn spin_one_period( &mut self, period_ms: u64, elapsed_ms: u64, ) -> SpinPeriodPollingResult
Process one iteration and return remaining sleep time.
This is no_std compatible — the caller is responsible for the actual
delay using platform-specific sleep.
§Arguments
period_ms- Target period in millisecondselapsed_ms- Time elapsed since last call (used for timer ticking)
§Example
loop {
let r = executor.spin_one_period(10, elapsed_ms);
platform_sleep_ms(r.remaining_ms);
}Source§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub fn cancel(&self)
pub fn cancel(&self)
Request the executor to stop spinning — rclcpp::Executor::cancel.
§ADOPT-BOUNDED (RFC-0089)
cancel sets a flag the spin loop observes at the NEXT POLL BOUNDARY,
so it returns BEFORE spinning has actually stopped;
is_spinning() is the observable that tells you
when it has. rclcpp makes the same promise in the same shape, but the
boundary here is one spin_once timeout wide (a backend’s drive_io
blocks up to the poll interval), so “next boundary” is a duration a
caller can measure rather than an implementation detail. The wake flag
below shortens it; it does not remove it.
Nothing here tears down the session: an executor that has been cancelled
is still initialised, still owns its entities, and can be spun again.
Teardown is a different verb (fini / shutdown), and collapsing the
two costs a full discovery round on every mode change.
Safe to call from another thread or a signal handler.
Also raises the Phase 104.C.6 wake flag so a spin_once already blocked
inside a backend’s drive_io falls through to the cancel check on its
next loop iteration instead of waiting out its full timeout_ms first.
Sourcepub fn wake_all_workers(&self)
pub fn wake_all_workers(&self)
phase-521 W6 (RFC-0113 D2) — wake every worker parked over this executor: its own and every level’s band worker, as an arrival nothing attributes does. A level parks until its band’s next deadline or an arrival, not for a fixed period, so a runner that stops its levels must end those parks or wait them out. Lock-free; safe from any thread. Raises no halt: a woken worker runs one pass and parks again unless its runner’s own stop flag says otherwise.
Sourcepub fn is_halted(&self) -> bool
pub fn is_halted(&self) -> bool
Has a cancel() been REQUESTED?
Not the same question as is_spinning(), and both
are kept because both have callers:
is_halted()— “someone asked this executor to stop.” True the instantcancel()returns, and it stays true until the next spin loop clears it on entry. This is the flag a hand-rolledwhile !exec.is_halted()loop polls, which is what a BSP that drivesspin_onceitself writes.is_spinning()— “a spin loop is running right now.” Answers the other half of the envelope: whether the cancel has been ACTED on yet.
The two are true together for as long as one poll takes, which is exactly
the window the ADOPT-BOUNDED envelope on cancel() describes. Neither is
derivable from the other: an executor that was never spun is neither
halted nor spinning, and one cancelled mid-poll is both.
The name is the pre-rename spelling and is deliberately NOT deprecated —
cancel’s rclcpp pair is a spinning observable, not an
is_cancel_requested one, so there is no ROS 2 name to adopt here and a
third spelling would buy nothing.
Sourcepub fn is_spinning(&self) -> bool
pub fn is_spinning(&self) -> bool
Is a spin loop running on this executor right now?
rclcpp spells this Executor::is_spinning(). True between the entry and
the exit of spin,
spin_period, the open_threaded worker loop, and
the C / C++ API loops — every construct that owns its own iteration.
A bare spin_once does NOT set it: one poll is not a spin, and a caller
running its own while around spin_once is the one who knows it is
looping. Such a caller can say so with
enter_spin_loop.
See is_halted() for why this is a different question
from “was cancel requested”.
Source§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub fn spin(&mut self, opts: SpinOptions) -> Result<(), NodeError>
pub fn spin(&mut self, opts: SpinOptions) -> Result<(), NodeError>
Blocking spin loop with configurable exit conditions — rclrs’s
Executor::spin(SpinOptions).
ADOPT-BOUNDED (RFC-0089). Same name, same argument, same meaning: run
until an exit condition in opts says stop. Two bounds, both visible in
the signature:
- The error channel is one error, not all of them. rclrs returns
Vec<RclrsError>and the caller writes.first_error()?; there is no allocator here, so ours returnsResult<(), NodeError>carrying the FIRST failing callback’s error and drops the rest (they are still counted in each pass’sSpinOnceResult). Ported line for line, the edit is.first_error()?->?, which the compiler points at. - The loop is a poll loop, so
opts.poll_intervalis the quantum at which every exit condition is observed — acancel()from another task, thetimeoutexpiring,max_callbacksbeing reached. Requesting atimeouton a build with no clock is an ERROR, not a silent never-ending loop (issue 0709).
phase-427 — this method was . spin_blockingspin was taken by the
-> ! task body, which is now spin_forever.
Runs until one of:
cancel()is called (from another thread or signal handler)opts.timeoutexpires (if set)opts.max_callbacksis reached (if set)opts.only_nextis true (single iteration)- a callback failed and
opts.stop_on_first_erroris set
§Example
// Spin until cancelled
executor.spin(SpinOptions::default())?;
// Spin with 5-second timeout
executor.spin(SpinOptions::new().timeout(core::time::Duration::from_secs(5)))?;
// Single iteration
executor.spin(SpinOptions::spin_once())?;Sourcepub fn spin_one_period_timed(&mut self, period: Duration) -> SpinPeriodResult
pub fn spin_one_period_timed(&mut self, period: Duration) -> SpinPeriodResult
Execute one period with wall-clock overrun detection.
Calls spin_once(), measures wall-clock time, sleeps
for the remainder if under budget.
§Example
let period = core::time::Duration::from_millis(10);
let result = executor.spin_one_period_timed(period);
if result.overrun {
log::warn!("Period overrun: {:?}", result.elapsed);
}Sourcepub fn spin_period(&mut self, period: Duration) -> Result<(), NodeError>
pub fn spin_period(&mut self, period: Duration) -> Result<(), NodeError>
Spin at a fixed rate with drift compensation. Blocks until halted.
Uses wall-clock time to maintain the target rate. The next invocation
time is accumulated (not reset to now + period) to prevent cumulative
drift.
§Example
// 100Hz control loop — blocks until halt() is called
executor.spin_period(core::time::Duration::from_millis(10))?;Source§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub unsafe fn open_threaded(
self,
policy: SchedPolicy,
apply_policy: fn(SchedPolicy) -> Result<(), SchedError>,
spin_period: Duration,
) -> ThreadHandlewhere
's: 'static,
pub unsafe fn open_threaded(
self,
policy: SchedPolicy,
apply_policy: fn(SchedPolicy) -> Result<(), SchedError>,
spin_period: Duration,
) -> ThreadHandlewhere
's: 'static,
Phase 110.D.b — move this Executor onto a fresh OS thread,
apply a per-thread scheduling policy via the caller-supplied
apply_policy function, and run the spin loop until
ThreadHandle::halt fires.
The function-pointer indirection on apply_policy lets the
caller pass any platform’s PlatformScheduler::set_current_thread_policy
without forcing Executor to be generic over the platform —
keeps the existing Executor type stable.
Multi-executor preemption (the actual hard-RT win) comes from
the OS scheduler — call open_threaded once per criticality
tier, each with its own policy / priority. The kernel handles
preemption across executors; within a single executor,
dispatch remains non-preemptive (110.A–C bucketed sets).
§Safety
Moves self across thread boundaries. Executor contains a
raw *mut session::ConcreteSession when constructed via
from_session_ptr; the caller must ensure that pointer’s
referent stays valid across the lifetime of the spawned thread
and that no other thread mutates the session concurrently.
from_session (Owned) is safer — ConcreteSession ownership
transfers cleanly into the thread.
Sourcepub fn wake(&self)
pub fn wake(&self)
Phase 104.C.6 — wake the executor from another thread / ISR / signal handler.
Sets the shared wake_flag. The next spin_once swap-clears the
flag, skips the blocking wait on the primary session, and polls
every session non-blockingly so whatever queued the wake is
observed in a single iteration. Idempotent — multiple wake()
calls collapse into one observed wake per spin_once.
Source§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub fn halt_flag(&self) -> Arc<AtomicBool>
pub fn halt_flag(&self) -> Arc<AtomicBool>
Sourcepub fn wake_handle(&self) -> Arc<AtomicBool>
pub fn wake_handle(&self) -> Arc<AtomicBool>
Phase 104.C.6 — clone of the shared wake flag for cross-thread use (signal handlers, foreign threads, future per-backend vtable wake hooks).
§Example
let wake = executor.wake_handle();
std::thread::spawn(move || {
// ... compute something ...
// hand off to executor by setting the flag.
wake.store(true, Ordering::SeqCst);
});
loop { executor.spin_once(Duration::from_millis(100)); }Source§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub fn set_deferred(&mut self, id: HandleId, deferred: bool) -> bool
pub fn set_deferred(&mut self, id: HandleId, deferred: bool) -> bool
Defer (or stop deferring) one registered callback.
A deferred callback is never run by spin_once / spin_some: when it
is ready the spin queues it and calls the notifier from
set_defer_notifier, and it runs when the
host calls run_deferred or
run_deferred_handle.
Returns false, changing nothing, when id names no live callback or
names a timer (see the module docs). Turning deferral OFF also drops
the callback from the queue; it stays ready, so the next spin runs it
inline.
A deferred callback takes no part in the spin’s trigger
(set_trigger): it is neither “ready” for
Trigger::Any nor required by Trigger::All.
Sourcepub fn is_deferred(&self, id: HandleId) -> bool
pub fn is_deferred(&self, id: HandleId) -> bool
Whether id names a deferred callback.
Sourcepub fn set_defer_notifier(
&mut self,
notify: fn(*mut c_void, HandleId),
ctx: *mut c_void,
)
pub fn set_defer_notifier( &mut self, notify: fn(*mut c_void, HandleId), ctx: *mut c_void, )
Install the notifier the spin calls when it queues a deferred callback, with the context it is called with. Replaces any earlier notifier.
Called once per queueing: a callback that is already queued is not
queued (or notified) again until a drain has run it. With no notifier
the queue still fills, and the host can poll
has_deferred.
ctx is handed back verbatim and never dereferenced by the executor;
whatever it points to must stay valid while the notifier is installed.
Sourcepub fn clear_defer_notifier(&mut self)
pub fn clear_defer_notifier(&mut self)
Remove the notifier. Deferred callbacks keep being queued.
Sourcepub fn has_deferred(&self) -> bool
pub fn has_deferred(&self) -> bool
Whether any deferred callback is queued and waiting for a drain.
Sourcepub fn is_deferred_pending(&self, id: HandleId) -> bool
pub fn is_deferred_pending(&self, id: HandleId) -> bool
Whether the deferred callback id is queued.
Sourcepub fn run_deferred(&mut self) -> SpinOnceResult
pub fn run_deferred(&mut self) -> SpinOnceResult
Run every queued deferred callback once, in slot (registration)
order, and empty the queue. The counts are spin_once’s.
Call this from the host task that owns deferred work. It does no I/O
and never waits: a callback that finds no data (it was taken some
other way) is an Ok(false) dispatch, counted as nothing.
Sourcepub fn run_deferred_handle(&mut self, id: HandleId) -> bool
pub fn run_deferred_handle(&mut self, id: HandleId) -> bool
Run the deferred callback id once if it is queued. Returns whether it
ran (false when it was not queued or id names no live callback).
For a host that maps deferred callbacks onto tasks of different priorities: the notifier says which one is ready, and the task that owns it runs exactly that one.
Source§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub fn register_action_server<A, GoalF, CancelF>(
&mut self,
action_name: &str,
goal_callback: GoalF,
cancel_callback: CancelF,
) -> Result<ActionServerHandle<A>, NodeError>where
A: RosAction + 'static,
<A as RosAction>::Goal: Clone + MessageForRmw,
<A as RosAction>::Result: Clone + Default + MessageForRmw,
<A as RosAction>::Feedback: MessageForRmw,
<A as RosAction>::SendGoalRequest: MessageForRmw,
<A as RosAction>::SendGoalResponse: MessageForRmw,
<A as RosAction>::GetResultRequest: MessageForRmw,
<A as RosAction>::GetResultResponse: MessageForRmw,
<A as RosAction>::FeedbackMessage: MessageForRmw,
GoalF: FnMut(&GoalUuid, &<A as RosAction>::Goal) -> GoalResponse + 'static,
CancelF: FnMut(&GoalUuid, GoalStatusCode) -> CancelResponse + 'static,
pub fn register_action_server<A, GoalF, CancelF>(
&mut self,
action_name: &str,
goal_callback: GoalF,
cancel_callback: CancelF,
) -> Result<ActionServerHandle<A>, NodeError>where
A: RosAction + 'static,
<A as RosAction>::Goal: Clone + MessageForRmw,
<A as RosAction>::Result: Clone + Default + MessageForRmw,
<A as RosAction>::Feedback: MessageForRmw,
<A as RosAction>::SendGoalRequest: MessageForRmw,
<A as RosAction>::SendGoalResponse: MessageForRmw,
<A as RosAction>::GetResultRequest: MessageForRmw,
<A as RosAction>::GetResultResponse: MessageForRmw,
<A as RosAction>::FeedbackMessage: MessageForRmw,
GoalF: FnMut(&GoalUuid, &<A as RosAction>::Goal) -> GoalResponse + 'static,
CancelF: FnMut(&GoalUuid, GoalStatusCode) -> CancelResponse + 'static,
Register an action server with goal/cancel callbacks.
The executor automatically dispatches:
- Goal acceptance via
goal_callback - Cancel requests via
cancel_callback - Result serving for completed goals
Use the returned ActionServerHandle to publish feedback and complete goals.
Uses default buffer sizes and max 4 concurrent goals.
Sourcepub fn register_action_server_sized<A, GoalF, CancelF, const GOAL_BUF: usize, const RESULT_BUF: usize, const FEEDBACK_BUF: usize, const MAX_GOALS: usize>(
&mut self,
action_name: &str,
goal_callback: GoalF,
cancel_callback: CancelF,
) -> Result<ActionServerHandle<A>, NodeError>where
A: RosAction + 'static,
<A as RosAction>::Goal: Clone + MessageForRmw,
<A as RosAction>::Result: Clone + Default + MessageForRmw,
<A as RosAction>::Feedback: MessageForRmw,
<A as RosAction>::SendGoalRequest: MessageForRmw,
<A as RosAction>::SendGoalResponse: MessageForRmw,
<A as RosAction>::GetResultRequest: MessageForRmw,
<A as RosAction>::GetResultResponse: MessageForRmw,
<A as RosAction>::FeedbackMessage: MessageForRmw,
GoalF: FnMut(&GoalUuid, &<A as RosAction>::Goal) -> GoalResponse + 'static,
CancelF: FnMut(&GoalUuid, GoalStatusCode) -> CancelResponse + 'static,
pub fn register_action_server_sized<A, GoalF, CancelF, const GOAL_BUF: usize, const RESULT_BUF: usize, const FEEDBACK_BUF: usize, const MAX_GOALS: usize>(
&mut self,
action_name: &str,
goal_callback: GoalF,
cancel_callback: CancelF,
) -> Result<ActionServerHandle<A>, NodeError>where
A: RosAction + 'static,
<A as RosAction>::Goal: Clone + MessageForRmw,
<A as RosAction>::Result: Clone + Default + MessageForRmw,
<A as RosAction>::Feedback: MessageForRmw,
<A as RosAction>::SendGoalRequest: MessageForRmw,
<A as RosAction>::SendGoalResponse: MessageForRmw,
<A as RosAction>::GetResultRequest: MessageForRmw,
<A as RosAction>::GetResultResponse: MessageForRmw,
<A as RosAction>::FeedbackMessage: MessageForRmw,
GoalF: FnMut(&GoalUuid, &<A as RosAction>::Goal) -> GoalResponse + 'static,
CancelF: FnMut(&GoalUuid, GoalStatusCode) -> CancelResponse + 'static,
Register an action server with custom buffer sizes.
Trait Implementations§
Source§impl<'s> ExecutorFfi<'s> for Executor<'s>
impl<'s> ExecutorFfi<'s> for Executor<'s>
Source§fn add_arena_subscription_c_callback<const RX_BUF: usize>(
&mut self,
node_id: Option<NodeId>,
topic_name: &str,
type_name: &str,
type_hash: &str,
qos: QoSProfile,
callback: unsafe extern "C" fn(*const u8, usize, *mut c_void),
context: *mut c_void,
group: Option<&str>,
rx_buffer_hint: usize,
) -> Result<HandleId, NodeError>
fn add_arena_subscription_c_callback<const RX_BUF: usize>( &mut self, node_id: Option<NodeId>, topic_name: &str, type_name: &str, type_hash: &str, qos: QoSProfile, callback: unsafe extern "C" fn(*const u8, usize, *mut c_void), context: *mut c_void, group: Option<&str>, rx_buffer_hint: usize, ) -> Result<HandleId, NodeError>
Executor::add_arena_subscription_c_callback in spin.rs.Source§fn add_arena_subscription_c_callback_with_capture<const RX_BUF: usize>(
&mut self,
node_id: Option<NodeId>,
topic_name: &str,
type_name: &str,
type_hash: &str,
qos: QoSProfile,
callback: unsafe extern "C" fn(*const u8, usize, *mut c_void),
context: *mut c_void,
group: Option<&str>,
rx_buffer_hint: usize,
capture: Option<&[u8]>,
capture_drop: Option<unsafe extern "C" fn(*mut c_void)>,
) -> Result<HandleId, NodeError>
fn add_arena_subscription_c_callback_with_capture<const RX_BUF: usize>( &mut self, node_id: Option<NodeId>, topic_name: &str, type_name: &str, type_hash: &str, qos: QoSProfile, callback: unsafe extern "C" fn(*const u8, usize, *mut c_void), context: *mut c_void, group: Option<&str>, rx_buffer_hint: usize, capture: Option<&[u8]>, capture_drop: Option<unsafe extern "C" fn(*mut c_void)>, ) -> Result<HandleId, NodeError>
Executor::add_arena_subscription_c_callback_with_capture in spin.rs.Source§fn add_arena_subscription_c_info_callback<const RX_BUF: usize>(
&mut self,
node_id: Option<NodeId>,
topic_name: &str,
type_name: &str,
type_hash: &str,
qos: QoSProfile,
callback: unsafe extern "C" fn(*const u8, usize, *const u8, usize, *mut c_void),
context: *mut c_void,
rx_buffer_hint: usize,
) -> Result<HandleId, NodeError>
fn add_arena_subscription_c_info_callback<const RX_BUF: usize>( &mut self, node_id: Option<NodeId>, topic_name: &str, type_name: &str, type_hash: &str, qos: QoSProfile, callback: unsafe extern "C" fn(*const u8, usize, *const u8, usize, *mut c_void), context: *mut c_void, rx_buffer_hint: usize, ) -> Result<HandleId, NodeError>
Executor::add_arena_subscription_c_info_callback in spin.rs.Source§unsafe fn add_arena_subscription_c_typed_callback<const RX_BUF: usize>(
&mut self,
node_id: Option<NodeId>,
topic_name: &str,
type_name: &str,
type_hash: &str,
qos: QoSProfile,
msg: NonNull<c_void>,
deserialize: unsafe extern "C" fn(*const u8, usize, *mut c_void) -> i32,
callback: unsafe extern "C" fn(*const c_void, *mut c_void),
context: *mut c_void,
group: Option<&str>,
rx_buffer_hint: usize,
) -> Result<HandleId, NodeError>
unsafe fn add_arena_subscription_c_typed_callback<const RX_BUF: usize>( &mut self, node_id: Option<NodeId>, topic_name: &str, type_name: &str, type_hash: &str, qos: QoSProfile, msg: NonNull<c_void>, deserialize: unsafe extern "C" fn(*const u8, usize, *mut c_void) -> i32, callback: unsafe extern "C" fn(*const c_void, *mut c_void), context: *mut c_void, group: Option<&str>, rx_buffer_hint: usize, ) -> Result<HandleId, NodeError>
Executor::add_arena_subscription_c_typed_callback in spin.rs.Source§unsafe fn add_on_shutdown_callback(
&mut self,
callback: unsafe extern "C" fn(*mut c_void),
context: *mut c_void,
) -> Result<ShutdownCallbackHandle, NodeError>
unsafe fn add_on_shutdown_callback( &mut self, callback: unsafe extern "C" fn(*mut c_void), context: *mut c_void, ) -> Result<ShutdownCallbackHandle, NodeError>
Executor::add_on_shutdown_callback in spin.rs.Source§unsafe fn add_pre_shutdown_callback(
&mut self,
callback: unsafe extern "C" fn(*mut c_void),
context: *mut c_void,
) -> Result<ShutdownCallbackHandle, NodeError>
unsafe fn add_pre_shutdown_callback( &mut self, callback: unsafe extern "C" fn(*mut c_void), context: *mut c_void, ) -> Result<ShutdownCallbackHandle, NodeError>
Executor::add_pre_shutdown_callback in spin.rs.Source§fn remove_pre_shutdown_callback(
&mut self,
handle: ShutdownCallbackHandle,
) -> bool
fn remove_pre_shutdown_callback( &mut self, handle: ShutdownCallbackHandle, ) -> bool
Executor::remove_pre_shutdown_callback in spin.rs.Source§fn remove_on_shutdown_callback(
&mut self,
handle: ShutdownCallbackHandle,
) -> bool
fn remove_on_shutdown_callback( &mut self, handle: ShutdownCallbackHandle, ) -> bool
Executor::remove_on_shutdown_callback in spin.rs.Source§unsafe fn from_session_ptr_in(
session_ptr: *mut CffiSession,
backing: &'s mut [MaybeUninit<u64>],
sizing: ExecutorSizing,
) -> Executor<'s>
unsafe fn from_session_ptr_in( session_ptr: *mut CffiSession, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Executor<'s>
Executor::from_session_ptr_in in spin.rs.Source§unsafe fn open_multi_in(
specs: &[SessionSpec<'_>],
backing: &'s mut [MaybeUninit<u64>],
sizing: ExecutorSizing,
) -> Result<Executor<'s>, NodeError>
unsafe fn open_multi_in( specs: &[SessionSpec<'_>], backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Result<Executor<'s>, NodeError>
Executor::open_multi_in in spin.rs.Source§unsafe fn open_with_session_handle_in(
handle: SessionHandle,
backing: &'s mut [MaybeUninit<u64>],
sizing: ExecutorSizing,
) -> Executor<'s>
unsafe fn open_with_session_handle_in( handle: SessionHandle, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Executor<'s>
Executor::open_with_session_handle_in in spin.rs.Source§fn register_service_client_raw_sized<const REPLY_BUF: usize>(
&mut self,
service_name: &str,
service_type: &str,
service_hash: &str,
qos: QoSProfile,
callback: Option<unsafe extern "C" fn(*const u8, usize, *mut c_void)>,
context: *mut c_void,
) -> Result<HandleId, NodeError>
fn register_service_client_raw_sized<const REPLY_BUF: usize>( &mut self, service_name: &str, service_type: &str, service_hash: &str, qos: QoSProfile, callback: Option<unsafe extern "C" fn(*const u8, usize, *mut c_void)>, context: *mut c_void, ) -> Result<HandleId, NodeError>
Executor::register_service_client_raw_sized in spin.rs.Source§fn register_service_client_raw_capturing<const REPLY_BUF: usize>(
&mut self,
node_id: Option<NodeId>,
service_name: &str,
service_type: &str,
service_hash: &str,
qos: QoSProfile,
callback: Option<unsafe extern "C" fn(*const u8, usize, *mut c_void)>,
capture: Option<&[u8]>,
) -> Result<HandleId, NodeError>
fn register_service_client_raw_capturing<const REPLY_BUF: usize>( &mut self, node_id: Option<NodeId>, service_name: &str, service_type: &str, service_hash: &str, qos: QoSProfile, callback: Option<unsafe extern "C" fn(*const u8, usize, *mut c_void)>, capture: Option<&[u8]>, ) -> Result<HandleId, NodeError>
Executor::register_service_client_raw_capturing in spin.rs.Source§fn register_service_raw_capturing<const REQ_BUF: usize, const REPLY_BUF: usize>(
&mut self,
node_id: Option<NodeId>,
service_name: &str,
service_type: &str,
service_hash: &str,
qos: QoSProfile,
callback: unsafe extern "C" fn(*const u8, usize, *mut u8, usize, *mut usize, *mut c_void) -> bool,
capture: Option<&[u8]>,
capture_drop: Option<unsafe extern "C" fn(*mut c_void)>,
) -> Result<HandleId, NodeError>
fn register_service_raw_capturing<const REQ_BUF: usize, const REPLY_BUF: usize>( &mut self, node_id: Option<NodeId>, service_name: &str, service_type: &str, service_hash: &str, qos: QoSProfile, callback: unsafe extern "C" fn(*const u8, usize, *mut u8, usize, *mut usize, *mut c_void) -> bool, capture: Option<&[u8]>, capture_drop: Option<unsafe extern "C" fn(*mut c_void)>, ) -> Result<HandleId, NodeError>
Executor::register_service_raw_capturing in spin.rs.Source§fn register_service_raw_sized<const REQ_BUF: usize, const REPLY_BUF: usize>(
&mut self,
service_name: &str,
service_type: &str,
service_hash: &str,
qos: QoSProfile,
callback: unsafe extern "C" fn(*const u8, usize, *mut u8, usize, *mut usize, *mut c_void) -> bool,
context: *mut c_void,
) -> Result<HandleId, NodeError>
fn register_service_raw_sized<const REQ_BUF: usize, const REPLY_BUF: usize>( &mut self, service_name: &str, service_type: &str, service_hash: &str, qos: QoSProfile, callback: unsafe extern "C" fn(*const u8, usize, *mut u8, usize, *mut usize, *mut c_void) -> bool, context: *mut c_void, ) -> Result<HandleId, NodeError>
Executor::register_service_raw_sized in spin.rs.Source§fn register_timer_c_capturing(
&mut self,
node_id: Option<NodeId>,
period: TimerDuration,
source: TimerClockSource,
callback: unsafe extern "C" fn(*mut c_void),
capture: Option<&[u8]>,
capture_drop: Option<unsafe extern "C" fn(*mut c_void)>,
) -> Result<HandleId, NodeError>
fn register_timer_c_capturing( &mut self, node_id: Option<NodeId>, period: TimerDuration, source: TimerClockSource, callback: unsafe extern "C" fn(*mut c_void), capture: Option<&[u8]>, capture_drop: Option<unsafe extern "C" fn(*mut c_void)>, ) -> Result<HandleId, NodeError>
Executor::register_timer_c_capturing in spin.rs.Source§fn register_timer_c_capturing_with_info<I>(
&mut self,
node_id: Option<NodeId>,
period: TimerDuration,
source: TimerClockSource,
autostart: bool,
group: Option<&str>,
callback: unsafe extern "C" fn(*const I, *mut c_void),
capture: Option<&[u8]>,
capture_drop: Option<unsafe extern "C" fn(*mut c_void)>,
) -> Result<HandleId, NodeError>where
I: From<TimerCallInfo> + 'static,
fn register_timer_c_capturing_with_info<I>(
&mut self,
node_id: Option<NodeId>,
period: TimerDuration,
source: TimerClockSource,
autostart: bool,
group: Option<&str>,
callback: unsafe extern "C" fn(*const I, *mut c_void),
capture: Option<&[u8]>,
capture_drop: Option<unsafe extern "C" fn(*mut c_void)>,
) -> Result<HandleId, NodeError>where
I: From<TimerCallInfo> + 'static,
Executor::register_timer_c_capturing_with_info in spin.rs.Source§unsafe fn release_action_client_raw(&mut self, entry_index: usize) -> bool
unsafe fn release_action_client_raw(&mut self, entry_index: usize) -> bool
Executor::release_action_client_raw in action.rs.Source§unsafe fn release_action_server_raw(
&mut self,
handle: &ActionServerRawHandle,
) -> bool
unsafe fn release_action_server_raw( &mut self, handle: &ActionServerRawHandle, ) -> bool
Executor::release_action_server_raw in action.rs.Source§unsafe fn release_node(&mut self, node: NodeId) -> usize
unsafe fn release_node(&mut self, node: NodeId) -> usize
Executor::release_node in spin.rs.Source§unsafe fn release_service(&mut self, handle: HandleId) -> bool
unsafe fn release_service(&mut self, handle: HandleId) -> bool
Executor::release_service in spin.rs.Source§unsafe fn release_service_client(&mut self, handle: HandleId) -> bool
unsafe fn release_service_client(&mut self, handle: HandleId) -> bool
Executor::release_service_client in spin.rs.Source§unsafe fn release_subscription(&mut self, handle: HandleId) -> bool
unsafe fn release_subscription(&mut self, handle: HandleId) -> bool
Executor::release_subscription in spin.rs.Source§unsafe fn release_timer(&mut self, handle: HandleId) -> bool
unsafe fn release_timer(&mut self, handle: HandleId) -> bool
Executor::release_timer in spin.rs.Source§unsafe fn retarget_action_client_raw(
&mut self,
entry_index: usize,
context: *mut c_void,
) -> bool
unsafe fn retarget_action_client_raw( &mut self, entry_index: usize, context: *mut c_void, ) -> bool
Executor::retarget_action_client_raw in action.rs.Source§unsafe fn retarget_action_server_raw(
&mut self,
handle: &ActionServerRawHandle,
context: *mut c_void,
) -> bool
unsafe fn retarget_action_server_raw( &mut self, handle: &ActionServerRawHandle, context: *mut c_void, ) -> bool
Executor::retarget_action_server_raw in action.rs.Source§fn set_park_primitive(
&mut self,
park: unsafe extern "C" fn(*mut c_void, u64) -> i8,
ctx: *mut c_void,
granularity_us: u64,
)
fn set_park_primitive( &mut self, park: unsafe extern "C" fn(*mut c_void, u64) -> i8, ctx: *mut c_void, granularity_us: u64, )
Executor::set_park_primitive in spin.rs.Source§fn set_wake_hook(
&mut self,
hook: Option<unsafe extern "C" fn(*mut c_void)>,
ctx: *mut c_void,
) -> bool
fn set_wake_hook( &mut self, hook: Option<unsafe extern "C" fn(*mut c_void)>, ctx: *mut c_void, ) -> bool
Executor::set_wake_hook in spin.rs.Source§fn wake_raw_ptr(&self) -> *mut c_void
fn wake_raw_ptr(&self) -> *mut c_void
Executor::wake_raw_ptr in spin.rs.Source§fn enter_spin_loop(&self)
fn enter_spin_loop(&self)
Executor::enter_spin_loop in spin.rs.Source§fn exit_spin_loop(&self)
fn exit_spin_loop(&self)
Executor::exit_spin_loop in spin.rs.Source§fn exec_lock(&self) -> Option<ExecLock>
fn exec_lock(&self) -> Option<ExecLock>
Executor::exec_lock in spin.rs.Source§fn node_session_mut(&mut self, node_id: NodeId) -> Option<&mut CffiSession>
fn node_session_mut(&mut self, node_id: NodeId) -> Option<&mut CffiSession>
Executor::node_session_mut in spin.rs.Source§fn install_wake_signal_on_primary(&mut self)
fn install_wake_signal_on_primary(&mut self)
Executor::install_wake_signal_on_primary in spin.rs.Source§fn set_entry_owner(&mut self, id: HandleId, node: NodeId) -> bool
fn set_entry_owner(&mut self, id: HandleId, node: NodeId) -> bool
Executor::set_entry_owner in spin.rs.Source§fn set_primary_identity(&mut self, rmw_name: &str, locator: &str)
fn set_primary_identity(&mut self, rmw_name: &str, locator: &str)
Executor::set_primary_identity in spin.rs.Source§fn session_io_failures(&self) -> u32
fn session_io_failures(&self) -> u32
Executor::session_io_failures in spin.rs.Source§fn timer_elapsed_us(&self, id: HandleId) -> Option<u64>
fn timer_elapsed_us(&self, id: HandleId) -> Option<u64>
Executor::timer_elapsed_us in spin.rs.Source§fn timer_is_ready(&self, id: HandleId) -> Option<bool>
fn timer_is_ready(&self, id: HandleId) -> Option<bool>
Executor::timer_is_ready in spin.rs.Source§fn exchange_timer_period_us(
&mut self,
id: HandleId,
new_period_us: u64,
) -> Option<u64>
fn exchange_timer_period_us( &mut self, id: HandleId, new_period_us: u64, ) -> Option<u64>
Executor::exchange_timer_period_us in spin.rs.Source§fn register_service_client_raw_sized_on<const REPLY_BUF: usize>(
&mut self,
node_id: NodeId,
service_name: &str,
service_type: &str,
service_hash: &str,
qos: QoSProfile,
callback: Option<unsafe extern "C" fn(*const u8, usize, *mut c_void)>,
context: *mut c_void,
) -> Result<HandleId, NodeError>
fn register_service_client_raw_sized_on<const REPLY_BUF: usize>( &mut self, node_id: NodeId, service_name: &str, service_type: &str, service_hash: &str, qos: QoSProfile, callback: Option<unsafe extern "C" fn(*const u8, usize, *mut c_void)>, context: *mut c_void, ) -> Result<HandleId, NodeError>
Executor::register_service_client_raw_sized_on in spin.rs.Source§fn register_service_raw_sized_on<const REQ_BUF: usize, const REPLY_BUF: usize>(
&mut self,
node_id: NodeId,
service_name: &str,
service_type: &str,
service_hash: &str,
qos: QoSProfile,
callback: unsafe extern "C" fn(*const u8, usize, *mut u8, usize, *mut usize, *mut c_void) -> bool,
context: *mut c_void,
) -> Result<HandleId, NodeError>
fn register_service_raw_sized_on<const REQ_BUF: usize, const REPLY_BUF: usize>( &mut self, node_id: NodeId, service_name: &str, service_type: &str, service_hash: &str, qos: QoSProfile, callback: unsafe extern "C" fn(*const u8, usize, *mut u8, usize, *mut usize, *mut c_void) -> bool, context: *mut c_void, ) -> Result<HandleId, NodeError>
Executor::register_service_raw_sized_on in spin.rs.Source§unsafe fn service_client_entry_mut(
&mut self,
entry_index: usize,
) -> Option<&mut ServiceClientRawArenaEntry<nros_node::::executor::ffi::{impl#1}::service_client_entry_mut::{constant#0}>>
unsafe fn service_client_entry_mut( &mut self, entry_index: usize, ) -> Option<&mut ServiceClientRawArenaEntry<nros_node::::executor::ffi::{impl#1}::service_client_entry_mut::{constant#0}>>
Executor::service_client_entry_mut in spin.rs.Source§unsafe fn from_session_ptr(session_ptr: *mut CffiSession) -> Executor<'static>
unsafe fn from_session_ptr(session_ptr: *mut CffiSession) -> Executor<'static>
Executor::from_session_ptr in spin.rs.Source§unsafe fn from_session_in(
session: CffiSession,
backing: &'s mut [MaybeUninit<u64>],
sizing: ExecutorSizing,
) -> Executor<'s>
unsafe fn from_session_in( session: CffiSession, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Executor<'s>
Executor::from_session_in in spin.rs.Source§unsafe fn open_with_session_in(
session: *mut CffiSession,
backing: &'s mut [MaybeUninit<u64>],
sizing: ExecutorSizing,
) -> Executor<'s>
unsafe fn open_with_session_in( session: *mut CffiSession, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Executor<'s>
Executor::open_with_session_in in spin.rs.Source§unsafe fn open_with_session_slot(
session: *mut CffiSession,
slot: &'s mut [MaybeUninit<u64>; 11068],
) -> Executor<'s>
unsafe fn open_with_session_slot( session: *mut CffiSession, slot: &'s mut [MaybeUninit<u64>; 11068], ) -> Executor<'s>
Executor::open_with_session_slot in spin.rs.Source§unsafe fn open_with_session_handle_slot(
handle: SessionHandle,
slot: &'s mut [MaybeUninit<u64>; 11068],
) -> Executor<'s>
unsafe fn open_with_session_handle_slot( handle: SessionHandle, slot: &'s mut [MaybeUninit<u64>; 11068], ) -> Executor<'s>
Executor::open_with_session_handle_slot in spin.rs.Source§unsafe fn detach_action_client_raw(&mut self, entry_index: usize) -> bool
unsafe fn detach_action_client_raw(&mut self, entry_index: usize) -> bool
Executor::detach_action_client_raw in action.rs.Source§unsafe fn detach_action_server_raw(
&mut self,
handle: &ActionServerRawHandle,
) -> bool
unsafe fn detach_action_server_raw( &mut self, handle: &ActionServerRawHandle, ) -> bool
Executor::detach_action_server_raw in action.rs.Source§unsafe fn detach_action_server_raw_sized<const GOAL_BUF: usize, const RESULT_BUF: usize, const FEEDBACK_BUF: usize, const MAX_GOALS: usize>(
&mut self,
entry_index: usize,
) -> bool
unsafe fn detach_action_server_raw_sized<const GOAL_BUF: usize, const RESULT_BUF: usize, const FEEDBACK_BUF: usize, const MAX_GOALS: usize>( &mut self, entry_index: usize, ) -> bool
Executor::detach_action_server_raw_sized in action.rs.Source§unsafe fn release_action_client_raw_sized<const GOAL_BUF: usize, const RESULT_BUF: usize, const FEEDBACK_BUF: usize>(
&mut self,
entry_index: usize,
) -> bool
unsafe fn release_action_client_raw_sized<const GOAL_BUF: usize, const RESULT_BUF: usize, const FEEDBACK_BUF: usize>( &mut self, entry_index: usize, ) -> bool
Executor::release_action_client_raw_sized in action.rs.Source§unsafe fn release_action_server_raw_sized<const GOAL_BUF: usize, const RESULT_BUF: usize, const FEEDBACK_BUF: usize, const MAX_GOALS: usize>(
&mut self,
entry_index: usize,
) -> bool
unsafe fn release_action_server_raw_sized<const GOAL_BUF: usize, const RESULT_BUF: usize, const FEEDBACK_BUF: usize, const MAX_GOALS: usize>( &mut self, entry_index: usize, ) -> bool
Executor::release_action_server_raw_sized in action.rs.Source§fn register_service_raw(
&mut self,
service_name: &str,
service_type: &str,
service_hash: &str,
callback: unsafe extern "C" fn(*const u8, usize, *mut u8, usize, *mut usize, *mut c_void) -> bool,
context: *mut c_void,
) -> Result<HandleId, NodeError>
fn register_service_raw( &mut self, service_name: &str, service_type: &str, service_hash: &str, callback: unsafe extern "C" fn(*const u8, usize, *mut u8, usize, *mut usize, *mut c_void) -> bool, context: *mut c_void, ) -> Result<HandleId, NodeError>
Executor::register_service_raw in spin.rs.Source§fn register_service_client_raw(
&mut self,
service_name: &str,
service_type: &str,
service_hash: &str,
callback: Option<unsafe extern "C" fn(*const u8, usize, *mut c_void)>,
context: *mut c_void,
) -> Result<HandleId, NodeError>
fn register_service_client_raw( &mut self, service_name: &str, service_type: &str, service_hash: &str, callback: Option<unsafe extern "C" fn(*const u8, usize, *mut c_void)>, context: *mut c_void, ) -> Result<HandleId, NodeError>
Executor::register_service_client_raw in spin.rs.Source§unsafe fn release_guard_condition(&mut self, handle: HandleId) -> bool
unsafe fn release_guard_condition(&mut self, handle: HandleId) -> bool
Executor::release_guard_condition in spin.rs.impl<'s> Send for Executor<'s>
alloc only.