Skip to content

Feature/action - #17

Merged
iv461 merged 99 commits into
mainfrom
feature/action
Feb 18, 2026
Merged

Feature/action#17
iv461 merged 99 commits into
mainfrom
feature/action

Conversation

@iv461

@iv461 iv461 commented Feb 13, 2026

Copy link
Copy Markdown
Owner

This PR implements an async await API for both action server and action clients.
It does so by adding a copy of the rclcpp_actions source code and patching some limitations as well as critical bugs that are absolutely necessary for implementing an async await API.

Feature set

Action server

  • Allowing to use asynchronous functions, i.e. coroutines besides synchronous functions for handling the goal request, cancellation and the execution of the goal.

The main limitation in rclcpp was a missing support for deferred response. So I've split the internal methods handling the service calls in half and moved the second half into separate send_respose methods (similar to the ones present in the services API).
Patch, see file icey/include/icey/action/server.hpp/cpp

Action client

  • awaiting the result of setting a goal
  • receiving feedback
  • obtaining the final result
  • receiving feedback

There were a couple of bugs in rclcpp that prevented a correct implementation:

  • Deadlock occurs when chaining send_goal requests. This is caused by non-reentrant mutexes being held locked unnecessarily during user callbacks. This was fixed in the rolling version (async_send_goal deadlocks ros2/rclcpp#2796), but Tomoja Fujita says it won't be backported (async_send_goal deadlocks ros2/rclcpp#2796 (comment)). Unfortunately, this conceptual issue is present in many places in rclcpp. I fixed it by not locking at all during user callbacks (Patch](ef48fc1...ef1258f), file /icey/src/actions/client.cpp, tested by test case ActionTimeoutAndMultipleGoalsTest.)

  • No cancellation API for cancelling send_goal and cancel requests (similar to regular service calls) is present. Currently, it is not possible to cancel callbacks or delete a pending goal request. If the server dies, the client leaks memory. This issue has not yet been resolved in the rolling version, as there is currently no way to delete items from the pimpl_->pending_goal_responses map added here. I've fixed this by adding returning the request ID of the underlying service calls and then adding cancellation function to cancel these requests (See patch, file icey/include/icey/action/client.hpp
    )

The action client API problem

One problem that remains unsolved in this PR is the action client API.
The problem is that the action protocol specification as given by the sequence diagrams in the action design document is not implemented correctly by ROS.
Whether or not it is implemented correctly however critically influences the client API.
If we would implement an API that follows the specification, we would get lost feedback messages.

Details

The issue is that per specification, the client should be able to receive feedback messages only after requesting the result request. However, the current ROS implementation does not adhere to this specification. This standpoint is also shared by Janosch Machowinski (ros2/rclcpp#2782 (comment)).
It instead allows the client to receive feedback messages before the result request has been sent.

This means a sensible async/await API such as:

auto maybe_goal_handle = co_await action_client.send_goal(goal, 2s);
if(maybe_goal_handle.has_value()) {
   auto goal_handle = maybe_goal_handle.value();
   for(auto fb: co_await goal_handle.get_result(20s)) {
       if(fb.is_feedback()) {
          
       } else {
            /// We got the final result
            auto result = fb.result();
            break;
       }
   }
} else {
   /// Goal request rejected
}

would have a race condition, although it adheres to the specification. Feedback messages may be received before the get_result function is called, meaning they may be lost. This issue has been discussed here and a fix proposed. The proposed fix lead to the implementation following the specification and therefore enabling the above async/await API.

I've decided to provide an API that uses a feedback callback to avoid loosing feedback messages. Once the bug in the underlying actions implementation is fixed, we could switch to the above API.

Other minor changes:

  • fixes Result-type to work with non-default-constructible values and errors
  • Fixes missing cancellations of the timeout timer in the promise destructor
  • Refactors the deferred cleanup of timeout timers to the NodeBase class (but generally the timeout handling can be refactored further, i.e. to a with_timeout() function)
  • fixes flaky count_subscribers and similar tests

Ivo Ivanov added 29 commits February 18, 2026 17:30
… to the async await support for feedback: the feedback has to remain a callback, because otherwise we would need to buffer feedbacks received between the goal acceptance and the implicit feedback callback registration done by co_await feedback(). This buffering would be required to avoid a race.
…re it throws an exception if the action is already done.
…, because the async_goal_result API does not make sense. For this, we move the result promise inside the goalhandle, this leads to the goal handle not being copyable, so we return a shared pointer to the goal handle (like the original ROS API). I've also added three other useful methods from the GoalHandle base
…always launches an asynchronous operation, which in the case of actions API does not hold anymore
Ivo Ivanov added 26 commits February 18, 2026 17:37
… adress as the timeout timer id. Reverted back to always suspend in the promise (await ready returns false), because this rules out stack overflows and is required anymore for the actions api. Removed some service apis because they were not implemented correctly. I can still add a two-callback version by adding then and except to the promise, and a cancel method to the promise. Also fixed some merge-related errors, and made result type to work with non-default-constructible types.
@iv461
iv461 merged commit d49340d into main Feb 18, 2026
3 checks passed
@iv461 iv461 mentioned this pull request Feb 18, 2026
@iv461
iv461 deleted the feature/action branch February 19, 2026 01:10
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