Skip to content

fix(hu): resolve the message schema before subscribe returns - #311

Merged
YuanYuYuan merged 1 commit into
mainfrom
fix/hu-schema-discovery
Aug 21, 2026
Merged

YuanYuYuan merged 1 commit into
mainfrom
fix/hu-schema-discovery

Conversation

@YuanYuYuan

@YuanYuYuan YuanYuYuan commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Last slice of 5, splitting the original #311. #317, #318, #319 and #320 are merged. This one now targets main.

Fixes #297. Fixes #298. Fixes #299.

What this does

The plugin host resolves a message schema before subscribe() returns. The subscribe path also gains a fallback to a local .msg file.

What fails without this

The host resolved the schema on a spawned task. subscribe() had already returned Ok by then. The plugin's failure branch could therefore never run. The dropped channel sender looked like an idle topic.

check before after
hu meter echo /chatter no output, exit 0 prints the decoded message
a publisher without the type description service no output, exit 0 exit 1, and names the type it could not resolve
hu meter delay runs forever reports the failure
a message that fails to decode skipped in silence reported once loudly, then at debug

Resolution now happens before the host mints the resource. The host allocates the rep only after the subscription exists.

Discovery fails against any node that lacks the type description service. The host then falls back to a .msg file on HIROZ_MSG_PATH. The type name still comes from the graph, because subscribe carries only a topic.

The fallback refuses a mismatched schema, and accepts an unverifiable one

CDR is positional. A skewed schema therefore yields plausible but wrong field values, not a decode error. The fallback compares the local hash against the advertised hash.

advertised hash action
equal to the local hash decode
different refuse, and name both
not advertised at all warn, then decode

The third row matters most. A Humble node advertises nothing. Any peer built without type hashing also advertises nothing. An earlier version treated that as a mismatch, which broke every cross-distro case. Those cases are the main reason to have a disk fallback.

Compare hash values, never rendered strings. hiroz_protocol's renderer is gated on no-type-hash. Under that feature it returns one constant for every value, so a string comparison passes on anything. The diagnostic renders through an ungated formatter for the same reason.

Why the guest did not report the failure before

wasmtime gives each guest an epoch budget in wall clock. A blocking host call spends that budget while the guest does not run. A 5 s discovery therefore trapped the guest on return, before it could run the error branch that the host had just given it. A reported failure became a hang.

#319 fixed that. It is merged, so HostBlockGuard suspends the epoch ticker across this wait.

Breaking changes

what changes who is affected before to after action
hu meter echo <topic> on a topic with no resolvable schema anything that scripts its exit status exit 0 after printing nothing, to exit non-zero with the reason drop || true guards. A silent success was indistinguishable from an idle topic.

hz and bw still work on a topic whose type does not resolve. Their numbers come from a raw counting subscriber that never decodes. The typed subscription they used to hold is now best-effort, and it moves out of the mode. The host keeps it only so that hz and bw stay visible in the ROS graph.

hu meter echo now writes subscribed to <topic>; waiting for messages to stderr. A subscribe that succeeds and then prints nothing looks the same as a broken one.

What this does not reach

#300 records the one case. A publisher may advertise no type at all. The fallback needs a type name, and the graph is its only source. That case needs an addition to the WIT interface.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Resolves schemas synchronously for WASM subscriptions, adds local .msg fallback, and improves runtime-typed example support.

Changes:

  • Propagates schema failures before subscribe() returns and pauses epoch accounting during host waits.
  • Adds hash-checked local schema fallback and enables type-description services in publishing examples.
  • Improves diagnostics, regression tests, and Axum route compatibility.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/hiroz/src/node.rs Documents type-description opt-in.
crates/hiroz/src/lifecycle/node.rs Adds lifecycle builder pass-through.
crates/hiroz/src/dynamic/type_info.rs Publicizes DDS type normalization.
crates/hiroz/src/dynamic/mod.rs Re-exports type normalization.
crates/hiroz/src/context.rs Initializes lifecycle option.
crates/hiroz/examples/z_pubsub.rs Enables schema service and renames nodes.
crates/hiroz/examples/z_custom_message.rs Enables schema service.
crates/hiroz/examples/z_cache/talker.rs Enables schema service.
crates/hiroz/examples/twist_pub.rs Enables schema service.
crates/hiroz/examples/shm_pointcloud2.rs Enables schema service.
crates/hiroz/examples/lifecycle/talker.rs Uses lifecycle pass-through.
crates/hiroz/examples/laser_scan.rs Enables schema service.
crates/hiroz/examples/demo_nodes/talker.rs Enables schema service.
crates/hiroz-union/src/plugin/wasm/mod.rs Adds epoch suspension guard.
crates/hiroz-union/src/plugin/wasm/host/ros.rs Resolves schemas synchronously with fallback.
crates/hiroz-union/src/modes/web.rs Fixes Axum wildcard routing.
crates/hiroz-union/src/core/logger.rs Includes hu in default filters.
crates/hiroz-union/plugins/hu-meter/src/lib.rs Updates subscription behavior and status output.
crates/hiroz-tests/tests/hu_meter.rs Tests unresolved-schema failure reporting.
Suppressed comments (3)

crates/hiroz-union/src/plugin/wasm/host/ros.rs:38

  • Only inspecting .first() can miss a usable type. Graph ordering is not guaranteed, so when the first publisher has type_info: None and a later publisher advertises the type, the fallback incorrectly reports that no endpoint advertises one. Scan all returned entities for the first endpoint with Some(type_info).
                .get_entities_by_topic(kind, topic)
                .first()
                .and_then(|ent| match ent.as_ref() {
                    Entity::Endpoint(ep) => ep.type_info.clone(),
                    _ => None,

crates/hiroz-union/src/plugin/wasm/host/ros.rs:91

  • The graph stores qualified topic names, but this fallback queries it with the caller's raw topic. create_dyn_sub_auto("chatter", ...) correctly qualifies to /chatter; after discovery fails, this lookup searches for chatter, misses the live publisher, and prevents the local .msg fallback. Qualify the topic using the node namespace/name before querying the graph while still passing the original topic to the subscriber builder.
    let Some(ti) = live_topic_type_info(graph, topic) else {

crates/hiroz-union/src/plugin/wasm/host/ros.rs:566

  • call_raw has the same process-wide suspension issue: the guest controls timeout_ms up to about 49 days, and this guard disables epoch interruption for all stores for that entire wait. Cap the wait with a host-defined maximum or account for blocking time only on the calling store rather than pausing the shared ticker.
            let _epoch = super::super::HostBlockGuard::enter();
            replies.recv()

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

/// against a hand-built `Graph` without a wasmtime store.
fn live_topic_type_info(graph: &Graph, topic: &str) -> Option<hiroz_protocol::TypeInfo> {
use hiroz_protocol::{EndpointKind, Entity};
[EndpointKind::Publisher, EndpointKind::Subscription]
Comment thread crates/hiroz-union/src/plugin/wasm/host/ros.rs
// path uses (`encode_yaml_to_cdr`). The type *name* still comes
// from the graph; only the schema body comes from disk.
tracing::debug!("WASM plugin: schema discovery failed for {topic}: {e}");
match dyn_sub_from_local_msg(&node, &self.engine.graph, &topic) {
@YuanYuYuan
YuanYuYuan force-pushed the fix/hu-schema-discovery branch from 030a7dd to 237914d Compare August 21, 2026 10:42
@YuanYuYuan
YuanYuYuan changed the base branch from main to feat/hiroz-type-desc-optin August 21, 2026 10:42
@YuanYuYuan
YuanYuYuan force-pushed the fix/hu-schema-discovery branch from 237914d to d3b55c7 Compare August 21, 2026 12:17
@YuanYuYuan
YuanYuYuan force-pushed the feat/hiroz-type-desc-optin branch from 542c04b to 49d9162 Compare August 21, 2026 12:17
@YuanYuYuan
YuanYuYuan force-pushed the fix/hu-schema-discovery branch from d3b55c7 to bd2615d Compare August 21, 2026 13:27
@YuanYuYuan
YuanYuYuan force-pushed the feat/hiroz-type-desc-optin branch from 49d9162 to 52d29dc Compare August 21, 2026 13:27
@YuanYuYuan
YuanYuYuan force-pushed the fix/hu-schema-discovery branch from bd2615d to 862d9ed Compare August 21, 2026 14:12
@YuanYuYuan
YuanYuYuan force-pushed the feat/hiroz-type-desc-optin branch from 52d29dc to 207339b Compare August 21, 2026 14:12
Base automatically changed from feat/hiroz-type-desc-optin to main August 21, 2026 15:06
The host resolved the schema on a spawned task, after `subscribe()` had
already returned Ok. The plugin's failure branch could never fire, and
the dropped channel sender read as an idle topic: `hu meter echo` on a
topic it could not decode printed nothing and exited 0, which is
indistinguishable from a topic with no traffic.

Resolution now happens before the resource is minted, and the rep is
allocated only once the subscription exists. When discovery fails --
which it does against any node built without the type description
service -- the host falls back to a `.msg` on HIROZ_MSG_PATH, taking the
type *name* from the graph, because `subscribe` carries only a topic.

The disk fallback refuses a schema whose hash disagrees with what the
topic advertises: CDR is positional, so a skewed schema yields
plausible, wrong values rather than a decode error. "Advertises no hash"
is a third state, not a mismatch -- a Humble node or any peer built
without type hashing cannot be verified either way, and refusing there
broke the cross-distro case the fallback exists for.

Decode failures on the stream are now reported, first loudly and then at
debug, for the same reason: a stream that decodes to nothing looks idle.

hz and bw keep working on a topic whose type cannot be resolved. Their
numbers come from a raw counting subscriber that never decodes, so the
typed subscription they used to hold becomes best-effort and moves out
of the mode, held only to keep them visible in the ROS graph.

Fixes #297. Fixes #298. Fixes #299.
@YuanYuYuan
YuanYuYuan force-pushed the fix/hu-schema-discovery branch from 862d9ed to 1040021 Compare August 21, 2026 15:08
@YuanYuYuan
YuanYuYuan merged commit ac112c0 into main Aug 21, 2026
29 checks passed
@YuanYuYuan
YuanYuYuan deleted the fix/hu-schema-discovery branch August 21, 2026 16:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants