Nav2 Navigation Stack - lyrical  lyrical
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/callback_group.hpp"
27 #include "rclcpp/executors/single_threaded_executor.hpp"
28 #include "rclcpp/logging.hpp"
29 #include "rclcpp/node_interfaces/node_base_interface.hpp"
30 #include "rclcpp/node_interfaces/node_clock_interface.hpp"
31 #include "rclcpp/node_interfaces/node_logging_interface.hpp"
32 #include "rclcpp/node_interfaces/node_parameters_interface.hpp"
33 #include "rclcpp/node_interfaces/node_waitables_interface.hpp"
34 #include "rclcpp/utilities.hpp"
35 #include "rclcpp_action/rclcpp_action.hpp"
36 #include "nav2_ros_common/node_thread.hpp"
37 #include "nav2_ros_common/node_utils.hpp"
38 
39 namespace nav2
40 {
41 
46 template<typename ActionT>
48 {
49 public:
50  using SharedPtr = std::shared_ptr<nav2::SimpleActionServer<ActionT>>;
51  using UniquePtr = std::unique_ptr<nav2::SimpleActionServer<ActionT>>;
52 
53  // Callback function to complete main work. This should itself deal with its
54  // own exceptions, but if for some reason one is thrown, it will be caught
55  // in SimpleActionServer and terminate the action itself.
56  typedef std::function<void ()> ExecuteCallback;
57 
58  // Callback function to validate a goal before acceptance and execution.
59  // Return true to accept the goal and false to reject it.
60  typedef std::function<bool (std::shared_ptr<const typename ActionT::Goal>)> GoalReceivedCallback;
61 
62  // Callback function to notify the user that an exception was thrown that
63  // the simple action server caught (or another failure) and the action was
64  // terminated. To avoid using, catch exceptions in your application such that
65  // the SimpleActionServer will never need to terminate based on failed action
66  // ExecuteCallback.
67  typedef std::function<void ()> CompletionCallback;
68 
79  template<typename NodeT>
81  NodeT node,
82  const std::string & action_name,
83  ExecuteCallback execute_callback,
84  GoalReceivedCallback goal_received_callback = nullptr,
85  CompletionCallback completion_callback = nullptr,
86  std::chrono::milliseconds server_timeout = std::chrono::milliseconds(500),
87  bool spin_thread = false,
88  const bool realtime = false)
90  node->get_node_base_interface(),
91  node->get_node_clock_interface(),
92  node->get_node_logging_interface(),
93  node->get_node_waitables_interface(),
94  node->get_node_parameters_interface(),
95  action_name, execute_callback, goal_received_callback, completion_callback,
96  server_timeout, spin_thread, realtime)
97  {}
98 
110  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface,
111  rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_interface,
112  rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_interface,
113  rclcpp::node_interfaces::NodeWaitablesInterface::SharedPtr node_waitables_interface,
114  rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_interface,
115  const std::string & action_name,
116  ExecuteCallback execute_callback,
117  GoalReceivedCallback goal_received_callback = nullptr,
118  CompletionCallback completion_callback = nullptr,
119  std::chrono::milliseconds server_timeout = std::chrono::milliseconds(500),
120  bool spin_thread = false,
121  const bool realtime = false)
122  : node_base_interface_(node_base_interface),
123  node_clock_interface_(node_clock_interface),
124  node_logging_interface_(node_logging_interface),
125  node_waitables_interface_(node_waitables_interface),
126  node_parameters_interface_(node_parameters_interface),
127  action_name_(action_name),
128  execute_callback_(execute_callback),
129  goal_received_callback_(goal_received_callback),
130  completion_callback_(completion_callback),
131  server_timeout_(server_timeout),
132  spin_thread_(spin_thread)
133  {
134  using namespace std::placeholders; // NOLINT
135  use_realtime_prioritization_ = realtime;
136  if (spin_thread_) {
137  callback_group_ = node_base_interface->create_callback_group(
138  rclcpp::CallbackGroupType::MutuallyExclusive, false);
139  }
140  action_server_ = rclcpp_action::create_server<ActionT>(
141  node_base_interface_,
142  node_clock_interface_,
143  node_logging_interface_,
144  node_waitables_interface_,
145  action_name_,
146  std::bind(&SimpleActionServer::handle_goal, this, _1, _2),
147  std::bind(&SimpleActionServer::handle_cancel, this, _1),
148  std::bind(&SimpleActionServer::handle_accepted, this, _1),
149  rcl_action_server_get_default_options(), // Use consistent QoS settings
150  callback_group_);
151 
152  nav2::setIntrospectionMode(
153  this->action_server_,
154  node_parameters_interface_, node_clock_interface_->get_clock());
155 
156  if (spin_thread_) {
157  executor_ = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
158  executor_->add_callback_group(callback_group_, node_base_interface_);
159  executor_thread_ = std::make_unique<nav2::NodeThread>(executor_);
160  }
161  }
162 
170  rclcpp_action::GoalResponse handle_goal(
171  const rclcpp_action::GoalUUID & /*uuid*/,
172  std::shared_ptr<const typename ActionT::Goal> goal)
173  {
174  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
175 
176  if (!server_active_) {
177  RCLCPP_INFO(
178  node_logging_interface_->get_logger(),
179  "Action server is inactive. Rejecting the goal.");
180  return rclcpp_action::GoalResponse::REJECT;
181  }
182 
183  if (goal_received_callback_ && !goal_received_callback_(goal)) {
184  debug_msg("Goal received callback rejected goal");
185  return rclcpp_action::GoalResponse::REJECT;
186  }
187 
188  debug_msg("Received request for goal acceptance");
189  return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
190  }
191 
198  rclcpp_action::CancelResponse handle_cancel(
199  const std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> handle)
200  {
201  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
202 
203  if (!handle->is_active()) {
204  warn_msg(
205  "Received request for goal cancellation,"
206  "but the handle is inactive, so reject the request");
207  return rclcpp_action::CancelResponse::REJECT;
208  }
209 
210  debug_msg("Received request for goal cancellation");
211  return rclcpp_action::CancelResponse::ACCEPT;
212  }
213 
218  {
219  if (use_realtime_prioritization_) {
220  nav2::setSoftRealTimePriority();
221  debug_msg("Soft realtime prioritization successfully set!");
222  }
223  }
224 
229  void handle_accepted(const std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> handle)
230  {
231  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
232  debug_msg("Receiving a new goal");
233 
234  if (is_active(current_handle_) || is_running()) {
235  debug_msg("An older goal is active, moving the new goal to a pending slot.");
236 
237  if (is_active(pending_handle_)) {
238  debug_msg(
239  "The pending slot is occupied."
240  " The previous pending goal will be terminated and replaced.");
241  terminate(pending_handle_);
242  }
243  pending_handle_ = handle;
244  preempt_requested_ = true;
245  } else {
246  if (is_active(pending_handle_)) {
247  // Shouldn't reach a state with a pending goal but no current one.
248  error_msg("Forgot to handle a preemption. Terminating the pending goal.");
249  terminate(pending_handle_);
250  preempt_requested_ = false;
251  }
252 
253  current_handle_ = handle;
254 
255  // Return quickly to avoid blocking the executor, so spin up a new thread
256  debug_msg("Executing goal asynchronously.");
257  execution_future_ = std::async(
258  std::launch::async, [this]() {
260  work();
261  });
262  }
263  }
264 
268  void work()
269  {
270  while (rclcpp::ok() && !stop_execution_ && is_active(current_handle_)) {
271  debug_msg("Executing the goal...");
272  try {
273  execute_callback_();
274  } catch (std::exception & ex) {
275  RCLCPP_ERROR(
276  node_logging_interface_->get_logger(),
277  "Action server failed while executing action callback: \"%s\"", ex.what());
278  terminate_all();
279  if (completion_callback_) {completion_callback_();}
280  return;
281  }
282 
283  debug_msg("Blocking processing of new goal handles.");
284  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
285 
286  if (stop_execution_) {
287  warn_msg("Stopping the thread per request.");
288  terminate_all();
289  if (completion_callback_) {completion_callback_();}
290  break;
291  }
292 
293  if (is_active(current_handle_)) {
294  warn_msg("Current goal was not completed successfully.");
295  terminate(current_handle_);
296  if (completion_callback_) {completion_callback_();}
297  }
298 
299  if (is_active(pending_handle_)) {
300  debug_msg("Executing a pending handle on the existing thread.");
302  } else {
303  debug_msg("Done processing available goals.");
304  break;
305  }
306  }
307  debug_msg("Worker thread done.");
308  }
309 
313  void activate()
314  {
315  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
316  server_active_ = true;
317  stop_execution_ = false;
318  }
319 
323  void deactivate()
324  {
325  debug_msg("Deactivating...");
326 
327  {
328  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
329  server_active_ = false;
330  stop_execution_ = true;
331  }
332 
333  if (!execution_future_.valid()) {
334  return;
335  }
336 
337  if (is_running()) {
338  warn_msg(
339  "Requested to deactivate server but goal is still executing."
340  " Should check if action server is running before deactivating.");
341  }
342 
343  using namespace std::chrono; //NOLINT
344  auto start_time = steady_clock::now();
345  while (execution_future_.wait_for(milliseconds(100)) != std::future_status::ready) {
346  info_msg("Waiting for async process to finish.");
347  if (steady_clock::now() - start_time >= server_timeout_) {
348  terminate_all();
349  if (completion_callback_) {completion_callback_();}
350  error_msg("Action callback is still running and missed deadline to stop");
351  }
352  }
353 
354  debug_msg("Deactivation completed.");
355  }
356 
361  bool is_running()
362  {
363  return execution_future_.valid() &&
364  (execution_future_.wait_for(std::chrono::milliseconds(0)) ==
365  std::future_status::timeout);
366  }
367 
373  {
374  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
375  return server_active_;
376  }
377 
382  bool is_preempt_requested() const
383  {
384  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
385  return preempt_requested_;
386  }
387 
392  const std::shared_ptr<const typename ActionT::Goal> accept_pending_goal()
393  {
394  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
395 
396  if (!pending_handle_ || !pending_handle_->is_active()) {
397  error_msg("Attempting to get pending goal when not available");
398  return std::shared_ptr<const typename ActionT::Goal>();
399  }
400 
401  if (is_active(current_handle_) && current_handle_ != pending_handle_) {
402  debug_msg("Cancelling the previous goal");
403  current_handle_->abort(empty_result());
404  }
405 
406  current_handle_ = pending_handle_;
407  pending_handle_.reset();
408  preempt_requested_ = false;
409 
410  debug_msg("Preempted goal");
411 
412  return current_handle_->get_goal();
413  }
414 
419  {
420  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
421 
422  if (!pending_handle_ || !pending_handle_->is_active()) {
423  error_msg("Attempting to terminate pending goal when not available");
424  return;
425  }
426 
427  terminate(pending_handle_);
428  preempt_requested_ = false;
429 
430  debug_msg("Pending goal terminated");
431  }
432 
437  const std::shared_ptr<const typename ActionT::Goal> get_current_goal() const
438  {
439  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
440 
441  if (!is_active(current_handle_)) {
442  error_msg("A goal is not available or has reached a final state");
443  return std::shared_ptr<const typename ActionT::Goal>();
444  }
445 
446  return current_handle_->get_goal();
447  }
448 
449  const rclcpp_action::GoalUUID get_current_goal_id() const
450  {
451  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
452 
453  if (!is_active(current_handle_)) {
454  error_msg("A goal is not available or has reached a final state");
455  return rclcpp_action::GoalUUID();
456  }
457 
458  return current_handle_->get_goal_id();
459  }
460 
465  const std::shared_ptr<const typename ActionT::Goal> get_pending_goal() const
466  {
467  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
468 
469  if (!pending_handle_ || !pending_handle_->is_active()) {
470  error_msg("Attempting to get pending goal when not available");
471  return std::shared_ptr<const typename ActionT::Goal>();
472  }
473 
474  return pending_handle_->get_goal();
475  }
476 
481  bool is_cancel_requested() const
482  {
483  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
484 
485  // A cancel request is assumed if either handle is canceled by the client.
486 
487  if (current_handle_ == nullptr) {
488  error_msg("Checking for cancel but current goal is not available");
489  return false;
490  }
491 
492  if (pending_handle_ != nullptr) {
493  return pending_handle_->is_canceling();
494  }
495 
496  return current_handle_->is_canceling();
497  }
498 
504  typename std::shared_ptr<typename ActionT::Result> result =
505  std::make_shared<typename ActionT::Result>())
506  {
507  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
508  terminate(current_handle_, result);
509  terminate(pending_handle_, result);
510  preempt_requested_ = false;
511  }
512 
518  typename std::shared_ptr<typename ActionT::Result> result =
519  std::make_shared<typename ActionT::Result>())
520  {
521  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
522  terminate(current_handle_, result);
523  }
524 
530  typename std::shared_ptr<typename ActionT::Result> result =
531  std::make_shared<typename ActionT::Result>())
532  {
533  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
534 
535  if (is_active(current_handle_)) {
536  debug_msg("Setting succeed on current goal.");
537  current_handle_->succeed(result);
538  current_handle_.reset();
539  }
540  }
541 
546  void publish_feedback(typename std::shared_ptr<typename ActionT::Feedback> feedback)
547  {
548  if (!is_active(current_handle_)) {
549  error_msg("Trying to publish feedback when the current goal handle is not active");
550  return;
551  }
552 
553  current_handle_->publish_feedback(feedback);
554  }
555 
556 protected:
557  // The SimpleActionServer isn't itself a node, so it needs interfaces to one
558  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface_;
559  rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_interface_;
560  rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_interface_;
561  rclcpp::node_interfaces::NodeWaitablesInterface::SharedPtr node_waitables_interface_;
562  rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_interface_;
563  std::string action_name_;
564 
565  ExecuteCallback execute_callback_;
566  GoalReceivedCallback goal_received_callback_;
567  CompletionCallback completion_callback_;
568  std::future<void> execution_future_;
569  bool stop_execution_{false};
570  bool use_realtime_prioritization_{false};
571 
572  mutable std::recursive_mutex update_mutex_;
573  bool server_active_{false};
574  bool preempt_requested_{false};
575  std::chrono::milliseconds server_timeout_;
576 
577  std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> current_handle_;
578  std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> pending_handle_;
579 
580  typename rclcpp_action::Server<ActionT>::SharedPtr action_server_;
581  bool spin_thread_;
582  rclcpp::CallbackGroup::SharedPtr callback_group_{nullptr};
583  rclcpp::executors::SingleThreadedExecutor::SharedPtr executor_;
584  std::unique_ptr<nav2::NodeThread> executor_thread_;
585 
589  constexpr auto empty_result() const
590  {
591  return std::make_shared<typename ActionT::Result>();
592  }
593 
599  constexpr bool is_active(
600  const std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> handle) const
601  {
602  return handle != nullptr && handle->is_active();
603  }
604 
611  template<typename T, typename = void>
612  struct has_error_msg : std::false_type {};
613  template<typename T>
614  struct has_error_msg<T, std::void_t<decltype(T::error_msg)>>: std::true_type {};
615  template<typename T, typename = void>
616  struct has_error_code : std::false_type {};
617  template<typename T>
618  struct has_error_code<T, std::void_t<decltype(T::error_code)>>: std::true_type {};
619 
620  template<typename T>
621  void log_error_details_if_available(const T & result)
622  {
625  {
626  warn_msg(
627  "Aborting handle. error_code:" + std::to_string(result->error_code) +
628  ", error_msg:'" + result->error_msg + "'.");
629  } else if constexpr (has_error_code<typename ActionT::Result>::value) {
630  warn_msg("Aborting handle. error_code:" + std::to_string(result->error_code) + ".");
631  } else {
632  warn_msg("Aborting handle.");
633  }
634  }
635 
641  void terminate(
642  std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> & handle,
643  typename std::shared_ptr<typename ActionT::Result> result =
644  std::make_shared<typename ActionT::Result>())
645  {
646  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
647 
648  if (is_active(handle)) {
649  if (handle->is_canceling()) {
650  info_msg("Client requested to cancel the goal. Cancelling.");
651  handle->canceled(result);
652  } else {
653  log_error_details_if_available(result);
654  handle->abort(result);
655  }
656  handle.reset();
657  }
658  }
659 
663  void info_msg(const std::string & msg) const
664  {
665  RCLCPP_INFO(
666  node_logging_interface_->get_logger(),
667  "[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
668  }
669 
673  void debug_msg(const std::string & msg) const
674  {
675  RCLCPP_DEBUG(
676  node_logging_interface_->get_logger(),
677  "[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
678  }
679 
683  void error_msg(const std::string & msg) const
684  {
685  RCLCPP_ERROR(
686  node_logging_interface_->get_logger(),
687  "[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
688  }
689 
693  void warn_msg(const std::string & msg) const
694  {
695  RCLCPP_WARN(
696  node_logging_interface_->get_logger(),
697  "[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
698  }
699 };
700 
701 } // namespace nav2
702 
703 #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.
SimpleActionServer(NodeT node, const std::string &action_name, ExecuteCallback execute_callback, GoalReceivedCallback goal_received_callback=nullptr, 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.
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.
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, GoalReceivedCallback goal_received_callback=nullptr, 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.
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.
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.
rclcpp_action::GoalResponse handle_goal(const rclcpp_action::GoalUUID &, std::shared_ptr< const typename ActionT::Goal > goal)
handle the goal requested: accept or reject. This implementation always accepts when the server is ac...
bool is_server_active()
Whether the action server is active or not.
const std::shared_ptr< const typename ActionT::Goal > accept_pending_goal()
Accept pending goals.
SFINAE (Substitution Failure Is Not An Error) to check for existence of error_code and error_msg in A...