-
Notifications
You must be signed in to change notification settings - Fork 412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Can't call spin_until_future_complete inside a callback executed by an executor #773
Comments
@wjwwood Could you provide some background on this? What's the root of the problem? We might be able to help. |
Re-ping @wjwwood 🙇♂️ |
I'm going to have to assume some understanding of how the executors and callback groups work to keep this reasonably brief. There are a few explanations floating around the internet, but I think the best thing to do if you want to understand them better is to start with the two most commonly used and just trace where the code goes in their spin functions:
The problem with recursive spinning is different in those two executors. For the single threaded executor, the issue is actually recursion, the code looks something like this:
This spin() method is not designed to be re-entrant. Also, you can too easily run into a dead lock using the single threaded executor, because if you did something like: void my_callback(const SomeMessageType & message) {
// ...
auto future = service->async_send_request(request);
spin_until_future_complete(future);
// ...
} And if the service callback and the above subscription callback are in the same mutually exclusive callback group, then even though you're spinning, the service would never be handled. For the multi threaded executor, the issue is thread safety because only one thread may wait for work at the same time, and while you're executing your callback another thread is likely waiting for work to be ready. This means you may not be able to get work in your thread. This isn't such an issue, but it would require the implementation to be restructured to allow for this. However, it suffers from the same callback group deadlock as the single threaded executors. Also while waiting for work you're consuming the thread you're in which could be used to execute work otherwise (especially in the same callback group). The "right" solution to this, in my opinion, requires an async/await syntax in C++, which doesn't exist right now. You can look at So ignoring that as a solution, I'd have to really dig into the architecture of the executor in order to figure out what we need to do in order to properly support this. |
Some other C++ executor/future frameworks I've seen have worked around this decently well by just improving existing futures a bit and requiring all functions executed in an executor to have a `Future' return type. Requiring callbacks to return futures along with a richer using rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;
rclcpp::Future<CallbackReturn>
on_activate(const rclcpp_lifecycle::State &)
{
// Future chaining results in a rclcpp::Future<CallbackReturn>
return client_->async_send_request(foo_request_)
.then([](const FooReply& reply){
return (reply.success) ? CallbackReturn::SUCCESS: CallbackReturn::FAILURE;
});
} Alternatively, don't expect return types. Expect a callback when the function is done. Note that there's no compile-time enforcement that someone handles the callback, so this isn't great. I think there might be many other pitfalls that I don't remember with this one. using rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;
void
on_activate(const rclcpp_lifecycle::State &, std::function<void(CallbackReturn)> done)
{
client_->async_send_request(foo_request_, [](const FooReply& reply) {
if (reply.success) {
done(CallbackReturn::SUCCESS);
} else {
done(CallbackReturn::FAILURE);
});
} |
This issue has been mentioned on ROS Discourse. There might be relevant details there: |
Signed-off-by: Stephen Brawner <brawner@gmail.com>
This issue has been mentioned on ROS Discourse. There might be relevant details there: https://discourse.ros.org/t/deferrable-canceleable-lifecycle-transitions/32318/1 |
Throwing out another way to "get around" this using async services + |
Feature request
Feature description
spin_until_future_complete
does not work recursively:rclcpp/rclcpp/include/rclcpp/executors.hpp
Line 76 in 0ccac1e
Could someone provide some background why this is not allowed?
The text was updated successfully, but these errors were encountered: