Skip to main content

ServiceTrait

Trait ServiceTrait 

Source
pub trait ServiceTrait {
    type Error;

    // Required methods
    fn take_request<'a>(
        &mut self,
        buf: &'a mut [u8],
    ) -> Result<Option<ServiceRequest<'a>>, Self::Error>;
    fn send_response(
        &mut self,
        sequence_number: i64,
        data: &[u8],
    ) -> Result<(), Self::Error>;

    // Provided methods
    fn has_request(&self) -> bool { ... }
    fn register_waker(&self, _waker: &Waker) { ... }
    fn refused_request_len(&self) -> Option<usize> { ... }
    fn handle_request<S>(
        &mut self,
        req_buf: &mut [u8],
        reply_buf: &mut [u8],
        handler: impl FnOnce(&<S as RosService>::Request) -> <S as RosService>::Reply,
    ) -> Result<bool, Self::Error>
       where S: RosService,
             Self::Error: From<TransportError> { ... }
    fn handle_request_boxed<S>(
        &mut self,
        req_buf: &mut [u8],
        reply_buf: &mut [u8],
        handler: impl FnOnce(&<S as RosService>::Request) -> Box<<S as RosService>::Reply>,
    ) -> Result<bool, Self::Error>
       where S: RosService,
             Self::Error: From<TransportError> { ... }
    fn handle_request_raw(
        &mut self,
        req_buf: &mut [u8],
        reply_buf: &mut [u8],
        handler: impl FnOnce(&mut CdrReader<'_>, &mut CdrWriter<'_>) -> Result<(), TransportError>,
    ) -> Result<bool, Self::Error>
       where Self::Error: From<TransportError> { ... }
    fn request_subscription_actual_qos(&self) -> QoSProfile { ... }
    fn response_publisher_actual_qos(&self) -> QoSProfile { ... }
}
Expand description

Service server trait for handling requests.

§Threading

&mut self on take_request and send_response — the executor owns the server while a request is being handled. Handler bodies run synchronously on the executor thread; long handlers should dispatch work to a worker queue and reply later via the recorded sequence_number.

§Calling pattern

  1. Executor calls take_request(buf).
  2. If Some(req) returned, decode, run handler, encode reply.
  3. send_response(req.sequence_number, &reply_buf).

sequence_number is the canonical request → reply correlation token; backends derive it from the wire-level metadata (zenoh query id, DDS sample identity).

Required Associated Types§

Source

type Error

Error type for service operations

Required Methods§

Source

fn take_request<'a>( &mut self, buf: &'a mut [u8], ) -> Result<Option<ServiceRequest<'a>>, Self::Error>

Try to receive a service request into buf (non-blocking).

On success returns a ServiceRequest that borrows from buf. The borrow is released when the returned struct is dropped — typically before send_response is called, since send_response takes &mut self.

Source

fn send_response( &mut self, sequence_number: i64, data: &[u8], ) -> Result<(), Self::Error>

Send a reply for the given sequence number. Non-blocking from the application’s perspective; the backend may queue the reply for transport-level transmission.

Provided Methods§

Source

fn has_request(&self) -> bool

Check if a request is available without consuming it.

Non-destructive. Default returns true (always assume one may be available); backends should override with a real check.

Source

fn register_waker(&self, _waker: &Waker)

Phase 122.3.c.6.e — register a Waker for event-driven service servers. Mirrors the matching method on SubscriberTrait / ClientTrait. Backends that surface incoming-request notifications wake waker when has_request() flips true. Default: no-op (backends without wake support ignore — caller falls back to polling).

Source

fn refused_request_len(&self) -> Option<usize>

Issue 1632 — how many bytes the request the most recent take_request REFUSED as too big for the caller’s buffer needed.

Subscription::refused_sample_len’s contract, for the service server: read it right after a take returns BufferTooSmall; None means the backend does not know (the default), never “zero bytes”.

Source

fn handle_request<S>( &mut self, req_buf: &mut [u8], reply_buf: &mut [u8], handler: impl FnOnce(&<S as RosService>::Request) -> <S as RosService>::Reply, ) -> Result<bool, Self::Error>
where S: RosService, Self::Error: From<TransportError>,

Handle a service request with typed messages

Source

fn handle_request_boxed<S>( &mut self, req_buf: &mut [u8], reply_buf: &mut [u8], handler: impl FnOnce(&<S as RosService>::Request) -> Box<<S as RosService>::Reply>, ) -> Result<bool, Self::Error>
where S: RosService, Self::Error: From<TransportError>,

Handle a service request where the handler returns Box<S::Reply>

Identical to handle_request but the handler returns a heap-allocated reply. This is needed for services with large response types (e.g., parameter services where Vec<ParameterValue, 64> is ~1MB+) that would overflow the stack.

Source

fn handle_request_raw( &mut self, req_buf: &mut [u8], reply_buf: &mut [u8], handler: impl FnOnce(&mut CdrReader<'_>, &mut CdrWriter<'_>) -> Result<(), TransportError>, ) -> Result<bool, Self::Error>
where Self::Error: From<TransportError>,

Handle one request by STREAMING: the handler reads fields off the wire and writes the reply’s fields straight back, so neither the request nor the reply is ever materialised as a value.

phase-382 W1’. handle_request_boxed above boxes the REPLY because the parameter responses are enormous — GetParametersResponse measures 1,176,072 bytes. It does not box the REQUEST, which is deserialized by value into a stack local one line above the handler, and SetParametersRequest measures 1,192,968 bytes. So every ros2 param set against a node put a 1.19 MB local on the calling task’s stack — larger than the reply the boxing exists for, on every platform, with param-services live on Zephyr.

Streaming removes both, and removes the alloc requirement with them: the whole value is never needed, because serialisation happens on the line after construction. Three things make it safe rather than clever:

  • No rcl_interfaces message uses a DHEADER — every Serialize impl is plain sequential CDR, so hand-written field writes are byte-identical to the generated ones. (If a future message gains XCDR2 extensibility this stops being true for THAT message; see RFC-0055.)
  • req_buf and reply_buf are disjoint, so a handler can hold the reader and the writer at once.
  • CdrReader::read_string borrows out of req_buf rather than copying, so a handler can look a name up without a buffer of its own.

The cost is that the hand-written writes can drift from the generated Serialize. Guard it with a round-trip test that deserialises the streamed bytes back into the generated type — the by-value handler makes a good test-only oracle.

No alloc, deliberately: this is the seam that lets param-services and lifecycle-services build without it.

Source

fn request_subscription_actual_qos(&self) -> QoSProfile

The profile this entity is ACTUALLY running — what the backend GRANTED, not what the call site requested.

Upstream’s rmw_service_request_subscription_get_actual_qos. The two differ whenever a backend serves something other than what it was asked for, and the difference is exactly what answers “why is nothing arriving”: a RELIABLE reader does not match a BEST_EFFORT writer, and a clamped history depth drops samples the caller believed were kept.

Per-field, and an unreportable field is an ABSENCE. A backend that determines four policies and not the fifth returns the four and leaves the fifth at its Unknown variant. Never echo the request into a field you did not read back — that is the inverse of this method’s meaning, and it is what the six C-ABI slots did until this phase.

The default is QoSProfile::QOS_PROFILE_UNKNOWN — every field an absence — which is the honest answer for a backend with no read-back at all (XRCE has none; zenoh-pico’s QoS is per-message flags). It is NOT the request, and it is NOT QOS_PROFILE_DEFAULT.

Source

fn response_publisher_actual_qos(&self) -> QoSProfile

The profile this entity is ACTUALLY running — what the backend GRANTED, not what the call site requested.

Upstream’s rmw_service_response_publisher_get_actual_qos. The two differ whenever a backend serves something other than what it was asked for, and the difference is exactly what answers “why is nothing arriving”: a RELIABLE reader does not match a BEST_EFFORT writer, and a clamped history depth drops samples the caller believed were kept.

Per-field, and an unreportable field is an ABSENCE. A backend that determines four policies and not the fifth returns the four and leaves the fifth at its Unknown variant. Never echo the request into a field you did not read back — that is the inverse of this method’s meaning, and it is what the six C-ABI slots did until this phase.

The default is QoSProfile::QOS_PROFILE_UNKNOWN — every field an absence — which is the honest answer for a backend with no read-back at all (XRCE has none; zenoh-pico’s QoS is per-message flags). It is NOT the request, and it is NOT QOS_PROFILE_DEFAULT.

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 ServiceTrait for CffiService

Source§

fn request_subscription_actual_qos(&self) -> QoSProfile

What the REQUEST endpoint — the subscription this service takes requests on — was granted.

Source§

fn response_publisher_actual_qos(&self) -> QoSProfile

What the RESPONSE endpoint — the publisher this service answers on — was granted. Not necessarily the same profile: one request built both endpoints, and they negotiate against different peers.

Source§

type Error = TransportError

Source§

fn has_request(&self) -> bool

Source§

fn take_request<'a>( &mut self, buf: &'a mut [u8], ) -> Result<Option<ServiceRequest<'a>>, TransportError>

Source§

fn refused_request_len(&self) -> Option<usize>

Source§

fn send_response( &mut self, sequence_number: i64, data: &[u8], ) -> Result<(), TransportError>

Implementors§