Skip to content

Commit bf1396b

Browse files
authored
Pass goal handle to goal response callback instead of a future (#1311)
* Pass goal handle to goal response callback instead of a future This resolves an issue where `promise->set_value` is called before a potential call to `promise->set_exception`. If there is an issue sending a result request, set the exception on the future to the result in the goal handle, instead of the future to the goal handle itself. Signed-off-by: Jacob Perron <[email protected]> * Do not remove goal handle from list if result request fails This way the user can still interact with the goal (e.g. send a cancel request). Signed-off-by: Jacob Perron <[email protected]> * Set the result awareness to false if goal handle is invalidated This will cause an exception when trying to get the future to result, in addition to the exception when trying to access values for existing references to the future. Signed-off-by: Jacob Perron <[email protected]> * Revert "Set the result awareness to false if goal handle is invalidated" This reverts commit d444e09. * Throw from Client::async_get_result if the goal handle was invalidated due to a failed result request Propagate error message from a failed result request. Also set result awareness to false if the result request fails so the user can also check before being hit with an exception. Signed-off-by: Jacob Perron <[email protected]> * Guard against mutliple calls to invalidate Signed-off-by: Jacob Perron <[email protected]>
1 parent e62f328 commit bf1396b

File tree

5 files changed

+62
-39
lines changed

5 files changed

+62
-39
lines changed

rclcpp_action/include/rclcpp_action/client.hpp

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#ifndef RCLCPP_ACTION__CLIENT_HPP_
1616
#define RCLCPP_ACTION__CLIENT_HPP_
1717

18+
#include <rclcpp/exceptions.hpp>
1819
#include <rclcpp/macros.hpp>
1920
#include <rclcpp/node_interfaces/node_base_interface.hpp>
2021
#include <rclcpp/node_interfaces/node_logging_interface.hpp>
@@ -37,6 +38,7 @@
3738
#include <utility>
3839

3940
#include "rclcpp_action/client_goal_handle.hpp"
41+
#include "rclcpp_action/exceptions.hpp"
4042
#include "rclcpp_action/types.hpp"
4143
#include "rclcpp_action/visibility_control.hpp"
4244

@@ -261,8 +263,7 @@ class Client : public ClientBase
261263
using Feedback = typename ActionT::Feedback;
262264
using GoalHandle = ClientGoalHandle<ActionT>;
263265
using WrappedResult = typename GoalHandle::WrappedResult;
264-
using GoalResponseCallback =
265-
std::function<void (std::shared_future<typename GoalHandle::SharedPtr>)>;
266+
using GoalResponseCallback = std::function<void (typename GoalHandle::SharedPtr)>;
266267
using FeedbackCallback = typename GoalHandle::FeedbackCallback;
267268
using ResultCallback = typename GoalHandle::ResultCallback;
268269
using CancelRequest = typename ActionT::Impl::CancelGoalService::Request;
@@ -284,12 +285,9 @@ class Client : public ClientBase
284285

285286
/// Function called when the goal is accepted or rejected.
286287
/**
287-
* Takes a single argument that is a future to a goal handle shared pointer.
288+
* Takes a single argument that is a goal handle shared pointer.
288289
* If the goal is accepted, then the pointer points to a valid goal handle.
289290
* If the goal is rejected, then pointer has the value `nullptr`.
290-
* If an error occurs while waiting for the goal response an exception will be thrown
291-
* when calling `future::get()`.
292-
* Possible exceptions include `rclcpp::RCLError` and `rclcpp::RCLBadAlloc`.
293291
*/
294292
GoalResponseCallback goal_response_callback;
295293

@@ -360,7 +358,7 @@ class Client : public ClientBase
360358
if (!goal_response->accepted) {
361359
promise->set_value(nullptr);
362360
if (options.goal_response_callback) {
363-
options.goal_response_callback(future);
361+
options.goal_response_callback(nullptr);
364362
}
365363
return;
366364
}
@@ -376,16 +374,11 @@ class Client : public ClientBase
376374
}
377375
promise->set_value(goal_handle);
378376
if (options.goal_response_callback) {
379-
options.goal_response_callback(future);
377+
options.goal_response_callback(goal_handle);
380378
}
381379

382380
if (options.result_callback) {
383-
try {
384-
this->make_result_aware(goal_handle);
385-
} catch (...) {
386-
promise->set_exception(std::current_exception());
387-
return;
388-
}
381+
this->make_result_aware(goal_handle);
389382
}
390383
});
391384

@@ -414,7 +407,7 @@ class Client : public ClientBase
414407
/// Asynchronously get the result for an active goal.
415408
/**
416409
* \throws exceptions::UnknownGoalHandleError If the goal unknown or already reached a terminal
417-
* state.
410+
* state, or if there was an error requesting the result.
418411
* \param[in] goal_handle The goal handle for which to get the result.
419412
* \param[in] result_callback Optional callback that is called when the result is received.
420413
* \return A future that is set to the goal result when the goal is finished.
@@ -428,6 +421,11 @@ class Client : public ClientBase
428421
if (goal_handles_.count(goal_handle->get_goal_id()) == 0) {
429422
throw exceptions::UnknownGoalHandleError();
430423
}
424+
if (goal_handle->is_invalidated()) {
425+
// This case can happen if there was a failure to send the result request
426+
// during the goal response callback
427+
throw goal_handle->invalidate_exception_;
428+
}
431429
if (result_callback) {
432430
// This will override any previously registered callback
433431
goal_handle->set_result_callback(result_callback);
@@ -518,7 +516,7 @@ class Client : public ClientBase
518516
while (it != goal_handles_.end()) {
519517
typename GoalHandle::SharedPtr goal_handle = it->second.lock();
520518
if (goal_handle) {
521-
goal_handle->invalidate();
519+
goal_handle->invalidate(exceptions::UnawareGoalHandleError());
522520
}
523521
it = goal_handles_.erase(it);
524522
}
@@ -632,22 +630,27 @@ class Client : public ClientBase
632630
using GoalResultRequest = typename ActionT::Impl::GetResultService::Request;
633631
auto goal_result_request = std::make_shared<GoalResultRequest>();
634632
goal_result_request->goal_id.uuid = goal_handle->get_goal_id();
635-
this->send_result_request(
636-
std::static_pointer_cast<void>(goal_result_request),
637-
[goal_handle, this](std::shared_ptr<void> response) mutable
638-
{
639-
// Wrap the response in a struct with the fields a user cares about
640-
WrappedResult wrapped_result;
641-
using GoalResultResponse = typename ActionT::Impl::GetResultService::Response;
642-
auto result_response = std::static_pointer_cast<GoalResultResponse>(response);
643-
wrapped_result.result = std::make_shared<typename ActionT::Result>();
644-
*wrapped_result.result = result_response->result;
645-
wrapped_result.goal_id = goal_handle->get_goal_id();
646-
wrapped_result.code = static_cast<ResultCode>(result_response->status);
647-
goal_handle->set_result(wrapped_result);
648-
std::lock_guard<std::mutex> lock(goal_handles_mutex_);
649-
goal_handles_.erase(goal_handle->get_goal_id());
650-
});
633+
try {
634+
this->send_result_request(
635+
std::static_pointer_cast<void>(goal_result_request),
636+
[goal_handle, this](std::shared_ptr<void> response) mutable
637+
{
638+
// Wrap the response in a struct with the fields a user cares about
639+
WrappedResult wrapped_result;
640+
using GoalResultResponse = typename ActionT::Impl::GetResultService::Response;
641+
auto result_response = std::static_pointer_cast<GoalResultResponse>(response);
642+
wrapped_result.result = std::make_shared<typename ActionT::Result>();
643+
*wrapped_result.result = result_response->result;
644+
wrapped_result.goal_id = goal_handle->get_goal_id();
645+
wrapped_result.code = static_cast<ResultCode>(result_response->status);
646+
goal_handle->set_result(wrapped_result);
647+
std::lock_guard<std::mutex> lock(goal_handles_mutex_);
648+
goal_handles_.erase(goal_handle->get_goal_id());
649+
});
650+
} catch (rclcpp::exceptions::RCLError & ex) {
651+
// This will cause an exception when the user tries to access the result
652+
goal_handle->invalidate(exceptions::UnawareGoalHandleError(ex.message));
653+
}
651654
}
652655

653656
/// \internal

rclcpp_action/include/rclcpp_action/client_goal_handle.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <memory>
2727
#include <mutex>
2828

29+
#include "rclcpp_action/exceptions.hpp"
2930
#include "rclcpp_action/types.hpp"
3031
#include "rclcpp_action/visibility_control.hpp"
3132

@@ -145,10 +146,15 @@ class ClientGoalHandle
145146
set_result(const WrappedResult & wrapped_result);
146147

147148
void
148-
invalidate();
149+
invalidate(const exceptions::UnawareGoalHandleError & ex);
150+
151+
bool
152+
is_invalidated() const;
149153

150154
GoalInfo info_;
151155

156+
std::exception_ptr invalidate_exception_{nullptr};
157+
152158
bool is_result_aware_{false};
153159
std::promise<WrappedResult> result_promise_;
154160
std::shared_future<WrappedResult> result_future_;

rclcpp_action/include/rclcpp_action/client_goal_handle_impl.hpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,24 @@ ClientGoalHandle<ActionT>::set_result_awareness(bool awareness)
138138

139139
template<typename ActionT>
140140
void
141-
ClientGoalHandle<ActionT>::invalidate()
141+
ClientGoalHandle<ActionT>::invalidate(const exceptions::UnawareGoalHandleError & ex)
142142
{
143143
std::lock_guard<std::mutex> guard(handle_mutex_);
144+
// Guard against multiple calls
145+
if (is_invalidated()) {
146+
return;
147+
}
148+
is_result_aware_ = false;
149+
invalidate_exception_ = std::make_exception_ptr(ex);
144150
status_ = GoalStatus::STATUS_UNKNOWN;
145-
result_promise_.set_exception(std::make_exception_ptr(exceptions::UnawareGoalHandleError()));
151+
result_promise_.set_exception(invalidate_exception_);
152+
}
153+
154+
template<typename ActionT>
155+
bool
156+
ClientGoalHandle<ActionT>::is_invalidated() const
157+
{
158+
return invalidate_exception_ != nullptr;
146159
}
147160

148161
template<typename ActionT>

rclcpp_action/include/rclcpp_action/exceptions.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#define RCLCPP_ACTION__EXCEPTIONS_HPP_
1717

1818
#include <stdexcept>
19+
#include <string>
1920

2021
namespace rclcpp_action
2122
{
@@ -33,8 +34,9 @@ class UnknownGoalHandleError : public std::invalid_argument
3334
class UnawareGoalHandleError : public std::runtime_error
3435
{
3536
public:
36-
UnawareGoalHandleError()
37-
: std::runtime_error("Goal handle is not tracking the goal result.")
37+
UnawareGoalHandleError(
38+
const std::string & message = "Goal handle is not tracking the goal result.")
39+
: std::runtime_error(message)
3840
{
3941
}
4042
};

rclcpp_action/test/test_client.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,9 +435,8 @@ TEST_F(TestClientAgainstServer, async_send_goal_with_goal_response_callback_wait
435435
auto send_goal_ops = rclcpp_action::Client<ActionType>::SendGoalOptions();
436436
send_goal_ops.goal_response_callback =
437437
[&goal_response_received]
438-
(std::shared_future<typename ActionGoalHandle::SharedPtr> future) mutable
438+
(typename ActionGoalHandle::SharedPtr goal_handle) mutable
439439
{
440-
auto goal_handle = future.get();
441440
if (goal_handle) {
442441
goal_response_received = true;
443442
}

0 commit comments

Comments
 (0)