Skip to content

Add type hash to names_and_types (#356) - #421

Open
tisomi wants to merge 1 commit into
ros2:rollingfrom
tisomi:rolling
Open

Add type hash to names_and_types (#356)#421
tisomi wants to merge 1 commit into
ros2:rollingfrom
tisomi:rolling

Conversation

@tisomi

@tisomi tisomi commented Jul 2, 2026

Copy link
Copy Markdown

Description

This PR extends rmw_names_and_types_t with a type_hashes parallel array, so the
ROS graph query APIs (rmw_get_topic_names_and_types, rmw_get_service_names_and_types,
and the per-node variants) can expose the type hash of each name/type pair alongside the
existing names and types arrays. The new array is indexed in lockstep with the others:
type_hashes[i][j] corresponds to types[i].data[j].

Alongside the struct change, the lifecycle helpers were updated and hardened:

  • rmw_names_and_types_init now enforces the documented zero-initialized precondition on
    its output argument, returning RMW_RET_INVALID_ARGUMENT when it is not zero-initialized.
    Previously the precondition was documented but unchecked, so passing a populated struct
    would silently leak. It also allocates and unwinds the new type_hashes array, restoring
    the struct to the zero state on any allocation failure so the caller can re-init without
    leaking.
  • rmw_names_and_types_fini now cleans up the type_hashes array and finalizes partially-initialized structs — e.g. one left by a middleware
    populate that inits the full struct then fails mid-fill — freeing the new type_hashes
    array row-by-row (skipping rows still NULL) instead of leaking it. The unconditional
    names cleanup is a no-op on a zero-initialized names array.

Updated the doxygen across the affected headers to mention type hashes.

New fault-injection tests cover the type_hashes allocation-failure path and assert the
output struct is restored to the zero state after RMW_RET_BAD_ALLOC, so the caller can
re-init without leaks.

Note: this extends a public struct and is therefore an ABI break.

This is the interface half of the change; populating the field is handled in the companion
middleware PRs (see Additional Information).

Related: Issue #356

Is this user-facing behavior change?

Yes.

  • rmw_names_and_types_t gains a type_hashes member — an ABI break; downstream consumers
    must be rebuilt against the new layout.
  • Once paired with the middleware PRs, the graph query APIs report the type hash of each
    name/type pair.
  • rmw_names_and_types_init now rejects a non-zero-initialized output argument with
    RMW_RET_INVALID_ARGUMENT. Callers that already follow the documented contract (passing a
    struct from rmw_get_zero_initialized_names_and_types()) are unaffected; callers that
    relied on the previous unchecked behavior get an explicit error instead of a silent leak.

Did you use Generative AI?

Yes. Claude (claude-sonnet-4-6) via GitHub Copilot was used to explore the codebase and
create an initial prototype.

Additional Information

  • Companion PRs completing the feature:
  • The new header include (rosidl_runtime_c/type_hash.h) needs no packaging change:
    rosidl_runtime_c is already a build/build_export dependency of this package.

Testing

  • Ran the package unit tests (including the new fault-injection cases) in a Docker
    container; all pass.
  • Implemented and successfully ran an integration test exercising the end-to-end
    type-hash path through the graph query APIs.

This is a SIEMENS activity and was initialized by @gramss

Extends rmw_names_and_types_t with a type_hashes parallel array so the
graph query APIs expose the type hash of each name/type pair, alongside
the existing names and types arrays. `type_hashes[i][j]` corresponds to
`types[i].data[j]`.

rmw_names_and_types_init now enforces the documented zero-initialized
precondition on its output argument and returns RMW_RET_INVALID_ARGUMENT
otherwise. Adds fault-injection tests for the new field and pins down
that the output struct is restored to zero state after a BAD_ALLOC, so
the caller can re-init without leaks.

Updates the doxygen across the affected headers so the descriptive
prose says "names, types, and type hashes" instead of repeating the
snake_case identifier outside code references, and documents the
no-op-on-zero behavior of the unconditional cleanup in
rmw_names_and_types_fini (which now correctly cleans up
partially-initialized structs that the previous code leaked).

Note: this extends a public struct and is therefore an ABI break.

Signed-off-by: tisomi <titus090404@gmail.com>
@mergify

mergify Bot commented Jul 2, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@wjwwood wjwwood left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I believe we should change the interface to allow representation of mismatched types, since this is an introspection interface, it should be possible to see what types and hashes there are, even when there is a mismatch (perhaps especially in that case).

Comment on lines +40 to +43

/// Array of arrays of type hashes, with the same length as `names`.
/// `type_hashes[i][j]` corresponds to `types[i].data[j]`.
rosidl_type_hash_t ** type_hashes;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In my opinion, we should have an array of type hashes for each type, so that when there is an incompatability (mismatched type hashes) it can be represented on the graph. If you have this issue in the graph right now, you can see each end-point's type and type hash, so it would be weird that this interface cannot reflect the same situation.

It's fine if we have warnings about this kind of thing, but I would prefer to see those warnings produced in rcl rather than in each middleware (for consistency), instead it would be better to have the middleware represent the reality of the graph (correct or not) and then notifying the rcl layer when the graph changes or when there is an incompatibility so that the rcl layer can produce the warning.

@tisomi

tisomi commented Jul 30, 2026

Copy link
Copy Markdown
Author

@wjwwood thanks for the feedback, I agree on both points. Having duplicate detection and warning logic separately in each middleware is inconsistent and will drift over time. Having the middleware report the graph as it is and letting rcl decide what to warn about avoids that for every future rmw.

Before I rewrite, I'd like to agree on the shape so I only do it once.

Proposed interface

Keep names and types exactly as they are, and make the third array a list per (name, type) pair, following the existing rmw_*_array_t convention in this package:


typedef struct RMW_PUBLIC_TYPE rmw_type_hash_array_s
{
  size_t size;
  rosidl_type_hash_t * data;
} rmw_type_hash_array_t;

/// Type hashes, parallel to `types`, with the same length as `names`.
/// `type_hashes[i][j]` holds every distinct type hash observed for
/// `types[i].data[j]`; more than one means the graph is inconsistent.
rmw_type_hash_array_t ** type_hashes;
so that:

size == 0 — no hash known for that type
size == 1 — all endpoints agree
size > 1 — the graph is inconsistent, and every observed hash is visible
That keeps the lockstep indexing with types and avoids reshaping types itself, which would ripple into rcl/rclcpp/rclpy/ros2cli. It also removes the ambiguity in the current version, where a zero hash means both "no consensus" and "unknown".

The list should hold distinct hashes, deterministically ordered, so that every implementation produces the same result for the same graph and tests can assert exact contents.

Questions

  1. Does that shape work for you?
  2. On the rcl side — you mentioned notifying rcl "when the graph changes or when there is an incompatibility". Is it enough for rcl to warn when a graph query returns size > 1, or would you rather have it driven by the graph guard condition so the warning fires on change rather than on query? I'd propose the former as a follow-up PR.
  3. Should each entry also carry how many endpoints reported that hash? It would make the warning more useful ("7 endpoints report A, 1 reports B"), at the cost of a slightly wider struct.

@emersonknapp — you filed #356, so your input on the shape would be welcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants