Skip to content

Zero-copy (SHM) silently skipped for large messages: generic publish path uses inaccurate size estimate #197

@find1dream

Description

@find1dream

Summary

ZPub::publish is generic over T: ZMessage and sizes the SHM buffer via msg.estimated_serialized_size(). Codegen emits the accurate CDR-based estimate only as an inherent method, so the generic context resolves the ZMessage trait default instead: size_of::<Self>() * 2 + 4 (~244 B for PointCloud2, regardless of actual payload size).

Impact

  • Large sensor messages (camera images, point clouds) estimate far below the SHM threshold and silently fall back to the regular serialization path - zero-copy never kicks in for exactly the messages it's meant for.
  • Workaround attempts make it worse: lowering the SHM threshold causes the undersized SHM buffer to overflow, since the buffer is allocated from the same bad estimate.

Reproduction

  1. Configure an SHM threshold of 64 KB.
  2. Publish a 640x480 rgb8 Image (>64 KB actual payload).
  3. Observe the publish span: used_shm = false — the image estimates ~304 B
    via the trait default and stays below the threshold.

Root cause

The accurate CdrSerializedSize walk exists, but only as an inherent method on the generated type. In a generic T: ZMessage context, method resolution picks the trait's default implementation, which is a crude size_of-based guess unrelated to the serialized payload.

Suggested fix

Override estimated_serialized_size in the blanket impl<T> ZMessage to use the accurate CdrSerializedSize walk. ShmWriter truncates to bytes actually written, so a small alignment pad (+16) on the estimate is free on the wire.

  File: crates/hiroz/src/msg.rs — the blanket impl<T> ZMessage for codegen'd types:

  impl<T> ZMessage for T
  where
      T: hiroz_cdr::CdrSerialize
          + hiroz_cdr::CdrDeserialize
          + hiroz_cdr::CdrSerializedSize
          // ... existing bounds ...
          + 'static,
  {
      type Serdes = NativeCdrSerdes<T>;

      /// Accurate size for SHM allocation.
      ///
      /// The trait default (`size_of::<Self>() * 2 + 4`) ignores dynamic-field
      /// payloads, so in the generic publish path a `PointCloud2`/`Image` was
      /// estimated at ~244 B regardless of its real size — below the SHM
      /// threshold, so large sensor messages silently skipped zero-copy; and when
      /// the threshold was lowered to admit them, `serialize_to_shm` allocated the
      /// undersized buffer and the serializer overflowed it.
      ///
      /// Every type reaching this blanket impl already implements
      /// `CdrSerializedSize` (the same accurate, codegen-generated size walk used
      /// by serialization), so use it: 4 B CDR encapsulation header + the body
      /// size measured from body offset 0. The `+ 16` absorbs any alignment slack
      /// so the buffer is always a safe upper bound; `ShmWriter` truncates the
      /// published `ZBuf` to the bytes actually written, so the pad costs nothing
      /// on the wire.
      fn estimated_serialized_size(&self) -> usize {
          4 + hiroz_cdr::CdrSerializedSize::cdr_serialized_size(self, 0) + 16
      }
  }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions