feat: add push-callback registration for rcl primitives (Tokio Executor 1/4)#653
feat: add push-callback registration for rcl primitives (Tokio Executor 1/4)#653azerupi wants to merge 2 commits into
Conversation
Add an opt-in way for primitives to report readiness via rcl's push callbacks (rcl_*_set_on_new_*_callback) instead of being polled in a wait set, as the foundation for an event-driven executor. `RclPrimitive::register_on_ready` installs a callback that the middleware invokes when the entity becomes ready and returns an `OnReadyHandle` (RAII) that deregisters on drop. `OnReadyRegistration` wraps the unsafe rcl setter: it boxes the callback context for a stable address and, on drop, clears the callback before freeing the context (finalizing the rcl entity first) so the middleware can never invoke a freed context during teardown. Implemented for subscriptions, services, and clients. No executor consumes this yet, so the basic executor is unchanged.
6809137 to
59123f5
Compare
Question: Can the callback be called if the rcl registration fails?We are registering a callback in But it is not clear if when for example The rcl documentation seems to imply that error paths would not register the callback. (e.g. subscriptions, clients) But in practice, there seem to be some deviation from this. For example in Jazzy it looks like CycloneDDS stores the callback and then could return How are discrepancies between the rcl API documentation and actual implementations usually handled? Do we implement for the "spec" and document known violations or do we try to be more defensive in the rclrs implementations? |
Wrap user closures in catch_unwind so a panic cannot unwind across the extern "C" boundary. Disable the registration after the first panic, log once, and expose an optional hook for executors to surface the error.
This is part of a set of PRs that attempt to add an event-driven Tokio executor to rclrs. The goal is to have something that:
I tried to split the work up into multiple PRs that build on top of each other to make the reviewing easier.
To make the CI work reliably for the Tokio event executor changes we need the following PRs to be merged:
Disclaimer
This work was heavily helped by the use of AI. That helped me clear a lot of ground quickly, but I want to be honest about it.
I'm submitting this set of PRs as a draft to get eyes on it and have other people help test, find limitations and flaws and provide feedback on how to improve it in order to reach the quality to be able to merge this.
add push-callback registration for rcl primitives
This PR adds the plumbing for event-driven readiness. Nothing in the existing executors consumes it yet, so behavior for users of the basic or polling Tokio executor is unchanged.
rcl can notify us when an entity becomes ready by calling a C callback, rather than us discovering it through
rcl_wait. The relevant entry points arercl_subscription_set_on_new_message_callbackand the service and client equivalents. This PR wraps those in a safe Rust API.RclPrimitivegains aregister_on_readymethod. You pass a closure to it and the implementation installs the rcl push callback and returns anOnReadyHandle. While that handle is alive, the middleware invokes your closure with aReadyKindand anumber_of_eventscount whenever the entity becomes ready.Dropping the handle deregisters the callback. Primitives with no push API (timers, guard conditions) return
Ok(None)so an executor can drive them another way.The delicate part is teardown. rcl keeps the
user_datapointer we register and passes it back on every notification, possibly from a middleware thread. That pointer must stay valid for as long as the callback is registered.OnReadyRegistrationboxes the callback context for a stable address and, on drop, clears the rcl callback before freeing the context.register_on_readyis implemented for subscriptions, services, and clients. Actions are added in a follow-up PR.Open questions
Changelog
2026-07-11 Catch panics from callbacks to avoid unwrapping across FFI
Wrap user closures in catch_unwind so a panic cannot unwind across the
extern "C"boundary. Disable the registration after the first panic, log once, and expose an optional hook for executors to surface the error.