Skip to content

feat(rmw-zenoh-rs): add lock-tripwire as a regression guard - #352

Open
YuanYuYuan wants to merge 2 commits into
feat/rmw-zenoh-rs-gil-deadlock-fixfrom
feat/rmw-zenoh-rs-lock-tripwire-guard
Open

YuanYuYuan wants to merge 2 commits into
feat/rmw-zenoh-rs-gil-deadlock-fixfrom
feat/rmw-zenoh-rs-lock-tripwire-guard

Conversation

@YuanYuYuan

@YuanYuYuan YuanYuYuan commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Stacked on the plain fix in this repo (the previous PR). Adds an opt-in lock-tripwire-guard Cargo feature that wraps lock-tripwire — a small, debug-time tripwire that panics when a thread calls into opaque/user code while holding a lock it tracked — around the callback/user-data locks the plain fix just cleaned up.

This is a regression guard, not a live fix: the previous PR already removes the deadlock unconditionally. If a future change ever reintroduces holding one of these locks across a call-out, this fires immediately and names the exact site, instead of silently reintroducing a hang.

Scope

Covers both locks the plain fix needed to release — callback and callback_user_data — for all three entities (Subscription/Service/Client). unread_count stays untracked; nothing in this codebase holds it across a call-out.

Off by default. GuardedMutex<T> is a plain std::sync::Mutex<T> when the feature is off, so there is no behavior change for anyone not opting in.

Real, verified regression-catch proof

Built a throwaway local demo (not part of this diff): reintroduced holding the callback lock across the call-out in the subscription notify path, keeping the tripwire wiring in place.

feature off feature on
regression (lock held across call-out) real hang — timeout 20 kills it panics in ~0.01s, naming the exact site
this PR's actual code, unmodified passes passes — no false positive

Scope of the guard, stated precisely

lock-tripwire's own tracking is gated on debug_assertions, so this guard is live in cargo test and any debug build, but is a no-op in a plain --release build unless lock-tripwire's own force-checks feature (or its per-package Cargo profile override) is also enabled. Not forwarded automatically here — it has a real, measured runtime cost, and enabling it is a decision for whoever builds the release binary, documented in this crate's own doc comments.

Verification

  • cargo fmt --check / cargo clippy --all-targets -- -D warnings, both feature states: clean.
  • cargo test -p rmw-zenoh-rs --lib, both feature states: all tests pass (0.02–0.03s), no regressions, no false positives.
  • lock-tripwire resolves from its public GitHub repository — verified for real, not assumed.

Breaking changes

None. Opt-in feature, off by default.

Related work

Beyond the plain fix this stacks on (#351), sibling public fixes for the same underlying defect shape across the zenoh/ROS 2 ecosystem: eclipse-zenoh/zenoh#2781 and YuanYuYuan/zenoh#1 (zenoh's own Rust core), and ros2/rmw_zenoh#1061 (the C++ RMW implementation).

The plain fix already removes the callback-mutex/GIL AB-BA deadlock;
this adds detection so a future regression that reintroduces holding
callback_holder or callback_user_data across a call-out fails fast and
by name instead of silently reintroducing a hang in production.

Off by default -- no change to shipped behavior. Wraps both locks that
matter to this hazard: callback_holder (the original scope) and
callback_user_data (the second lock this crate's own plain fix found
was also contended with the setter -- not covered by the original
lock-tripwire-guard design, added here since it's the same hazard
shape). unread_count stays untracked, matching the established,
disclosed scope.

Verified: fmt/clippy clean both feature states; all 11 tests pass
(0.02-0.03s) both feature states, no regressions, no false positives.
Independent review found the doc comments implied this guards a plain
release build, when in fact lock-tripwire's own tracking is gated on
debug_assertions -- a release build silently checks nothing unless
force-checks (or the per-package profile override) is also enabled.
Not forwarding force-checks automatically: it has a real, measured cost
(see lock-tripwire's own OVERHEAD.md), and that's a decision for whoever
builds the release binary, not this crate. Corrected the docs to say so
plainly instead of leaving the gap implicit.
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