Part of #282. Two public-API sharp edges found in the same adversarial pass. Both pre-existing; #260 makes the second more visible by promoting it to a documented trait API.
1. EventsManager::set_callback is still pub, and is still the failure-3 footgun
It takes &mut self, so a caller holding an Arc<Mutex<EventsManager>> can only reach it with the outer mutex held — and it fires the backlog underneath that guard. A callback that re-enters (rmw_take_event → RmEventHandle::take_event) then self-deadlocks on a non-reentrant Mutex.
That is #259's failure 3 verbatim, still reachable from shipped public API. Its own doc comment says so, which helps a reader but prevents nothing.
The tripwire cannot catch it either: Mutex<EventsManager> is caller-owned and untracked.
2. Duplicate registration + single unregistration removes every entry
register_graph_guard_condition pushes with no dedup. unregister_graph_guard_condition uses retain(|gc| !Arc::ptr_eq(..)), which removes all matching entries.
register(gc);
register(gc);
unregister(&gc); // leaves ZERO registrations, not one
Pre-existing shape — the previous usize-keyed version had the same retain — but #260 promotes this to a documented public trait API taking Arc<dyn GraphGuardCondition>, and its new unit test exercises only the 1:1 case.
Unregistering a never-registered handle is a silent no-op, also untested.
Part of #282. Two public-API sharp edges found in the same adversarial pass. Both pre-existing; #260 makes the second more visible by promoting it to a documented trait API.
1.
EventsManager::set_callbackis stillpub, and is still the failure-3 footgunIt takes
&mut self, so a caller holding anArc<Mutex<EventsManager>>can only reach it with the outer mutex held — and it fires the backlog underneath that guard. A callback that re-enters (rmw_take_event→RmEventHandle::take_event) then self-deadlocks on a non-reentrantMutex.That is #259's failure 3 verbatim, still reachable from shipped public API. Its own doc comment says so, which helps a reader but prevents nothing.
The tripwire cannot catch it either:
Mutex<EventsManager>is caller-owned and untracked.#[deprecated]with a pointer to the collect-release-fire shape, or make itpub(crate)invoke_user_callback!so a violation is at least detected in debug2. Duplicate registration + single unregistration removes every entry
register_graph_guard_conditionpushes with no dedup.unregister_graph_guard_conditionusesretain(|gc| !Arc::ptr_eq(..)), which removes all matching entries.Pre-existing shape — the previous
usize-keyed version had the sameretain— but #260 promotes this to a documented public trait API takingArc<dyn GraphGuardCondition>, and its new unit test exercises only the 1:1 case.Unregistering a never-registered handle is a silent no-op, also untested.