Skip to content

Unregister the RMW subscription callback before finalizing the handle - #3370

Draft
Quchaosheng wants to merge 2 commits into
ros2:rollingfrom
Quchaosheng:fix/2437-subscription-callback-lifetime
Draft

Quchaosheng wants to merge 2 commits into
ros2:rollingfrom
Quchaosheng:fix/2437-subscription-callback-lifetime

Conversation

@Quchaosheng

@Quchaosheng Quchaosheng commented Oct 21, 2026 •

Copy link
Copy Markdown

Description

~SubscriptionBase() never unregisters the middleware on-new-message callback. The callback's std::function storage is destroyed by the destructor, while Fast DDS can still be inside RMWSubscriptionEvent::update_data_available() on its own thread and call the trampoline with a pointer to that storage.

This unregisters the callback at the start of the destructor, before the rcl_subscription_t shared-pointer deleter runs rcl_subscription_fini(). Destruction must not throw, so a failure to unregister is logged and the handle is still finalized.

Refs #2437

Status

Draft, not ready for merge.

I have not reproduced #2437 in a standalone test. My reduced two-node/EventsExecutor stress harness only produced an unrelated stack-buffer-overflow inside the harness, and the corrected version did not crash. So this change addresses a destructor ordering gap, not a confirmed root cause of the original report.

The new test asserts that rcl_subscription_set_on_new_message_callback() is called with a null callback before rcl_subscription_fini() is entered. That is a narrower property than the one the issue actually needs, which is that teardown synchronizes with an in-flight middleware callback. Please treat this PR as a question about whether that unregister belongs in ~SubscriptionBase() at all.

I could not run the full test_subscription target locally: the available Rolling dependency prefix predates this tree and lacks rcl_publisher_event_type_is_supported, and the installed Jazzy prefix is too old for the rest of the build. CI should provide the first full build.

Is this user-facing behavior change?

No. Only the destruction path changes: the middleware callback is cleared earlier, and a teardown error is logged instead of being ignored.

Did you use Generative AI?

Yes. OpenAI Codex assisted with source analysis and test preparation.

EventsExecutor registers an on-new-message callback with the middleware and
only unregisters it when it notices the entity is gone. The subscription
handle could therefore be finalized while the callback was still reachable.

Unregister the callback from ~SubscriptionBase(), before the shared
rcl_subscription_t deleter runs rcl_subscription_fini(). Destruction must not
throw, so failures are logged and the handle is still finalized.

Refs ros2#2437

Signed-off-by: Chaosheng Qu <chaosheng.qu.linux@gmail.com>
Assert that rcl_subscription_set_on_new_message_callback() has been called
with a null callback by the time rcl_subscription_fini() is entered.

Signed-off-by: Chaosheng Qu <chaosheng.qu.linux@gmail.com>
@Quchaosheng
Quchaosheng force-pushed the fix/2437-subscription-callback-lifetime branch from f981f6b to e53f501 Compare October 21, 2026 07:12
@Quchaosheng
Quchaosheng marked this pull request as draft October 21, 2026 07:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant