Skip to content

Fix lock order inversion in rmw_wait#1005

Merged
jmachowinski merged 2 commits into
rollingfrom
jm/deadlock_fix
Jul 16, 2026
Merged

Fix lock order inversion in rmw_wait#1005
jmachowinski merged 2 commits into
rollingfrom
jm/deadlock_fix

Conversation

@jmachowinski

@jmachowinski jmachowinski commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Description

This is a second attempted to fix #921

From my point of view the previous patches introduced in
#937 and #992
introduced a use after free issue.

If the triggering thread is scheduled away after obtaining the wait_set_data_
pointer, another thread may delete the waitset directly afterwards.
Then the locally saved pointer points to invalid memory.

Fixes #921

Is this user-facing behavior change?

No deadlocks any more ?

Did you use Generative AI?

I asked AI up front and it came up with the broken patch from #937
This one was written by me...

Additional Information

We hit this bug on shutdown of ros2 bag record.

@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

@jmachowinski jmachowinski changed the title Jm/deadlock fix Fix lock oder inversion in rmw_wait Jul 2, 2026
@jmachowinski jmachowinski changed the title Fix lock oder inversion in rmw_wait Fix lock order inversion in rmw_wait Jul 2, 2026

@Yadunund Yadunund 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.

@jmachowinski thanks for looking into the issue and providing a fix. I'll review the changes more closely but left an early comment for your consideration.

@@ -1,97 +0,0 @@
// Copyright 2026 Open Source Robotics Foundation, Inc.

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.

Can we retain + update the test to capture the issue faced during rosbag record? It would be great to have a way to check against the deadlock behavior and to prevent future regressions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We could hammer a guard condition in an endless loop in a thread and wait a few times in a second thread that should trigger the dead lock.

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.

That works. Do you have a way for me to reproduce the use after free issue to test both cases against this PR?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same test, just delete the waitset directly after the rcl_wait. This is basically what the executor will do.

I would not put the test in this pkg, but rather in to rcl or system_tests. I was also looking at the fastrtps impl for rmw_wait and it is almost identical. Therefore having a test in a unified location checking both makes more sense to me.

@jmachowinski jmachowinski Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For clarification on the use after free :
If this code runs in thread 1:

 rmw_wait_set_data_t * wait_set_data_to_trigger = nullptr;
  {
    std::lock_guard<std::mutex> lock(internal_mutex_);

    // the change to hasTriggered_ needs to be mutually exclusive with
    // rmw_wait() which checks hasTriggered() and decides if wait() needs to
    // be called
    has_triggered_ = true;
    wait_set_data_to_trigger = wait_set_data_;
  }

Now we switch to Thread 0 and return from rcl_wait and delete the waitset.

Switch back to Thread 1

 // now wait_set_data_to_trigger points to deleted memory

  if (wait_set_data_to_trigger != nullptr) {
    std::lock_guard<std::mutex> wait_set_lock(wait_set_data_to_trigger->condition_mutex);
    wait_set_data_to_trigger->triggered = true;
    wait_set_data_to_trigger->condition_variable.notify_one();
  }

This is seriously racy, and the only chance for creating a test case is hammering the trigger from a second thread. And even then there is a chance that you don't catch the bug. These things are hard to test and I don't know if it is worth the effort to try to create a test.

@araitaiga

Copy link
Copy Markdown

@jmachowinski
I tested this PR with my reproducer from #1003 (rapidly creating/destroying endpoints while ros2 bag record runs). On rolling (5eaeb6d) the recorder aborts within seconds with rcl_wait unexpectedly timed out, but with this PR (c6f8e44) it ran for 10 min with no crash.

@jmachowinski

Copy link
Copy Markdown
Contributor Author

Pulls: #1005
Gist: https://gist.githubusercontent.com/jmachowinski/6fec33dea698e307917e505300290f6c/raw/e02bdcd19854627dc9bc4d993c849623ffa2e6f3/ros2.repos
BUILD args: --cmake-args -DRMW_IMPLEMENTATION=rmw_zenoh_cpp
TEST args:
ROS Distro: rolling
Job: ci_launcher
ci_launcher ran: https://ci.ros2.org/job/ci_launcher/19762

  • Linux Build Status
  • Linux-aarch64 Build Status
  • Linux-rhel Build Status
  • Windows Build Status

@jmachowinski

jmachowinski commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Pulls: #1005
Gist: https://gist.githubusercontent.com/jmachowinski/986dbd19e51e25544f152bb28efdcd67/raw/e02bdcd19854627dc9bc4d993c849623ffa2e6f3/ros2.repos
BUILD args: --cmake-args -DRMW_IMPLEMENTATION=rmw_zenoh_cpp
TEST args:
ROS Distro: rolling
Job: ci_launcher
ci_launcher ran: https://ci.ros2.org/job/ci_launcher/19774

  • Linux Build Status
  • Linux-aarch64 Build Status
  • Linux-rhel Build Status
  • Windows Build Status

@GreatAlexander

Copy link
Copy Markdown

TL;DR: Same as @araitaiga , we believe this patch fixes the issue we have observed (Dockerised Jazzy Ubuntu 24.04).

For reference, we are running data recording on an AV with many Docker containers, 100+ topics (inc. chonky camera images & lidar pointclouds), about ~1GB/s total.
Before this patch (latest Jazzy version) ros2 bag record would freeze around 5-20mins into recording.
After this patch (back ported to Jazzy) we've recorded ~1h straight, ~6M messages, ~1.3TB, no issues.

I'll post back if we see this issue appear again while using this patch. In any case, thank you very much for your work!!

@jmachowinski

Copy link
Copy Markdown
Contributor Author

@Yadunund The CI seems as green as you can get it if actually running the system tests with zenoh and not fastdds.
The diff to the CI runs in #996
Seems to be the same if looking at the failed tests.

I would like to merge this now and start a backport so that we can get it in the lyrical sync.

@MiguelCompany added a test here ros2/rmw_implementation#280 that should catch this issue as well. So I don't see the point of adding another one in this repo.

@Yadunund Yadunund 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.

LGTM!

@Yadunund

Copy link
Copy Markdown
Member

@jmachowinski mind resolving the merge conflict?

GreatAlexander added a commit to assistive-autonomy/av_tools that referenced this pull request Jul 14, 2026
GreatAlexander added a commit to assistive-autonomy/av_tools that referenced this pull request Jul 14, 2026
* Fix runtime msg dependencies for runtime script

* Add convenient script to run ghcr.io docker image

* Update Dockerfile with msg pkgs available upstream

 - pandar_msgs and autoware_msgs now (finally) available via apt
 - Fix tiny typo

* Fix pre-commit issues

* Apply temporary patch to fix rmw_zenoh freeze

 - ros2/rmw_zenoh#1005

* Fix pre-commit issues
Janosch Machowinski added 2 commits July 15, 2026 18:13
This fixes a deadlock if a guard condition was triggered while
a guard condition was being attached or detached.

Signed-off-by: Janosch Machowinski <J.Machowinski@cellumation.com>
@jmachowinski

Copy link
Copy Markdown
Contributor Author

Pulls: #1005
Gist: https://gist.githubusercontent.com/jmachowinski/a1156dd15c12def3cbeb36a5823eee2f/raw/e02bdcd19854627dc9bc4d993c849623ffa2e6f3/ros2.repos
BUILD args: --cmake-args -DRMW_IMPLEMENTATION=rmw_zenoh_cpp
TEST args:
ROS Distro: rolling
Job: ci_launcher
ci_launcher ran: https://ci.ros2.org/job/ci_launcher/19862

  • Linux Build Status
  • Linux-aarch64 Build Status
  • Linux-rhel Build Status
  • Windows Build Status

@jmachowinski
jmachowinski merged commit 60e4fee into rolling Jul 16, 2026
6 checks passed
@jmachowinski
jmachowinski deleted the jm/deadlock_fix branch July 16, 2026 08:16
@Tiryoh

Tiryoh commented Jul 16, 2026

Copy link
Copy Markdown

@jmachowinski @Yadunund Thanks for fixing this. Are there plans to backport this fix to Jazzy, Kilted, and Lyrical?

@jmachowinski

Copy link
Copy Markdown
Contributor Author

Yeah, but I need to do it manually

@GreatAlexander

Copy link
Copy Markdown

Thanks for the quick great work everyone! 🎉
Any idea when there will be a new Jazzy release inc. this fix? Backport just got merged yesterday.

@Yadunund

Copy link
Copy Markdown
Member

@GreatAlexander will get it out in the next day or so.

@Tiryoh

Tiryoh commented Jul 22, 2026

Copy link
Copy Markdown

Thank you for backporting the patch! @jmachowinski @Yadunund

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.

rmw_zenoh_cpp: deadlock

5 participants