Skip to main content

Executor

Struct Executor 

Source
pub struct Executor<'s> { /* private fields */ }

Implementations§

Source§

impl<'s> Executor<'s>

Source

pub unsafe fn open_in( config: &ExecutorConfig<'_>, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Result<Self, 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>

Source

pub fn open(config: &ExecutorConfig<'_>) -> Result<Self, 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)?;
Source

pub fn open_sized( config: &ExecutorConfig<'_>, sizing: ExecutorSizing, ) -> Result<Self, 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.

Source

pub fn open_multi(specs: &[SessionSpec<'_>]) -> Result<Self, 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).

Source

pub fn open_with_rmw( rmw_name: &str, config: &ExecutorConfig<'_>, ) -> Result<Self, 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>

Source

pub unsafe fn open_with_rmw_in( rmw_name: &str, config: &ExecutorConfig<'_>, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Result<Self, 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>

Source

pub fn from_session(session: CffiSession) -> Self

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.

Source

pub fn from_session_with( session: CffiSession, config: &ExecutorConfig<'_>, ) -> Self

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>

Source

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.

Source

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).

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

pub fn domain_id(&self) -> u32

The ROS domain this executor declares entities on.

Source

pub fn set_node_identity(&mut self, node_name: &str, namespace: &str)

Source

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).

Source

pub fn resolve_entity_name_for( &self, node_name: &str, namespace: &str, source: &str, ) -> Result<ResolvedName, ()>

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).

Source

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.

Source

pub fn resolve_entity_name(&self, source: &str) -> Result<ResolvedName, ()>

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

pub fn monitor_table(&self) -> &'static [MonitorSpec]

The installed monitor table (empty unless the entry set one).

Source

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.

Source

pub fn age_table(&self) -> &'static [AgeMonitorSpec]

The installed age table (empty unless the entry set one).

Source

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.

Source

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.

Source

pub fn current_mode(&self) -> u16

The mode whose tables this executor judges by (0 = the base).

Source

pub fn hazard_table(&self) -> &'static [HazardSpec]

The installed hazard table (empty unless the entry set one).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn set_fault_handler(&mut self, f: fn(&Violation))

Source

pub fn set_reaction_hook(&mut self, hook: Option<(ReactionHookFn, *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.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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).

Source

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.

Source

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.

Source

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.

Source

pub unsafe fn spin_once_shared( this: *mut Self, timeout: Duration, ) -> SpinOnceResult

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.

Source

pub unsafe fn spin_band_once( this: *mut Self, 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

As Self::spin_once_shared.

Source

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.

Source

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.

Source

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.

Source

pub fn register_wake_source( &mut self, next: NextDeadlineFn, 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.

Source

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.

Source

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.

Source

pub fn has_park_primitive(&self) -> bool

Whether a platform park primitive is installed.

Source

pub fn last_park(&self) -> (u64, WakeSourceId)

phase-436 — the bound the last park used, and which source won it.

Source

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.

Source

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 with platform_sleep_us between 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.

Source

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_timedwait and pthread_cond_timedwait are nanosecond-native, and only the uint32_t timeout_ms signature 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.

Source

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.

Source

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.

Source

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.

Source

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.)

Source

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.

Source

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.

Source

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.

Source

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”).

Source

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.

Source

pub fn monitors_armed(&self) -> bool

phase-474 I2 – whether this executor’s monitors are armed.

Source

pub fn violations_suppressed_before_arm(&self) -> u32

phase-474 I2 – verdicts reached before the monitors armed: counted, never stored, logged, sunk or traced.

Source

pub unsafe fn set_violation_sink( &mut self, sink: Option<(ViolationSink, *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).

Source

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).

Source

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.

Source

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.

Source

pub fn set_min_stack_headroom_bytes(&mut self, bytes: usize)

Source

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.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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).

Source

pub fn sched_context(&self, sc_id: SchedContextId) -> Option<&SchedContext>

Inspect a registered scheduling context. Phase 110.B.

Source

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.

Source

pub fn nodes(&self) -> &[NodeRecord]

Return the Node table — Phase 104.C.2 read accessor.

Source

pub fn node(&self, id: NodeId) -> Option<&NodeRecord>

Borrow a Node’s metadata by id, returning None if the id is out of range.

Source

pub fn node_mut(&mut self, id: NodeId) -> Node<'_, 's>

Source

pub fn set_node_qos_overrides( &mut self, node_id: NodeId, overrides: &'static [QoSOverrideCode], )

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.

Source

pub fn create_publisher_on<M: MessageForRmw>( &mut self, node_id: NodeId, topic_name: &str, qos: QoSProfile, ) -> Result<EmbeddedPublisher<M>, NodeError>

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.

Source

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.

Source

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(...))?;
Source

pub fn with_node<R>( &mut self, id: NodeId, f: impl FnOnce(&mut NodeHandle<'_>) -> R, ) -> Result<R, NodeError>

Source

pub fn node_id_by_name(&self, name: &str, namespace: &str) -> Option<NodeId>

Find a registered executor node by final name and namespace.

Source

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.

Source

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.

Source

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).

Source

pub fn close(&mut self) -> Result<(), NodeError>

Close the underlying session, running the shutdown hooks around it.

Issue 0790. The order is the feature:

  1. 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;
  2. the session close;
  3. 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.

Source

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.

Source

pub fn has_ticking_component(&self) -> bool

phase-521 W6 — whether an enrolled component polls from its tick.

Source

pub fn session(&self) -> &CffiSession

Get a reference to the underlying session.

Source

pub fn session_mut(&mut self) -> &mut CffiSession

Get a mutable reference to the underlying session.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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”.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn set_invocation(&mut self, id: HandleId, mode: InvocationMode)

Set the invocation mode for a specific handle.

Controls whether the callback fires on every spin (Always) or only when new data arrives (OnNewData, the default).

Source

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.

Source

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.

Source

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.

Source

pub fn arena_holes(&self) -> usize

Issue 1667 — how many separate holes the free list holds. For tests and diagnostics.

Source

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.

Source

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.

Source

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>
where F: FnMut(&[u8], &RawMessageInfo<'_>) + 'static,

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).

Source

pub fn add_arena_subscription_callback<F, const RX_BUF: usize>( &mut self, handle: RmwSubscriber, qos: QoSProfile, callback: F, ) -> Result<HandleId, NodeError>
where F: FnMut(&[u8]) + 'static,

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_subscription on self.session_mut() with a TopicInfo. The node_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 via Self::session_mut and call its backend-specific create method, then hand the handle here. nros-px4::uorb::create_subscription_with_callback is 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].

Source

pub fn register_service<Svc, F>( &mut self, service_name: &str, callback: F, ) -> Result<HandleId, NodeError>
where Svc: RosService + 'static, Svc::Request: MessageForRmw, Svc::Reply: MessageForRmw, F: FnMut(&Svc::Request) -> Svc::Reply + 'static,

Register a service callback with the default buffer size.

The callback is stored in the arena and invoked during spin_once().

Source

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::Request: MessageForRmw, Svc::Reply: MessageForRmw, F: FnMut(&Svc::Request) -> Svc::Reply + 'static,

Register a service callback with custom request/reply buffer sizes.

Source

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::Request: MessageForRmw, Svc::Reply: MessageForRmw, F: FnMut(&Svc::Request) -> Svc::Reply + 'static,

Phase 104.C.3.3.a — Node-aware variant of register_service_sized.

Source

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::Request: MessageForRmw, Svc::Reply: MessageForRmw, F: FnMut(&Svc::Request, &ServiceInfo) -> Svc::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.

Source

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::Request: MessageForRmw, Svc::Reply: MessageForRmw, F: FnMut(&Svc::Request) -> Svc::Reply + 'static,

Phase 104.C.3.3.a — Node-aware variant of register_service.

Source

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.

Source

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.

Source

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.

Source

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()),
)?;
Source

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.

Source

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.

Source

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>
where F: FnMut(&[u8], Option<&MessageInfo>) + 'static,

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.

Source

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: RawServiceRequestIdCallback, 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).

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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_ns + time_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.

Source

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.

Source

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”.

Source

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). spin_default() went with it: it named a 50 ms poll interval and had zero call sites, and the interval is now 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 returns
Source

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’s spin_all re-collects and its spin_some leaves for the next call. Ours re-drains until a pass does NO work, which is spin_all’s answer, because the alternative is work sitting in the arena until someone spins again. Bounded by max, so a callback that feeds itself cannot wedge the caller.
  • max is 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)?;
Source

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 milliseconds
  • elapsed_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>

Source

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.

Source

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.

Source

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 instant cancel() returns, and it stays true until the next spin loop clears it on entry. This is the flag a hand-rolled while !exec.is_halted() loop polls, which is what a BSP that drives spin_once itself 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.

Source

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>

Source

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 returns Result<(), NodeError> carrying the FIRST failing callback’s error and drops the rest (they are still counted in each pass’s SpinOnceResult). Ported line for line, the edit is .first_error()? -> ?, which the compiler points at.
  • The loop is a poll loop, so opts.poll_interval is the quantum at which every exit condition is observed — a cancel() from another task, the timeout expiring, max_callbacks being reached. Requesting a timeout on a build with no clock is an ERROR, not a silent never-ending loop (issue 0709).

phase-427 — this method was spin_blocking. spin 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.timeout expires (if set)
  • opts.max_callbacks is reached (if set)
  • opts.only_next is true (single iteration)
  • a callback failed and opts.stop_on_first_error is 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())?;
Source

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);
}
Source

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>

Source

pub unsafe fn open_threaded( self, policy: SchedPolicy, apply_policy: fn(SchedPolicy) -> Result<(), SchedError>, spin_period: Duration, ) -> ThreadHandle
where '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.

Source

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>

Source

pub fn halt_flag(&self) -> Arc<AtomicBool>

Get a clone of the halt flag for use in signal handlers or other threads.

§Example
ⓘ
let halt = executor.halt_flag();
std::thread::spawn(move || {
    std::thread::sleep(Duration::from_secs(5));
    halt.store(true, Ordering::SeqCst);
});
executor.spin(SpinOptions::default())?;
Source

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>

Source

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.

Source

pub fn is_deferred(&self, id: HandleId) -> bool

Whether id names a deferred callback.

Source

pub fn set_defer_notifier(&mut self, notify: DeferNotify, 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.

Source

pub fn clear_defer_notifier(&mut self)

Remove the notifier. Deferred callbacks keep being queued.

Source

pub fn has_deferred(&self) -> bool

Whether any deferred callback is queued and waiting for a drain.

Source

pub fn is_deferred_pending(&self, id: HandleId) -> bool

Whether the deferred callback id is queued.

Source

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.

Source

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>

Source

pub fn register_action_server<A, GoalF, CancelF>( &mut self, action_name: &str, goal_callback: GoalF, cancel_callback: CancelF, ) -> Result<ActionServerHandle<A>, NodeError>

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.

Source

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>

Register an action server with custom buffer sizes.

Trait Implementations§

Source§

impl<'s> Drop for Executor<'s>

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more
Source§

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: RawSubscriptionCallback, context: *mut c_void, group: Option<&str>, rx_buffer_hint: usize, ) -> Result<HandleId, NodeError>

The FFI entry point; see 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: RawSubscriptionCallback, 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>

The FFI entry point; see 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: RawSubscriptionInfoCallback, context: *mut c_void, rx_buffer_hint: usize, ) -> Result<HandleId, NodeError>

The FFI entry point; see 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: RawMessageDeserializeFn, callback: TypedSubscriptionCallback, context: *mut c_void, group: Option<&str>, rx_buffer_hint: usize, ) -> Result<HandleId, NodeError>

The FFI entry point; see Executor::add_arena_subscription_c_typed_callback in spin.rs.
Source§

unsafe fn add_on_shutdown_callback( &mut self, callback: ShutdownCallbackFn, context: *mut c_void, ) -> Result<ShutdownCallbackHandle, NodeError>

The FFI entry point; see Executor::add_on_shutdown_callback in spin.rs.
Source§

unsafe fn add_pre_shutdown_callback( &mut self, callback: ShutdownCallbackFn, context: *mut c_void, ) -> Result<ShutdownCallbackHandle, NodeError>

The FFI entry point; see Executor::add_pre_shutdown_callback in spin.rs.
Source§

fn remove_pre_shutdown_callback( &mut self, handle: ShutdownCallbackHandle, ) -> bool

The FFI entry point; see Executor::remove_pre_shutdown_callback in spin.rs.
Source§

fn remove_on_shutdown_callback( &mut self, handle: ShutdownCallbackHandle, ) -> bool

The FFI entry point; see 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, ) -> Self

The FFI entry point; see Executor::from_session_ptr_in in spin.rs.
Source§

unsafe fn open_multi_in( specs: &[SessionSpec<'_>], backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Result<Self, NodeError>

The FFI entry point; see Executor::open_multi_in in spin.rs.
Source§

unsafe fn open_with_session_handle_in( handle: SessionHandle, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Self

The FFI entry point; see 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<RawResponseCallback>, context: *mut c_void, ) -> Result<HandleId, NodeError>

The FFI entry point; see 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<RawResponseCallback>, capture: Option<&[u8]>, ) -> Result<HandleId, NodeError>

The FFI entry point; see 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: RawServiceCallback, capture: Option<&[u8]>, capture_drop: Option<unsafe extern "C" fn(*mut c_void)>, ) -> Result<HandleId, NodeError>

The FFI entry point; see 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: RawServiceCallback, context: *mut c_void, ) -> Result<HandleId, NodeError>

The FFI entry point; see 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>

The FFI entry point; see 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,

The FFI entry point; see Executor::register_timer_c_capturing_with_info in spin.rs.
Source§

unsafe fn release_action_client_raw(&mut self, entry_index: usize) -> bool

The FFI entry point; see Executor::release_action_client_raw in action.rs.
Source§

unsafe fn release_action_server_raw( &mut self, handle: &ActionServerRawHandle, ) -> bool

The FFI entry point; see Executor::release_action_server_raw in action.rs.
Source§

unsafe fn release_node(&mut self, node: NodeId) -> usize

The FFI entry point; see Executor::release_node in spin.rs.
Source§

unsafe fn release_service(&mut self, handle: HandleId) -> bool

The FFI entry point; see Executor::release_service in spin.rs.
Source§

unsafe fn release_service_client(&mut self, handle: HandleId) -> bool

The FFI entry point; see Executor::release_service_client in spin.rs.
Source§

unsafe fn release_subscription(&mut self, handle: HandleId) -> bool

The FFI entry point; see Executor::release_subscription in spin.rs.
Source§

unsafe fn release_timer(&mut self, handle: HandleId) -> bool

The FFI entry point; see Executor::release_timer in spin.rs.
Source§

unsafe fn retarget_action_client_raw( &mut self, entry_index: usize, context: *mut c_void, ) -> bool

The FFI entry point; see Executor::retarget_action_client_raw in action.rs.
Source§

unsafe fn retarget_action_server_raw( &mut self, handle: &ActionServerRawHandle, context: *mut c_void, ) -> bool

The FFI entry point; see Executor::retarget_action_server_raw in action.rs.
Source§

fn set_park_primitive( &mut self, park: ParkUntilFn, ctx: *mut c_void, granularity_us: u64, )

The FFI entry point; see Executor::set_park_primitive in spin.rs.
Source§

fn set_wake_hook(&mut self, hook: Option<WakeHookFn>, ctx: *mut c_void) -> bool

The FFI entry point; see Executor::set_wake_hook in spin.rs.
Source§

fn wake_raw_ptr(&self) -> *mut c_void

The FFI entry point; see Executor::wake_raw_ptr in spin.rs.
Source§

fn enter_spin_loop(&self)

The FFI entry point; see Executor::enter_spin_loop in spin.rs.
Source§

fn exit_spin_loop(&self)

The FFI entry point; see Executor::exit_spin_loop in spin.rs.
Source§

fn exec_lock(&self) -> Option<ExecLock>

The FFI entry point; see Executor::exec_lock in spin.rs.
Source§

fn node_session_mut(&mut self, node_id: NodeId) -> Option<&mut CffiSession>

The FFI entry point; see Executor::node_session_mut in spin.rs.
Source§

fn install_wake_signal_on_primary(&mut self)

The FFI entry point; see Executor::install_wake_signal_on_primary in spin.rs.
Source§

fn set_entry_owner(&mut self, id: HandleId, node: NodeId) -> bool

The FFI entry point; see Executor::set_entry_owner in spin.rs.
Source§

fn set_primary_identity(&mut self, rmw_name: &str, locator: &str)

The FFI entry point; see Executor::set_primary_identity in spin.rs.
Source§

fn session_io_failures(&self) -> u32

The FFI entry point; see Executor::session_io_failures in spin.rs.
Source§

fn timer_elapsed_us(&self, id: HandleId) -> Option<u64>

The FFI entry point; see Executor::timer_elapsed_us in spin.rs.
Source§

fn timer_is_ready(&self, id: HandleId) -> Option<bool>

The FFI entry point; see Executor::timer_is_ready in spin.rs.
Source§

fn exchange_timer_period_us( &mut self, id: HandleId, new_period_us: u64, ) -> Option<u64>

The FFI entry point; see 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<RawResponseCallback>, context: *mut c_void, ) -> Result<HandleId, NodeError>

The FFI entry point; see 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: RawServiceCallback, context: *mut c_void, ) -> Result<HandleId, NodeError>

The FFI entry point; see Executor::register_service_raw_sized_on in spin.rs.
Source§

unsafe fn service_client_entry_mut( &mut self, entry_index: usize, ) -> Option<&mut ServiceClientRawArenaEntry<{ crate::config::DEFAULT_RX_BUF_SIZE }>>

The FFI entry point; see Executor::service_client_entry_mut in spin.rs.
Source§

unsafe fn from_session_ptr(session_ptr: *mut CffiSession) -> Executor<'static>

The FFI entry point; see Executor::from_session_ptr in spin.rs.
Source§

unsafe fn from_session_in( session: CffiSession, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Self

The FFI entry point; see Executor::from_session_in in spin.rs.
Source§

unsafe fn open_with_session_in( session: *mut CffiSession, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Self

The FFI entry point; see Executor::open_with_session_in in spin.rs.
Source§

unsafe fn open_with_session_slot( session: *mut CffiSession, slot: &'s mut TierExecutorBackingSlot, ) -> Self

The FFI entry point; see Executor::open_with_session_slot in spin.rs.
Source§

unsafe fn open_with_session_handle_slot( handle: SessionHandle, slot: &'s mut TierExecutorBackingSlot, ) -> Self

The FFI entry point; see Executor::open_with_session_handle_slot in spin.rs.
Source§

unsafe fn detach_action_client_raw(&mut self, entry_index: usize) -> bool

The FFI entry point; see Executor::detach_action_client_raw in action.rs.
Source§

unsafe fn detach_action_server_raw( &mut self, handle: &ActionServerRawHandle, ) -> bool

The FFI entry point; see 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

The FFI entry point; see 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

The FFI entry point; see 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

The FFI entry point; see 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: RawServiceCallback, context: *mut c_void, ) -> Result<HandleId, NodeError>

The FFI entry point; see 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<RawResponseCallback>, context: *mut c_void, ) -> Result<HandleId, NodeError>

The FFI entry point; see Executor::register_service_client_raw in spin.rs.
Source§

unsafe fn release_guard_condition(&mut self, handle: HandleId) -> bool

The FFI entry point; see Executor::release_guard_condition in spin.rs.
Source§

impl<'s> Send for Executor<'s>

Available on crate feature alloc only.

Auto Trait Implementations§

§

impl<'s> !Freeze for Executor<'s>

§

impl<'s> !Sync for Executor<'s>

§

impl<'s> !UnwindSafe for Executor<'s>

§

impl<'s> RefUnwindSafe for Executor<'s>

§

impl<'s> Unpin for Executor<'s>

§

impl<'s> UnsafeUnpin for Executor<'s>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.