rclcpp/rclcpp_action/include/rclcpp_action/client_goal_handle.hpp
Jacob Perron 41a99b64d3
Guard against making multiple result requests for a goal handle (#808)
This fixes a runtime error caused by a race condition when making consecutive requests for the
result.
Specifically, this happens if the user provides a result callback when sending a goal and then
calls async_get_result shortly after.

Resolves #783

Signed-off-by: Jacob Perron <jacob@openrobotics.org>
2019-08-07 09:26:19 -07:00

165 lines
4.7 KiB
C++

// Copyright 2018 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef RCLCPP_ACTION__CLIENT_GOAL_HANDLE_HPP_
#define RCLCPP_ACTION__CLIENT_GOAL_HANDLE_HPP_
#include <rcl_action/action_client.h>
#include <action_msgs/msg/goal_status.hpp>
#include <rclcpp/macros.hpp>
#include <rclcpp/time.hpp>
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include "rclcpp_action/types.hpp"
#include "rclcpp_action/visibility_control.hpp"
namespace rclcpp_action
{
/// The possible statuses that an action goal can finish with.
enum class ResultCode : int8_t
{
UNKNOWN = action_msgs::msg::GoalStatus::STATUS_UNKNOWN,
SUCCEEDED = action_msgs::msg::GoalStatus::STATUS_SUCCEEDED,
CANCELED = action_msgs::msg::GoalStatus::STATUS_CANCELED,
ABORTED = action_msgs::msg::GoalStatus::STATUS_ABORTED
};
// Forward declarations
template<typename ActionT>
class Client;
/// Class for interacting with goals sent from action clients.
/**
* Use this class to check the status of a goal as well as get the result.
*
* This class is not meant to be created by a user, instead it is created when a goal has been
* accepted.
* A `Client` will create an instance and return it to the user (via a future) after calling
* `Client::async_send_goal`.
*/
template<typename ActionT>
class ClientGoalHandle
{
public:
RCLCPP_SMART_PTR_DEFINITIONS_NOT_COPYABLE(ClientGoalHandle)
// A wrapper that defines the result of an action
typedef struct WrappedResult
{
/// The unique identifier of the goal
GoalUUID goal_id;
/// A status to indicate if the goal was canceled, aborted, or suceeded
ResultCode code;
/// User defined fields sent back with an action
typename ActionT::Result::SharedPtr result;
} WrappedResult;
using Feedback = typename ActionT::Feedback;
using Result = typename ActionT::Result;
using FeedbackCallback =
std::function<void (
typename ClientGoalHandle<ActionT>::SharedPtr,
const std::shared_ptr<const Feedback>)>;
using ResultCallback = std::function<void (const WrappedResult & result)>;
virtual ~ClientGoalHandle();
/// Get the unique ID for the goal.
const GoalUUID &
get_goal_id() const;
/// Get the time when the goal was accepted.
rclcpp::Time
get_goal_stamp() const;
/// Get a future to the goal result.
/**
* This method should not be called if the `ignore_result` flag was set when
* sending the original goal request (see Client::async_send_goal).
*
* `is_result_aware()` can be used to check if it is safe to call this method.
*
* \throws exceptions::UnawareGoalHandleError If the the goal handle is unaware of the result.
* \return A future to the result.
*/
std::shared_future<WrappedResult>
async_result();
/// Get the goal status code.
int8_t
get_status();
/// Check if an action client has subscribed to feedback for the goal.
bool
is_feedback_aware();
/// Check if an action client has requested the result for the goal.
bool
is_result_aware();
private:
// The templated Client creates goal handles
friend Client<ActionT>;
ClientGoalHandle(
const GoalInfo & info,
FeedbackCallback feedback_callback,
ResultCallback result_callback);
void
set_feedback_callback(FeedbackCallback callback);
void
set_result_callback(ResultCallback callback);
void
call_feedback_callback(
typename ClientGoalHandle<ActionT>::SharedPtr shared_this,
typename std::shared_ptr<const Feedback> feedback_message);
/// Returns the previous value of awareness
bool
set_result_awareness(bool awareness);
void
set_status(int8_t status);
void
set_result(const WrappedResult & wrapped_result);
void
invalidate();
GoalInfo info_;
bool is_result_aware_{false};
std::promise<WrappedResult> result_promise_;
std::shared_future<WrappedResult> result_future_;
FeedbackCallback feedback_callback_{nullptr};
ResultCallback result_callback_{nullptr};
int8_t status_{GoalStatus::STATUS_ACCEPTED};
std::mutex handle_mutex_;
};
} // namespace rclcpp_action
#include <rclcpp_action/client_goal_handle_impl.hpp> // NOLINT(build/include_order)
#endif // RCLCPP_ACTION__CLIENT_GOAL_HANDLE_HPP_