Skip to main content

Session

Trait Session 

Source
pub trait Session {
    type Error;
    type PublisherHandle;
    type SubscriptionHandle;
    type ServiceHandle;
    type ClientHandle;

    const SERIALIZATION_FORMAT: &'static str = _;
    const SERIALIZATION_FORMAT_ID: SerializationFormatId = nros_serdes::format::SerializationFormatId::Cdr;
Show 19 methods // Required methods fn create_publisher( &mut self, topic: &TopicInfo<'_>, qos: QoSProfile, ) -> Result<Self::PublisherHandle, Self::Error>; fn create_subscription( &mut self, topic: &TopicInfo<'_>, qos: QoSProfile, ) -> Result<Self::SubscriptionHandle, Self::Error>; fn create_service( &mut self, service: &ServiceInfo<'_>, qos: QoSProfile, ) -> Result<Self::ServiceHandle, Self::Error>; fn create_client( &mut self, service: &ServiceInfo<'_>, qos: QoSProfile, ) -> Result<Self::ClientHandle, Self::Error>; fn close(&mut self) -> Result<(), Self::Error>; fn drive_io(&mut self, timeout_ms: i32) -> Result<(), Self::Error>; // Provided methods fn serialization_format(&self) -> &'static str { ... } fn supported_qos_policies(&self) -> QoSPolicyMask { ... } fn next_deadline_ms(&self) -> Option<u32> { ... } unsafe fn set_wake_callback( &mut self, cb: Option<unsafe extern "C" fn(*mut c_void)>, ctx: *mut c_void, ) { ... } fn supports_wake_callback(&self) -> bool { ... } fn ping_session(&mut self, timeout_ms: i32) -> Result<(), Self::Error> where Self::Error: From<TransportError> { ... } fn get_node_names( &mut self, visit: &mut dyn FnMut(&str, &str, Option<&str>) -> bool, ) -> Result<(), Self::Error> where Self::Error: From<TransportError> { ... } fn count_publishers( &mut self, topic_name: &str, ) -> Result<usize, Self::Error> where Self::Error: From<TransportError> { ... } fn count_subscribers( &mut self, topic_name: &str, ) -> Result<usize, Self::Error> where Self::Error: From<TransportError> { ... } fn get_topic_names_and_types( &mut self, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), Self::Error> where Self::Error: From<TransportError> { ... } fn get_service_names_and_types( &mut self, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), Self::Error> where Self::Error: From<TransportError> { ... } fn get_names_and_types_by_node( &mut self, kind: GraphEntityKind, node_name: &str, node_namespace: &str, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), Self::Error> where Self::Error: From<TransportError> { ... } fn get_endpoint_info_by_topic( &mut self, publishers: bool, topic_name: &str, visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool, ) -> Result<(), Self::Error> where Self::Error: From<TransportError> { ... }
}
Expand description

Transport session trait — the per-process anchor an RMW backend gives to the executor.

§Threading

&mut self on every method means the executor serialises all session calls onto a single thread. A backend may rely on this — no internal locking is required for create_* / close / drive_io. Publisher / subscriber / service handles created from the session, however, are typically used from worker threads and must carry their own synchronisation (see the Publisher / Subscription trait docs).

§Calling pattern

  1. Open the session (backend-specific factory; not on this trait).
  2. create_* for every entity at startup. Creating entities mid- flight after drive_io has run is allowed but not common.
  3. The executor calls drive_io periodically. Worker threads publish / receive in parallel.
  4. close once at shutdown. Entities must be dropped first.

Provided Associated Constants§

Source

const SERIALIZATION_FORMAT: &'static str = _

RFC-0088 — the serialization format this backend speaks, as ROS 2’s rmw_get_serialization_format() reports it (“One middleware can only have one encoding”).

Defaulted to CDR because every backend in tree except uORB speaks it, and a backend that speaks something else says so by overriding these two. They travel together: SERIALIZATION_FORMAT is the identity that crosses images (bridge config, tooling, the vtable slot) and SERIALIZATION_FORMAT_ID is the image-local discriminant used for the one-byte comparison a bridge makes at construction.

Source

const SERIALIZATION_FORMAT_ID: SerializationFormatId = nros_serdes::format::SerializationFormatId::Cdr

Image-local discriminant for Self::SERIALIZATION_FORMAT. Never persisted, never compared across images — see nros_serdes::format.

Required Associated Types§

Source

type Error

Error type for this session

Source

type PublisherHandle

Publisher handle type

Source

type SubscriptionHandle

Subscription handle type

Source

type ServiceHandle

Service server handle type

Source

type ClientHandle

Service client handle type

Required Methods§

Source

fn create_publisher( &mut self, topic: &TopicInfo<'_>, qos: QoSProfile, ) -> Result<Self::PublisherHandle, Self::Error>

Create a publisher bound to this session.

May allocate transport resources (zenoh declarations, DDS writers). Returns a handle that outlives the call but not the session — drop the handle before close().

Source

fn create_subscription( &mut self, topic: &TopicInfo<'_>, qos: QoSProfile, ) -> Result<Self::SubscriptionHandle, Self::Error>

Create a subscriber bound to this session.

Subscribers may start receiving immediately after creation if the transport supports late-joining publishers. Late messages are buffered up to the QoS depth.

Source

fn create_service( &mut self, service: &ServiceInfo<'_>, qos: QoSProfile, ) -> Result<Self::ServiceHandle, Self::Error>

Create a service server bound to this session. Replies are matched to requests by the sequence number returned from ServiceTrait::take_request.

qos is applied to both the request and reply endpoints (a service is two DDS topics; rmw uses one profile for both). The default is QoSProfile::services_default (RELIABLE+VOLATILE+KEEP_LAST(10)).

Source

fn create_client( &mut self, service: &ServiceInfo<'_>, qos: QoSProfile, ) -> Result<Self::ClientHandle, Self::Error>

Create a service client bound to this session.

qos is applied to both the request and reply endpoints (a service is two DDS topics; rmw uses one profile for both). The default is QoSProfile::services_default (RELIABLE+VOLATILE+KEEP_LAST(10)).

Source

fn close(&mut self) -> Result<(), Self::Error>

Close the session, releasing transport resources. All entity handles created from this session must already be dropped.

Source

fn drive_io(&mut self, timeout_ms: i32) -> Result<(), Self::Error>

Drive transport I/O (poll network, dispatch callbacks).

Both zenoh-pico and XRCE-DDS are pull-based: they require the application to periodically call this method to read from the network socket and dispatch incoming messages to subscriber buffers.

timeout_ms is the maximum time to wait for data (0 = non-blocking; negative values mean “block indefinitely” — see Phase 84.D7 for the planned migration to core::time::Duration).

Required. There is no default body — both shipped backends (zenoh and XRCE) must drive I/O, and a silent no-op default was a trap for third-party implementers. If your backend genuinely receives data via OS callbacks (push-based) and has nothing to do here, return Ok(()) explicitly.

Provided Methods§

Source

fn serialization_format(&self) -> &'static str

RFC-0088 — this session’s serialization format, as ROS 2’s rmw_get_serialization_format() reports it.

Per session, not per process. ROS 2’s function takes no handle because one process links one middleware; an Executor::open_multi image links two, so the answer must be asked of the session.

The default answers from Self::SERIALIZATION_FORMAT, which is right for any backend whose format is a compile-time fact. A session that dispatches to a backend chosen at run time — the C-ABI adapter, whose vtable carries the answer — overrides this to ask the backend.

Source

fn supported_qos_policies(&self) -> QoSPolicyMask

Phase 109 — report which QoS policies the active backend honours. The runtime validates requested QoS against this mask at entity-create time and returns TransportError::IncompatibleQos if the requested profile includes a policy the backend can’t enforce. No silent downgrade.

Default returns QoSPolicyMask::CORE — reliability + durability VOLATILE + history + depth. Backends override per supported policy.

Source

fn next_deadline_ms(&self) -> Option<u32>

Phase 110.0 — backend’s next internal-event deadline in milliseconds from now (lease keepalive, heartbeat, reader ACK-NACK timeout, etc.).

The executor caps its drive_io timeout against min(user_timeout, timer_deadline, this) so quiet links don’t wake early, see no user-visible work, and round-trip back into drive_io. Returns None when the backend has no internal deadlines or chooses not to expose them.

Default None keeps existing backends working unchanged; opt-in per backend.

Source

unsafe fn set_wake_callback( &mut self, cb: Option<unsafe extern "C" fn(*mut c_void)>, ctx: *mut c_void, )

Phase 124.B.1 — install (or clear, when cb.is_none()) the executor wake callback. The runtime calls this once per session after open with cb pointing at a runtime-owned function and ctx pointing at the executor’s wake state. The backend stores (cb, ctx) in its per-session state and calls cb(ctx) whenever its transport notification path fires (datagram arrival, condvar wake, etc.) — the runtime cb does flag-write + condvar-signal atomically, so a spin_once blocked on the wake condvar resumes immediately instead of waiting for the next poll iteration.

§Safety

When cb is Some, ctx must remain valid until the callback is cleared or the session is closed. The backend may invoke cb(ctx) from its transport notification path.

Default body: ignore the call. Poll-only backends (XRCE, bare-metal) leave the default in place; the executor still drains them on its deadline-bound cv-wait boundary.

Source

fn supports_wake_callback(&self) -> bool

Phase 130.4 — does this backend actually honour set_wake_callback?

true means the backend installs the callback and will fire it from its async notify path (worker thread, ISR, signalfd, …). false (the default) means set_wake_callback was a no-op — the executor must drive I/O for the caller’s full timeout because no async wake will pre-empt it.

The executor uses this to choose between the wake-primitive wait (NodeWake::wait_ms / std::Condvar::wait_timeout_while) and a direct drive_io(timeout_ms). Poll-only backends (XRCE-DDS-Client, bare-metal smoltcp) return false; event-driven backends (zenoh-pico with an RX task that invokes the callback on packet arrival) return true.

Source

fn ping_session(&mut self, timeout_ms: i32) -> Result<(), Self::Error>
where Self::Error: From<TransportError>,

Phase 124.F.1 — session-level connectivity probe.

Sends a wire-level round-trip probe and waits up to timeout_ms. Ok(()) on reply, Err(TransportError::Timeout) on no reply, Err(TransportError::Unsupported) when the backend can’t probe (DDS without participant introspection). Lesson from micro-ROS’s rmw_uros_ping_agent.

Default body: Err(Unsupported). Backends with a native ping API (zenoh: z_send_ping; XRCE: uxr_ping_agent_session_until_timeout) opt in by overriding.

Source

fn get_node_names( &mut self, visit: &mut dyn FnMut(&str, &str, Option<&str>) -> bool, ) -> Result<(), Self::Error>
where Self::Error: From<TransportError>,

phase-381 W3 — enumerate the nodes this session can see.

A VISITOR, not a returned collection, because upstream’s rcutils_string_array_t allocates two levels deep and there is no allocator at this seam. A caller-provided buffer is worse than it looks: the graph has no bound the CALLER can know. So the backend streams from state it already holds, peak extra RAM is one entry, and a caller with a bound stops early by returning false.

namespace and name are ROS names. enclave is None where the backend does not track one — which is what lets this one method answer both rmw_get_node_names and rmw_get_node_names_with_enclaves.

Every string is BORROWED for the duration of the call.

Must not block on the wire, and takes no timeout. It reports what has ALREADY arrived, so the first call after startup legitimately returns a partial graph — a backend feeds its view from drive_io. Letting this block was considered and rejected: it would stall the executor’s only thread inside an introspection call, on a runtime whose premise is that there is no other thread to do the work.

Default body: Err(Unsupported) — a backend with no graph (XRCE) says so, and the runtime can tell that from an empty graph.

Source

fn count_publishers(&mut self, topic_name: &str) -> Result<usize, Self::Error>
where Self::Error: From<TransportError>,

phase-381 W3 — how many publishers this session can see on topic_name.

topic_name is a ROS name (/chatter); the backend mangles as needed. Same warm-up caveat as Self::get_node_names: a count reflects what has already been discovered, so it can be low right after startup and is never a proof of absence.

Default body: Err(Unsupported) — distinct from Ok(0), which claims there are none.

Source

fn count_subscribers(&mut self, topic_name: &str) -> Result<usize, Self::Error>
where Self::Error: From<TransportError>,

phase-381 W3 — how many subscribers this session can see on topic_name. See Self::count_publishers for the caveats.

Source

fn get_topic_names_and_types( &mut self, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), Self::Error>
where Self::Error: From<TransportError>,

phase-381 W3 — every topic, with the types published or subscribed on it.

A visitor for the same reason as Self::get_node_names, and one call per distinct NAME: the contract hands over a name and the types on it, so a topic carrying two types is one visit with two entries, not two visits.

types_count may legitimately be 0 on a partially discovered graph — reporting the name without a type beats dropping it.

Default body: Err(Unsupported).

Source

fn get_service_names_and_types( &mut self, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), Self::Error>
where Self::Error: From<TransportError>,

phase-381 W3 — every service, with its types. As Self::get_topic_names_and_types, over servers and clients.

Source

fn get_names_and_types_by_node( &mut self, kind: GraphEntityKind, node_name: &str, node_namespace: &str, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), Self::Error>
where Self::Error: From<TransportError>,

phase-381 W3 — what ONE named node publishes / subscribes / serves / calls.

node_name and node_namespace are ROS names; 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 is “not seen yet”.

kind selects which of the four upstream *_by_node questions this answers. One method rather than four because the four differ ONLY by which entity kind they keep, and four trait methods would be four copies of one filter.

The trait keeps the ABI’s subscriber vocabulary because rmw_get_subscriber_names_and_types_by_node is what upstream rmw calls it and RFC-0054 makes the C headers the SSoT. The USER-facing spelling is per language and settled at that layer: rcl says subscriber, rclcpp and rclrs say subscription.

Default body: Err(Unsupported).

Source

fn get_endpoint_info_by_topic( &mut self, publishers: bool, topic_name: &str, visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool, ) -> Result<(), Self::Error>
where Self::Error: From<TransportError>,

phase-381 W3 — the endpoints on one topic, with the QoS each GRANTED.

publishers selects rmw_get_publishers_info_by_topic (true) or rmw_get_subscriptions_info_by_topic (false).

The granted profile is the whole reason a consumer asks: “why is nothing arriving” is usually a QoS incompatibility, and the REQUESTED profile cannot answer it. A backend that cannot read a remote’s granted QoS says so per field rather than echoing the request back — see rmw_topic_endpoint_info_t.

Default body: Err(Unsupported).

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementations on Foreign Types§

Source§

impl Session for CffiSession

Source§

fn serialization_format(&self) -> &'static str

The backend is chosen at run time here, so the trait’s compile-time default would be a guess. Ask the vtable, and fall back to the constant only for a backend that installed no body — which reads as “this backend has not said”, not as “cdr”.

Source§

fn get_node_names( &mut self, visit: &mut dyn FnMut(&str, &str, Option<&str>) -> bool, ) -> Result<(), TransportError>

phase-381 W5/W6 — forward to the backend’s slot; NULL means UNSUPPORTED.

Without this the graph slots were unreachable for every C backend: the trait default returns Unsupported, so a wired slot — cyclone’s, as of W5 — would never be called and would look implemented while being dead code. That is exactly the “a slot exists, therefore it works” overstatement issue 0800 measured, one layer above where 0800 found it.

NULL surfaces Unsupported rather than an empty enumeration, which is W6’s requirement: XRCE has no graph and must say “cannot tell you”, not “nothing is there”.

Source§

fn get_topic_names_and_types( &mut self, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), TransportError>

phase-381 / issue 0903 — the REST of the graph family.

W5 added get_node_names here and stopped, which made this the same defect one method wide: every other graph call fell through to the trait default and returned Unsupported, so the zenoh backend — which reaches the runtime through this vtable — answered node names and NOTHING else. Measured against a live rmw_zenoh_cpp talker: node enumeration worked and get_topic_names_and_types returned empty, because the entity query was never even STARTED.

Fixing one method of eleven is how the first version passed every unit test in the phase.

Source§

fn get_names_and_types_by_node( &mut self, kind: GraphEntityKind, node_name: &str, node_namespace: &str, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), TransportError>

phase-381 W4, wired here by the live acceptance run.

The zenoh shim implemented this and get_endpoint_info_by_topic all along; CffiSession never dispatched them, so both fell through to the trait default and every caller got Unsupported. That is issue 0903’s third defect for the SIX slots the 0903 fix did not cover — it wired the five that had a failing symptom in front of it and left these, and check-rmw-slot-producers calls them produced either way because it asks whether a slot has a producer, not whether anything reaches it.

Found by graph_interop.rs on its first run against a real peer, which is the only place it could have been found.

Source§

fn get_endpoint_info_by_topic( &mut self, publishers: bool, topic_name: &str, visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool, ) -> Result<(), TransportError>

The endpoints on a topic — see Self::get_names_and_types_by_node for why this was unreachable until the live run.

Source§

fn supported_qos_policies(&self) -> QoSPolicyMask

Phase 115.K.2.5.1.2 — declare a permissive QoS-policy mask here so backends behind the cffi vtable don’t get rejected by the runtime’s pre-validate step before they ever see the create_publisher / create_subscription call. The vtable doesn’t expose a per-backend policy mask yet; until it does, the cffi route has to assume the registered backend supports the union of every policy any nros-supported RMW honours. Backends that don’t support a policy MUST surface NROS_RMW_RET_INCOMPATIBLE_QOS from create_publisher etc. to keep the no-silent-degradation contract.

TODO 115.K.2.x: extend nros_rmw_vtable_t with a supported_qos_policies() callback so the runtime queries the backend instead of guessing.

Source§

type Error = TransportError

Source§

type PublisherHandle = CffiPublisher

Source§

type SubscriptionHandle = CffiSubscription

Source§

type ServiceHandle = CffiService

Source§

type ClientHandle = CffiClient

Source§

fn create_publisher( &mut self, topic: &TopicInfo<'_>, qos: QoSProfile, ) -> Result<CffiPublisher, TransportError>

Source§

fn create_subscription( &mut self, topic: &TopicInfo<'_>, qos: QoSProfile, ) -> Result<CffiSubscription, TransportError>

Source§

fn create_service( &mut self, service: &ServiceInfo<'_>, qos: QoSProfile, ) -> Result<CffiService, TransportError>

Source§

fn create_client( &mut self, service: &ServiceInfo<'_>, qos: QoSProfile, ) -> Result<CffiClient, TransportError>

Source§

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

Source§

fn drive_io(&mut self, timeout_ms: i32) -> Result<(), TransportError>

Source§

fn next_deadline_ms(&self) -> Option<u32>

Source§

unsafe fn set_wake_callback( &mut self, cb: Option<unsafe extern "C" fn(*mut c_void)>, ctx: *mut c_void, )

Source§

fn supports_wake_callback(&self) -> bool

Source§

fn get_service_names_and_types( &mut self, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), TransportError>

Source§

fn count_publishers( &mut self, topic_name: &str, ) -> Result<usize, TransportError>

Source§

fn count_subscribers( &mut self, topic_name: &str, ) -> Result<usize, TransportError>

Source§

fn ping_session(&mut self, timeout_ms: i32) -> Result<(), TransportError>

Implementors§