Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
simple_action_server.hpp
1 // Copyright (c) 2019 Intel Corporation
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #ifndef NAV2_ROS_COMMON__SIMPLE_ACTION_SERVER_HPP_
16 #define NAV2_ROS_COMMON__SIMPLE_ACTION_SERVER_HPP_
17 
18 #include <memory>
19 #include <mutex>
20 #include <string>
21 #include <thread>
22 #include <future>
23 #include <chrono>
24 #include <type_traits>
25 
26 #include "rclcpp/rclcpp.hpp"
27 #include "rclcpp_action/rclcpp_action.hpp"
28 #include "nav2_ros_common/node_thread.hpp"
29 #include "nav2_ros_common/node_utils.hpp"
30 
31 namespace nav2
32 {
33 
38 template<typename ActionT>
40 {
41 public:
42  using SharedPtr = std::shared_ptr<nav2::SimpleActionServer<ActionT>>;
43  using UniquePtr = std::unique_ptr<nav2::SimpleActionServer<ActionT>>;
44 
45  // Callback function to complete main work. This should itself deal with its
46  // own exceptions, but if for some reason one is thrown, it will be caught
47  // in SimpleActionServer and terminate the action itself.
48  typedef std::function<void ()> ExecuteCallback;
49 
50  // Callback function to notify the user that an exception was thrown that
51  // the simple action server caught (or another failure) and the action was
52  // terminated. To avoid using, catch exceptions in your application such that
53  // the SimpleActionServer will never need to terminate based on failed action
54  // ExecuteCallback.
55  typedef std::function<void ()> CompletionCallback;
56 
67  template<typename NodeT>
69  NodeT node,
70  const std::string & action_name,
71  ExecuteCallback execute_callback,
72  CompletionCallback completion_callback = nullptr,
73  std::chrono::milliseconds server_timeout = std::chrono::milliseconds(500),
74  bool spin_thread = false,
75  const bool realtime = false)
77  node->get_node_base_interface(),
78  node->get_node_clock_interface(),
79  node->get_node_logging_interface(),
80  node->get_node_waitables_interface(),
81  node->get_node_parameters_interface(),
82  action_name, execute_callback, completion_callback,
83  server_timeout, spin_thread, realtime)
84  {}
85 
97  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface,
98  rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_interface,
99  rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_interface,
100  rclcpp::node_interfaces::NodeWaitablesInterface::SharedPtr node_waitables_interface,
101  rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_interface,
102  const std::string & action_name,
103  ExecuteCallback execute_callback,
104  CompletionCallback completion_callback = nullptr,
105  std::chrono::milliseconds server_timeout = std::chrono::milliseconds(500),
106  bool spin_thread = false,
107  const bool realtime = false)
108  : node_base_interface_(node_base_interface),
109  node_clock_interface_(node_clock_interface),
110  node_logging_interface_(node_logging_interface),
111  node_waitables_interface_(node_waitables_interface),
112  node_parameters_interface_(node_parameters_interface),
113  action_name_(action_name),
114  execute_callback_(execute_callback),
115  completion_callback_(completion_callback),
116  server_timeout_(server_timeout),
117  spin_thread_(spin_thread)
118  {
119  using namespace std::placeholders; // NOLINT
120  use_realtime_prioritization_ = realtime;
121  if (spin_thread_) {
122  callback_group_ = node_base_interface->create_callback_group(
123  rclcpp::CallbackGroupType::MutuallyExclusive, false);
124  }
125  action_server_ = rclcpp_action::create_server<ActionT>(
126  node_base_interface_,
127  node_clock_interface_,
128  node_logging_interface_,
129  node_waitables_interface_,
130  action_name_,
131  std::bind(&SimpleActionServer::handle_goal, this, _1, _2),
132  std::bind(&SimpleActionServer::handle_cancel, this, _1),
133  std::bind(&SimpleActionServer::handle_accepted, this, _1),
134  rcl_action_server_get_default_options(), // Use consistent QoS settings
135  callback_group_);
136 
137  nav2::setIntrospectionMode(this->action_server_,
138  node_parameters_interface_, node_clock_interface_->get_clock());
139 
140  if (spin_thread_) {
141  executor_ = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
142  executor_->add_callback_group(callback_group_, node_base_interface_);
143  executor_thread_ = std::make_unique<nav2::NodeThread>(executor_);
144  }
145  }
146 
154  rclcpp_action::GoalResponse handle_goal(
155  const rclcpp_action::GoalUUID & /*uuid*/,
156  std::shared_ptr<const typename ActionT::Goal>/*goal*/)
157  {
158  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
159 
160  if (!server_active_) {
161  RCLCPP_INFO(
162  node_logging_interface_->get_logger(),
163  "Action server is inactive. Rejecting the goal.");
164  return rclcpp_action::GoalResponse::REJECT;
165  }
166 
167  debug_msg("Received request for goal acceptance");
168  return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
169  }
170 
177  rclcpp_action::CancelResponse handle_cancel(
178  const std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> handle)
179  {
180  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
181 
182  if (!handle->is_active()) {
183  warn_msg(
184  "Received request for goal cancellation,"
185  "but the handle is inactive, so reject the request");
186  return rclcpp_action::CancelResponse::REJECT;
187  }
188 
189  debug_msg("Received request for goal cancellation");
190  return rclcpp_action::CancelResponse::ACCEPT;
191  }
192 
197  {
198  if (use_realtime_prioritization_) {
199  nav2::setSoftRealTimePriority();
200  debug_msg("Soft realtime prioritization successfully set!");
201  }
202  }
203 
208  void handle_accepted(const std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> handle)
209  {
210  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
211  debug_msg("Receiving a new goal");
212 
213  if (is_active(current_handle_) || is_running()) {
214  debug_msg("An older goal is active, moving the new goal to a pending slot.");
215 
216  if (is_active(pending_handle_)) {
217  debug_msg(
218  "The pending slot is occupied."
219  " The previous pending goal will be terminated and replaced.");
220  terminate(pending_handle_);
221  }
222  pending_handle_ = handle;
223  preempt_requested_ = true;
224  } else {
225  if (is_active(pending_handle_)) {
226  // Shouldn't reach a state with a pending goal but no current one.
227  error_msg("Forgot to handle a preemption. Terminating the pending goal.");
228  terminate(pending_handle_);
229  preempt_requested_ = false;
230  }
231 
232  current_handle_ = handle;
233 
234  // Return quickly to avoid blocking the executor, so spin up a new thread
235  debug_msg("Executing goal asynchronously.");
236  execution_future_ = std::async(
237  std::launch::async, [this]() {
239  work();
240  });
241  }
242  }
243 
247  void work()
248  {
249  while (rclcpp::ok() && !stop_execution_ && is_active(current_handle_)) {
250  debug_msg("Executing the goal...");
251  try {
252  execute_callback_();
253  } catch (std::exception & ex) {
254  RCLCPP_ERROR(
255  node_logging_interface_->get_logger(),
256  "Action server failed while executing action callback: \"%s\"", ex.what());
257  terminate_all();
258  if (completion_callback_) {completion_callback_();}
259  return;
260  }
261 
262  debug_msg("Blocking processing of new goal handles.");
263  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
264 
265  if (stop_execution_) {
266  warn_msg("Stopping the thread per request.");
267  terminate_all();
268  if (completion_callback_) {completion_callback_();}
269  break;
270  }
271 
272  if (is_active(current_handle_)) {
273  warn_msg("Current goal was not completed successfully.");
274  terminate(current_handle_);
275  if (completion_callback_) {completion_callback_();}
276  }
277 
278  if (is_active(pending_handle_)) {
279  debug_msg("Executing a pending handle on the existing thread.");
281  } else {
282  debug_msg("Done processing available goals.");
283  break;
284  }
285  }
286  debug_msg("Worker thread done.");
287  }
288 
292  void activate()
293  {
294  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
295  server_active_ = true;
296  stop_execution_ = false;
297  }
298 
302  void deactivate()
303  {
304  debug_msg("Deactivating...");
305 
306  {
307  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
308  server_active_ = false;
309  stop_execution_ = true;
310  }
311 
312  if (!execution_future_.valid()) {
313  return;
314  }
315 
316  if (is_running()) {
317  warn_msg(
318  "Requested to deactivate server but goal is still executing."
319  " Should check if action server is running before deactivating.");
320  }
321 
322  using namespace std::chrono; //NOLINT
323  auto start_time = steady_clock::now();
324  while (execution_future_.wait_for(milliseconds(100)) != std::future_status::ready) {
325  info_msg("Waiting for async process to finish.");
326  if (steady_clock::now() - start_time >= server_timeout_) {
327  terminate_all();
328  if (completion_callback_) {completion_callback_();}
329  error_msg("Action callback is still running and missed deadline to stop");
330  }
331  }
332 
333  debug_msg("Deactivation completed.");
334  }
335 
340  bool is_running()
341  {
342  return execution_future_.valid() &&
343  (execution_future_.wait_for(std::chrono::milliseconds(0)) ==
344  std::future_status::timeout);
345  }
346 
352  {
353  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
354  return server_active_;
355  }
356 
361  bool is_preempt_requested() const
362  {
363  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
364  return preempt_requested_;
365  }
366 
371  const std::shared_ptr<const typename ActionT::Goal> accept_pending_goal()
372  {
373  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
374 
375  if (!pending_handle_ || !pending_handle_->is_active()) {
376  error_msg("Attempting to get pending goal when not available");
377  return std::shared_ptr<const typename ActionT::Goal>();
378  }
379 
380  if (is_active(current_handle_) && current_handle_ != pending_handle_) {
381  debug_msg("Cancelling the previous goal");
382  current_handle_->abort(empty_result());
383  }
384 
385  current_handle_ = pending_handle_;
386  pending_handle_.reset();
387  preempt_requested_ = false;
388 
389  debug_msg("Preempted goal");
390 
391  return current_handle_->get_goal();
392  }
393 
398  {
399  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
400 
401  if (!pending_handle_ || !pending_handle_->is_active()) {
402  error_msg("Attempting to terminate pending goal when not available");
403  return;
404  }
405 
406  terminate(pending_handle_);
407  preempt_requested_ = false;
408 
409  debug_msg("Pending goal terminated");
410  }
411 
416  const std::shared_ptr<const typename ActionT::Goal> get_current_goal() const
417  {
418  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
419 
420  if (!is_active(current_handle_)) {
421  error_msg("A goal is not available or has reached a final state");
422  return std::shared_ptr<const typename ActionT::Goal>();
423  }
424 
425  return current_handle_->get_goal();
426  }
427 
428  const rclcpp_action::GoalUUID get_current_goal_id() const
429  {
430  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
431 
432  if (!is_active(current_handle_)) {
433  error_msg("A goal is not available or has reached a final state");
434  return rclcpp_action::GoalUUID();
435  }
436 
437  return current_handle_->get_goal_id();
438  }
439 
444  const std::shared_ptr<const typename ActionT::Goal> get_pending_goal() const
445  {
446  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
447 
448  if (!pending_handle_ || !pending_handle_->is_active()) {
449  error_msg("Attempting to get pending goal when not available");
450  return std::shared_ptr<const typename ActionT::Goal>();
451  }
452 
453  return pending_handle_->get_goal();
454  }
455 
460  bool is_cancel_requested() const
461  {
462  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
463 
464  // A cancel request is assumed if either handle is canceled by the client.
465 
466  if (current_handle_ == nullptr) {
467  error_msg("Checking for cancel but current goal is not available");
468  return false;
469  }
470 
471  if (pending_handle_ != nullptr) {
472  return pending_handle_->is_canceling();
473  }
474 
475  return current_handle_->is_canceling();
476  }
477 
483  typename std::shared_ptr<typename ActionT::Result> result =
484  std::make_shared<typename ActionT::Result>())
485  {
486  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
487  terminate(current_handle_, result);
488  terminate(pending_handle_, result);
489  preempt_requested_ = false;
490  }
491 
497  typename std::shared_ptr<typename ActionT::Result> result =
498  std::make_shared<typename ActionT::Result>())
499  {
500  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
501  terminate(current_handle_, result);
502  }
503 
509  typename std::shared_ptr<typename ActionT::Result> result =
510  std::make_shared<typename ActionT::Result>())
511  {
512  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
513 
514  if (is_active(current_handle_)) {
515  debug_msg("Setting succeed on current goal.");
516  current_handle_->succeed(result);
517  current_handle_.reset();
518  }
519  }
520 
525  void publish_feedback(typename std::shared_ptr<typename ActionT::Feedback> feedback)
526  {
527  if (!is_active(current_handle_)) {
528  error_msg("Trying to publish feedback when the current goal handle is not active");
529  return;
530  }
531 
532  current_handle_->publish_feedback(feedback);
533  }
534 
535 protected:
536  // The SimpleActionServer isn't itself a node, so it needs interfaces to one
537  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface_;
538  rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_interface_;
539  rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_interface_;
540  rclcpp::node_interfaces::NodeWaitablesInterface::SharedPtr node_waitables_interface_;
541  rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_interface_;
542  std::string action_name_;
543 
544  ExecuteCallback execute_callback_;
545  CompletionCallback completion_callback_;
546  std::future<void> execution_future_;
547  bool stop_execution_{false};
548  bool use_realtime_prioritization_{false};
549 
550  mutable std::recursive_mutex update_mutex_;
551  bool server_active_{false};
552  bool preempt_requested_{false};
553  std::chrono::milliseconds server_timeout_;
554 
555  std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> current_handle_;
556  std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> pending_handle_;
557 
558  typename rclcpp_action::Server<ActionT>::SharedPtr action_server_;
559  bool spin_thread_;
560  rclcpp::CallbackGroup::SharedPtr callback_group_{nullptr};
561  rclcpp::executors::SingleThreadedExecutor::SharedPtr executor_;
562  std::unique_ptr<nav2::NodeThread> executor_thread_;
563 
567  constexpr auto empty_result() const
568  {
569  return std::make_shared<typename ActionT::Result>();
570  }
571 
577  constexpr bool is_active(
578  const std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> handle) const
579  {
580  return handle != nullptr && handle->is_active();
581  }
582 
589  template<typename T, typename = void>
590  struct has_error_msg : std::false_type {};
591  template<typename T>
592  struct has_error_msg<T, std::void_t<decltype(T::error_msg)>>: std::true_type {};
593  template<typename T, typename = void>
594  struct has_error_code : std::false_type {};
595  template<typename T>
596  struct has_error_code<T, std::void_t<decltype(T::error_code)>>: std::true_type {};
597 
598  template<typename T>
599  void log_error_details_if_available(const T & result)
600  {
603  {
604  warn_msg("Aborting handle. error_code:" + std::to_string(result->error_code) +
605  ", error_msg:'" + result->error_msg + "'.");
606  } else if constexpr (has_error_code<typename ActionT::Result>::value) {
607  warn_msg("Aborting handle. error_code:" + std::to_string(result->error_code) + ".");
608  } else {
609  warn_msg("Aborting handle.");
610  }
611  }
612 
618  void terminate(
619  std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> & handle,
620  typename std::shared_ptr<typename ActionT::Result> result =
621  std::make_shared<typename ActionT::Result>())
622  {
623  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
624 
625  if (is_active(handle)) {
626  if (handle->is_canceling()) {
627  info_msg("Client requested to cancel the goal. Cancelling.");
628  handle->canceled(result);
629  } else {
630  log_error_details_if_available(result);
631  handle->abort(result);
632  }
633  handle.reset();
634  }
635  }
636 
640  void info_msg(const std::string & msg) const
641  {
642  RCLCPP_INFO(
643  node_logging_interface_->get_logger(),
644  "[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
645  }
646 
650  void debug_msg(const std::string & msg) const
651  {
652  RCLCPP_DEBUG(
653  node_logging_interface_->get_logger(),
654  "[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
655  }
656 
660  void error_msg(const std::string & msg) const
661  {
662  RCLCPP_ERROR(
663  node_logging_interface_->get_logger(),
664  "[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
665  }
666 
670  void warn_msg(const std::string & msg) const
671  {
672  RCLCPP_WARN(
673  node_logging_interface_->get_logger(),
674  "[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
675  }
676 };
677 
678 } // namespace nav2
679 
680 #endif // NAV2_ROS_COMMON__SIMPLE_ACTION_SERVER_HPP_
An action server wrapper to make applications simpler using Actions.
void terminate_pending_goal()
Terminate pending goals.
constexpr bool is_active(const std::shared_ptr< rclcpp_action::ServerGoalHandle< ActionT >> handle) const
Whether a given goal handle is currently active.
constexpr auto empty_result() const
Generate an empty result object for an action type.
void publish_feedback(typename std::shared_ptr< typename ActionT::Feedback > feedback)
Publish feedback to the action server clients.
void setSoftRealTimePriority()
Sets thread priority level.
void warn_msg(const std::string &msg) const
Warn logging.
const std::shared_ptr< const typename ActionT::Goal > get_pending_goal() const
Get the pending goal object.
void debug_msg(const std::string &msg) const
Debug logging.
void info_msg(const std::string &msg) const
Info logging.
void terminate(std::shared_ptr< rclcpp_action::ServerGoalHandle< ActionT >> &handle, typename std::shared_ptr< typename ActionT::Result > result=std::make_shared< typename ActionT::Result >())
Terminate a particular action with a result.
void terminate_current(typename std::shared_ptr< typename ActionT::Result > result=std::make_shared< typename ActionT::Result >())
Terminate the active action.
void work()
Computed background work and processes stop requests.
const std::shared_ptr< const typename ActionT::Goal > get_current_goal() const
Get the current goal object.
void activate()
Active action server.
void handle_accepted(const std::shared_ptr< rclcpp_action::ServerGoalHandle< ActionT >> handle)
Handles accepted goals and adds to preempted queue to switch to.
bool is_cancel_requested() const
Whether or not a cancel command has come in.
void deactivate()
Deactivate action server.
void terminate_all(typename std::shared_ptr< typename ActionT::Result > result=std::make_shared< typename ActionT::Result >())
Terminate all pending and active actions.
SimpleActionServer(NodeT node, const std::string &action_name, ExecuteCallback execute_callback, CompletionCallback completion_callback=nullptr, std::chrono::milliseconds server_timeout=std::chrono::milliseconds(500), bool spin_thread=false, const bool realtime=false)
An constructor for SimpleActionServer.
rclcpp_action::CancelResponse handle_cancel(const std::shared_ptr< rclcpp_action::ServerGoalHandle< ActionT >> handle)
Accepts cancellation requests of action server.
bool is_preempt_requested() const
Whether the action server has been asked to be preempted with a new goal.
void error_msg(const std::string &msg) const
Error logging.
bool is_running()
Whether the action server is munching on a goal.
void succeeded_current(typename std::shared_ptr< typename ActionT::Result > result=std::make_shared< typename ActionT::Result >())
Return success of the active action.
bool is_server_active()
Whether the action server is active or not.
SimpleActionServer(rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface, rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_interface, rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_interface, rclcpp::node_interfaces::NodeWaitablesInterface::SharedPtr node_waitables_interface, rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_interface, const std::string &action_name, ExecuteCallback execute_callback, CompletionCallback completion_callback=nullptr, std::chrono::milliseconds server_timeout=std::chrono::milliseconds(500), bool spin_thread=false, const bool realtime=false)
An constructor for SimpleActionServer.
const std::shared_ptr< const typename ActionT::Goal > accept_pending_goal()
Accept pending goals.
rclcpp_action::GoalResponse handle_goal(const rclcpp_action::GoalUUID &, std::shared_ptr< const typename ActionT::Goal >)
handle the goal requested: accept or reject. This implementation always accepts when the server is ac...
SFINAE (Substitution Failure Is Not An Error) to check for existence of error_code and error_msg in A...