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
- Configure an SHM threshold of 64 KB.
- Publish a 640x480
rgb8 Image (>64 KB actual payload).
- 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
}
}
Summary
ZPub::publishis generic overT: ZMessageand sizes the SHM buffer viamsg.estimated_serialized_size(). Codegen emits the accurate CDR-based estimate only as an inherent method, so the generic context resolves theZMessagetrait default instead:size_of::<Self>() * 2 + 4(~244 B forPointCloud2, regardless of actual payload size).Impact
Reproduction
rgb8Image(>64 KB actual payload).used_shm = false— the image estimates ~304 Bvia the trait default and stays below the threshold.
Root cause
The accurate
CdrSerializedSizewalk exists, but only as an inherent method on the generated type. In a genericT: ZMessagecontext, method resolution picks the trait's default implementation, which is a crudesize_of-based guess unrelated to the serialized payload.Suggested fix
Override
estimated_serialized_sizein the blanketimpl<T> ZMessageto use the accurateCdrSerializedSizewalk.ShmWritertruncates to bytes actually written, so a small alignment pad (+16) on the estimate is free on the wire.