Repository navigation
hiroz typed and dynamic subscribers receive nothing from buffer-aware rmw_zenoh publishers #371
Description
Part of #370. Reported in #369 for a raw subscriber. The raw subscriber is #372.
Problem
A hiroz typed or dynamic subscriber on a buffer-typed topic declares a liveliness token without a backends: field. The rmw_zenoh publisher routes such a subscriber to <base>/_buf/<gid>. Hiroz never reads that key, so the subscriber receives nothing. It stays matched, and nothing logs an error. Messages without buffer fields are not affected.
Fixed by
| Pull request | What it does |
|---|---|
| #384 | Adds BufferGroup { Auto, Cpu, Off } and with_buffer_group on the subscriber builder and the cache builder. The default in this PR is Off. |
| #387 | Changes the default to Auto, so a subscriber joins without a code change. It also documents the access-control effect of the wider subscription. |
The issue is fixed when both pull requests merge.
Mechanism
sequenceDiagram
autonumber
participant Sub as hiroz subscriber
participant Z as Zenoh
participant Pub as rmw_zenoh publisher
Sub->>Z: declare token (no backends: field)
Sub->>Z: subscribe to base key
Z->>Pub: token appears
Note over Pub: not a CPU-group member, so it counts as an accelerated subscriber
Pub->>Z: put on base/_buf/gid
Note over Sub: never subscribed to base/_buf/gid, receives nothing
rect rgb(235, 245, 235)
Note over Sub,Pub: with a joined subscriber
Sub->>Z: declare token ending in backends:cpu:
Sub->>Z: subscribe to base/**, keep the base key and base/_buf_cpu
Pub->>Z: put on base/_buf_cpu
Z->>Sub: every message
end
Reproducer
Use a container with ros-lyrical-rmw-zenoh-cpp 0.10.6.
- Start the router:
ros2 run rmw_zenoh_cpp rmw_zenohd. - Start a publisher:
ros2 topic pub -r 5 /cloud sensor_msgs/msg/PointCloud2 "{}". - Start a hiroz typed subscriber on
/cloud, built frommainbefore the fix. - Count the messages for 10 s.
Before and after
| before | with the shipped design | |
|---|---|---|
token of a typed subscriber on a message with an unbounded uint8[], such as PointCloud2 |
no backends: field |
ends in /backends:cpu: |
token of a subscriber on a message that nests one, such as visualization_msgs/Marker |
no backends: field |
ends in /backends:cpu: |
dynamic subscriber, with a schema set by with_dyn_schema |
no backends: field |
joins when the schema has an unbounded uint8[] or char[], directly or nested |
| subscriber with no message definition | no backends: field |
joins, because hiroz cannot tell whether the topic carries buffers |
| cache subscriber | no backends: field |
joins by the same rule, and with_buffer_group on the cache builder overrides it |
| subscription | on the base key | on <base>/**, other keys dropped in process |
| publisher routes it to | <base>/_buf/<gid>, which nobody reads |
<base>/_buf_cpu |
| the subscriber receives | nothing | every message |
message with no byte field, such as Float64MultiArray |
unchanged | unchanged: stays out under Auto |
| transient-local subscriber | unchanged | unchanged: stays out, even under Cpu |
key format other than rmw_zenoh's |
unchanged | unchanged: stays out, even under Cpu |
with_buffer_group(BufferGroup::Off) |
not available | stays out |
Behaviour
- The default in feat(hiroz): join the rmw_zenoh cpu buffer group #384 is
Off. The default in feat(hiroz): join the buffer group by default #387 isAuto. - A joined subscriber declares
<base>/**. A router rule that allows only the exact topic key starves it, andbuildstill returnsOk. A call towith_buffer_group(BufferGroup::Off)avoids this. - A subscriber without a
backends:field receives nothing from a buffer-aware publisher until Publish on the base key to subscribers without abackends:field ros2/rmw_zenoh#1078 ships. This coversOff, transient-local subscribers and other key formats. - The Python binding has a
buffer_groupargument in feat(hiroz-py): add a buffer_group argument #388, and the action client status subscriber stays outside the group there. The C and Go bindings are in feat(ffi): join the cpu group from c and go #389.
Evidence on main
The runs use the released ros-lyrical-rmw-zenoh-cpp 0.10.6 with three runs in each cell. The older releases are ros-humble-rmw-zenoh-cpp 0.1.11, ros-kilted-rmw-zenoh-cpp 0.6.8 and ros-jazzy-rmw-zenoh-cpp 0.2.11, with one run in each cell. The publisher is ros2 topic pub -r 5 with CPU buffers, so complete delivery in a 10 s window is 50 or 51. One process holds a typed subscriber on the topic. The probe was built from hiroz main before the fix. The Humble probe uses the humble feature. The Kilted and Jazzy probes use the default feature set, which is the Jazzy one. The probes count messages and do not compare payload contents.
Lyrical 0.10.6, three runs per cell, messages received on main:
| Publisher message | main |
|---|---|
UInt8MultiArray |
0, 0, 0 |
PointCloud2 |
0, 0, 0 |
visualization_msgs/Marker, which nests MeshFile |
0, 0, 0 |
Float64MultiArray (control, no buffer field) |
51, 50, 51 |
The 0 cells have two controls. In the same cell, a native ros2 topic hz subscriber read 5.000 Hz, so the publisher was producing. The same probe binary received 50 to 51 messages on Float64MultiArray, so the probe and the router work.
Older releases, one run per cell, typed subscriber, messages received on main:
| Release | Publisher message | main |
|---|---|---|
| Humble 0.1.11 | PointCloud2 |
50 |
| Humble 0.1.11 | String |
50 |
| Kilted 0.6.8 | PointCloud2 |
50 |
| Kilted 0.6.8 | String |
50 |
| Jazzy 0.2.11 | PointCloud2 |
50 |
| Jazzy 0.2.11 | String |
50 |
On these releases main already delivers every message, so they show no loss. The loss follows the rmw_zenoh release and the message definition.
The rule that decides which messages carry buffers follows the message definition. In Lyrical an unbounded uint8[] is a rosidl::Buffer, directly or in a nested message. The reference is the compiled typesupport of one Lyrical install: its has_buffer_fields function, called for every message of the 223 packages that ship .msg files.
| Rule | Messages compared | Mismatches |
|---|---|---|
the exact rule: unbounded uint8[] or char[], directly or nested |
2227 | 0 |
| the same rule without nested messages (control) | 2227 | 45 |
| a fixed list of five messages (baseline) | 2227 | 113, all missed buffer messages |
118 of the 2227 messages have buffer fields. They include visualization_msgs/Marker, MarkerArray, the four interactive-marker messages, stereo_msgs/DisparityImage, map_msgs/PointCloud2Update and nav2_msgs/Costmap.
The comparison covers the exact rule, not the hiroz code. Hiroz applies the rule to the message schema, and a schema cannot tell byte from uint8. Hiroz therefore also counts an unbounded byte[]. In the bundled Jazzy .msg files, five messages count by the schema rule and not by the exact rule: std_msgs/ByteMultiArray, example_interfaces/ByteMultiArray, rcl_interfaces/ParameterValue, rcl_interfaces/Parameter and rcl_interfaces/ParameterEvent. A subscriber on one of them joins without need, and receives each message once.
Activity
[-]Typed and dynamic subscribers receive nothing from buffer-aware rmw_zenoh publishers[/-][+]hiroz typed and dynamic subscribers receive nothing from buffer-aware rmw_zenoh publishers[/+]