nros_node/executor/mod.rs
1//! Embedded executor with build-time configured arena.
2//!
3//! Provides `Executor` and `Node` that work with the compile-time
4//! selected RMW backend (zenoh, XRCE-DDS, or C FFI).
5//!
6//! # Example
7//!
8//! ```ignore
9//! use nros_node::executor::*;
10//! use std_msgs::msg::Int32;
11//!
12//! let config = ExecutorConfig::from_env().node_name("my_node");
13//! let mut executor = Executor::open(&config)?;
14//! let mut node = executor.create_node("my_node")?;
15//!
16//! let publisher = node.create_publisher::<Int32>("/chatter")?;
17//! publisher.publish(&Int32 { data: 42 })?;
18//!
19//! loop {
20//! executor.spin_once(core::time::Duration::from_millis(10));
21//! }
22//! ```
23
24#[cfg(any(has_rmw, test))]
25pub mod action_core;
26#[cfg(any(has_rmw, test))]
27pub(crate) mod activator;
28#[cfg(any(has_rmw, test))]
29mod arena;
30// phase-412 #4 — the compile-time half of "is the arena big enough?". Not
31// `has_rmw`-gated: it reads only `config`, and the check it carries must run in
32// every compilation of this crate, whatever the backend.
33pub mod arena_oracle;
34// phase-392 W6 — the named `.bss` static the `alloc` convenience constructors
35// serve their backing from. `alloc`-gated because it exists to replace a
36// `Box::leak`: a no-alloc entry already supplies its own `static` through
37// `open_in`, and reserving a second one there would be pure waste in exactly
38// the images this campaign is about.
39#[cfg(all(any(has_rmw, test), feature = "alloc"))]
40mod backing;
41// issue 1571 — the spawned tiers' half of the same machinery: the ENTRY owns
42// the static (it knows the tier count), boards hand each tier a slot.
43#[cfg(all(any(has_rmw, test), feature = "alloc"))]
44pub use backing::{
45 EXECUTOR_BACKING_DEFAULT_U64S, TIER_TASK_TCB_U64S, TierBackingShort, TierExecutorBacking,
46 TierExecutorBackingSlot, TierSetupGate, TierSetupGuard, TierTaskMemory, TierTaskMemoryRaw,
47 TierTaskMemorySet, check_tier_executor_backing,
48};
49// Phase 8 (autoware-safety-island `docs/design/callback_tracing.rst`) —
50// callback-level dispatch tracing. Same gating shape as `wake_probe`, for the
51// same reason: hot-path hooks that must vanish in production. The module ALSO
52// self-gates (`#![cfg(feature = "trace-callbacks")]`), so the call sites use
53// paired stubs rather than naming it directly.
54#[cfg(all(any(has_rmw, test), feature = "trace-callbacks"))]
55pub mod callback_trace;
56// phase-463 W7 — the host profile (`profile-mode`, host-only: it needs `std`
57// and no RTOS umbrella resolves it).
58#[cfg(all(any(has_rmw, test), feature = "profile-mode"))]
59pub mod profile;
60// phase-457 W5 (issue 1522) — the shape a DECLARED subscription lowers to.
61// UNGATED on purpose: the executor half needs an RMW seam, but the recorder
62// half runs in a probe that opens no executor, and both must read the same
63// enum.
64pub mod declared_shape;
65#[cfg(any(has_rmw, test))]
66pub(crate) mod dispatcher;
67#[cfg(any(has_rmw, test))]
68mod handles;
69// issue 0669 / phase-359 W10 — `pub mod handoff` stood here. `Handoff<M, N>`
70// was optional sugar over `Arc<Mutex<heapless::Vec<M, N>>>` for the
71// cross-priority bridge pattern (phase-104.E.3), it was `std`-gated on
72// `std::sync::Mutex` alone, and it had no consumer anywhere in the tree — not
73// one call site in `packages/` or `examples/` since it landed. Deleted rather
74// than ported: the only portable mutex reachable from here costs a `spin`
75// dependency edge on every build of this crate (measured: `spin` is in NO
76// board's graph today), and an API with no consumer has no evidence its shape
77// is the right one. The pattern it wrapped is six lines and is written out in
78// issue 0669.
79#[cfg(any(has_rmw, test))]
80pub mod monitor;
81#[cfg(any(has_rmw, test))]
82mod node;
83#[cfg(any(has_rmw, test))]
84pub mod node_record;
85#[cfg(any(has_rmw, test))]
86mod node_wake;
87// phase-436 -- one backend wake slot shared by every executor over a session.
88#[cfg(any(all(feature = "alloc", feature = "rmw-cffi"), test))]
89pub(crate) mod wake_fanout;
90// phase-359 W10 — the per-OS-priority worker pool, ported off `std::thread`
91// onto the platform task ABI so it is reachable on every platform.
92//
93// Deliberately the SAME predicate as `node_wake` above, which it imports:
94// spelling a second, hand-matched predicate here is how the two drift, and they
95// did — an `all(feature = …, any(has_rmw, test))` copy resolved true in a build
96// where `node_wake` resolved false. The feature half lives inside the file as
97// an inner `#![cfg]`, so there is one condition per fact and no pair to keep in
98// step.
99pub(crate) mod os_priority;
100// Issue 1705 — the worker hand-off protocol: compiled with the pool that uses
101// it, and in every unit-test build, which drives it without the pool. The
102// predicate is the file's inner `#![cfg]`, as for `os_priority`.
103#[cfg(any(has_rmw, test))]
104/// phase-436 W7 — installing a port's park primitive, with the wake-object
105/// constraint stated once instead of per board.
106pub mod port_park;
107pub(crate) mod worker_item;
108// phase-359 W10 — the allocate/spawn/join helper moved to
109// `nros_platform_api::task`, beside the ABI it wraps, once `nros-cpp` became a
110// third caller. Reached through a `use` below rather than a module here.
111#[cfg(any(has_rmw, test))]
112pub(crate) mod ready_set;
113// phase-457 W3 — the per-endpoint half of `registration_path`, reported by the
114// one site that knows it. Behind a feature only a metadata probe turns on.
115#[cfg(feature = "registration-observer")]
116pub mod registration_observer;
117pub mod sched_context;
118#[cfg(any(has_rmw, test))]
119pub(crate) mod spin;
120#[cfg(any(has_rmw, test))]
121pub(crate) mod spsc_ring;
122#[cfg(any(has_rmw, test))]
123mod storage;
124#[cfg(any(has_rmw, test))]
125pub(crate) mod triple_buffer;
126mod types;
127#[cfg(all(any(has_rmw, test), feature = "wake-latency-probe"))]
128pub mod wake_probe;
129
130#[cfg(any(has_rmw, test))]
131pub mod action;
132
133// MockSession-based tests. Disabled when any rmw-* feature is active because
134// feature unification under `cargo test --workspace` flips `ConcreteSession`
135// to a real backend handle (e.g. UorbSession when rmw-uorb is on transitively
136// via the workspace), breaking the type signatures the tests expect.
137// `feature = "alloc"` is part of the gate, not decoration. The crate is
138// `#![no_std]`; `tests.rs` uses `std::` in 155 places (in scope because
139// `lib.rs` brings `std` in under `cfg(test)`) and calls `from_session`, which
140// is itself `#[cfg(feature = "alloc")]`. Without the
141// feature in the gate, a plain `cargo test -p nros-node` compiles this module
142// into a no_std crate and produces 252 errors, 192 of them "cannot find module
143// or crate `std`" — an incomprehensible wall for anyone running the obvious
144// command, and unrelated to whatever they were changing.
145//
146// These tests genuinely need a host: they spawn threads, sleep, and read the
147// wall clock. Gating them is not hiding coverage, it is declaring what they
148// already required. `cargo test -p nros-node --features std` is unchanged and
149// remains the way to run them.
150#[cfg(all(test, feature = "alloc", not(feature = "rmw-cffi")))]
151mod tests;
152
153// phase-444 — the two `wait_for_*` decisions, in their own file rather
154// than appended to `tests.rs`. Same gate, same fixture (`MockSession`);
155// separate because what they pin is a CONTRACT choice, not executor
156// mechanics, and a reader looking for "why does Unsupported not wait?"
157// should not have to find it inside a 10 000-line file.
158#[cfg(all(test, feature = "alloc", not(feature = "rmw-cffi")))]
159mod graph_wait_tests;
160
161// Flat re-exports so users write `executor::Executor` etc.
162#[cfg(any(has_rmw, test))]
163pub use action::{
164 ActionClientRawHandle, ActionServerHandle, ActionServerRawHandle, RawActionClientSpec,
165 RawActionServerSpec,
166};
167#[cfg(any(has_rmw, test))]
168pub use action_core::{ActionClientCore, ActionServerCore, RawActiveGoal, action_channel_type};
169// phase-430 W4 — DEFINED in `crate::timer` (ungated) and re-exported here, so
170// `nros_node::executor::TimerClockSource` keeps resolving for every caller
171// phase-425 wrote while the declarative metadata layer, which builds with no
172// transport, can name the same enum instead of inventing a second one.
173pub use crate::timer::TimerClockSource;
174#[cfg(any(has_rmw, test))]
175pub use arena::TimerOverrunPolicy;
176#[cfg(any(has_rmw, test))]
177pub use handles::*;
178#[cfg(any(has_rmw, test))]
179// `Node` joined this line in 2026-09-12 (issue 1351). It was the only one of
180// the three that `mod node` kept to itself, and it was not a smaller surface for
181// it: `Executor::node_mut` is `pub` and RETURNS a `Node`, the `nros` crate's
182// own first doc example is `executor.node_mut(node).create_publisher::<Int32>(…)`,
183// and 15 in-tree binaries call its methods. What the missing `pub use` bought
184// was that the type had no PATH — a user could call all ~25 methods and could
185// not write `fn setup(ctx: &mut Node)` to factor two of them out, and the
186// Rust parity extractor (which walks the umbrella's reachable surface) had never
187// produced a single row for any of them.
188pub use node::{CallbackGroup, Node, NodeHandle};
189#[cfg(any(has_rmw, test))]
190pub use node_record::{NodeBuilder, NodeId, NodeRecord};
191#[cfg(any(has_rmw, test))]
192pub use spin::Executor;
193// phase-436 — the wake-source seam's types. `Executor::last_park` RETURNS a
194// `WakeSourceId` and `register_wake_source` / `set_park_primitive` TAKE the two
195// fn-pointer types, so all four are already public API; without these a caller
196// outside the crate could receive a value it cannot name or match on.
197#[cfg(any(has_rmw, test))]
198pub use spin::SessionHandle;
199#[cfg(all(any(has_rmw, test), feature = "rmw-cffi"))]
200pub use spin::SessionSpec;
201#[cfg(any(has_rmw, test))]
202pub use spin::{MAX_WAKE_SOURCES, NextDeadlineFn, ParkUntilFn, WakeSourceId};
203#[cfg(any(has_rmw, test))]
204pub use storage::{
205 ExecutorInlineStorage, ExecutorSizing, RegionUnit, RegionUnits, executor_storage_layout,
206 executor_storage_layout_with, executor_storage_u64_len,
207};
208pub use types::*;