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/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 validate a goal before acceptance and execution.
51  // Return true to accept the goal and false to reject it.
52  typedef std::function<bool (std::shared_ptr<const typename ActionT::Goal>)> GoalReceivedCallback;
53 
54  // Callback function to notify the user that an exception was thrown that
55  // the simple action server caught (or another failure) and the action was
56  // terminated. To avoid using, catch exceptions in your application such that
57  // the SimpleActionServer will never need to terminate based on failed action
58  // ExecuteCallback.
59  typedef std::function<void ()> CompletionCallback;
60 
71  template<typename NodeT>
73  NodeT node,
74  const std::string & action_name,
75  ExecuteCallback execute_callback,
76  GoalReceivedCallback goal_received_callback = nullptr,
77  CompletionCallback completion_callback = nullptr,
78  std::chrono::milliseconds server_timeout = std::chrono::milliseconds(500),
79  bool spin_thread = false,
80  const bool realtime = false)
82  node->get_node_base_interface(),
83  node->get_node_clock_interface(),
84  node->get_node_logging_interface(),
85  node->get_node_waitables_interface(),
86  node->get_node_parameters_interface(),
87  action_name, execute_callback, goal_received_callback, completion_callback,
88  server_timeout, spin_thread, realtime)
89  {}
90 
102  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface,
103  rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_interface,
104  rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_interface,
105  rclcpp::node_interfaces::NodeWaitablesInterface::SharedPtr node_waitables_interface,
106  rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_interface,
107  const std::string & action_name,
108  ExecuteCallback execute_callback,
109  GoalReceivedCallback goal_received_callback = nullptr,
110  CompletionCallback completion_callback = nullptr,
111  std::chrono::milliseconds server_timeout = std::chrono::milliseconds(500),
112  bool spin_thread = false,
113  const bool realtime = false)
114  : node_base_interface_(node_base_interface),
115  node_clock_interface_(node_clock_interface),
116  node_logging_interface_(node_logging_interface),
117  node_waitables_interface_(node_waitables_interface),
118  node_parameters_interface_(node_parameters_interface),
119  action_name_(action_name),
120  execute_callback_(execute_callback),
121  goal_received_callback_(goal_received_callback),
122  completion_callback_(completion_callback),
123  server_timeout_(server_timeout),
124  spin_thread_(spin_thread)
125  {
126  using namespace std::placeholders; // NOLINT
127  use_realtime_prioritization_ = realtime;
128  if (spin_thread_) {
129  callback_group_ = node_base_interface->create_callback_group(
130  rclcpp::CallbackGroupType::MutuallyExclusive, false);
131  }
132  action_server_ = rclcpp_action::create_server<ActionT>(
133  node_base_interface_,
134  node_clock_interface_,
135  node_logging_interface_,
136  node_waitables_interface_,
137  action_name_,
138  std::bind(&SimpleActionServer::handle_goal, this, _1, _2),
139  std::bind(&SimpleActionServer::handle_cancel, this, _1),
140  std::bind(&SimpleActionServer::handle_accepted, this, _1),
141  rcl_action_server_get_default_options(), // Use consistent QoS settings
142  callback_group_);
143 
144  nav2::setIntrospectionMode(
145  this->action_server_,
146  node_parameters_interface_, node_clock_interface_->get_clock());
147 
148  if (spin_thread_) {
149  executor_ = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
150  executor_->add_callback_group(callback_group_, node_base_interface_);
151  executor_thread_ = std::make_unique<nav2::NodeThread>(executor_);
152  }
153  }
154 
162  rclcpp_action::GoalResponse handle_goal(
163  const rclcpp_action::GoalUUID & /*uuid*/,
164  std::shared_ptr<const typename ActionT::Goal> goal)
165  {
166  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
167 
168  if (!server_active_) {
169  RCLCPP_INFO(
170  node_logging_interface_->get_logger(),
171  "Action server is inactive. Rejecting the goal.");
172  return rclcpp_action::GoalResponse::REJECT;
173  }
174 
175  if (goal_received_callback_ && !goal_received_callback_(goal)) {
176  debug_msg("Goal received callback rejected goal");
177  return rclcpp_action::GoalResponse::REJECT;
178  }
179 
180  debug_msg("Received request for goal acceptance");
181  return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
182  }
183 
190  rclcpp_action::CancelResponse handle_cancel(
191  const std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> handle)
192  {
193  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
194 
195  if (!handle->is_active()) {
196  warn_msg(
197  "Received request for goal cancellation,"
198  "but the handle is inactive, so reject the request");
199  return rclcpp_action::CancelResponse::REJECT;
200  }
201 
202  debug_msg("Received request for goal cancellation");
203  return rclcpp_action::CancelResponse::ACCEPT;
204  }
205 
210  {
211  if (use_realtime_prioritization_) {
212  nav2::setSoftRealTimePriority();
213  debug_msg("Soft realtime prioritization successfully set!");
214  }
215  }
216 
221  void handle_accepted(const std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> handle)
222  {
223  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
224  debug_msg("Receiving a new goal");
225 
226  if (is_active(current_handle_) || is_running()) {
227  debug_msg("An older goal is active, moving the new goal to a pending slot.");
228 
229  if (is_active(pending_handle_)) {
230  debug_msg(
231  "The pending slot is occupied."
232  " The previous pending goal will be terminated and replaced.");
233  terminate(pending_handle_);
234  }
235  pending_handle_ = handle;
236  preempt_requested_ = true;
237  } else {
238  if (is_active(pending_handle_)) {
239  // Shouldn't reach a state with a pending goal but no current one.
240  error_msg("Forgot to handle a preemption. Terminating the pending goal.");
241  terminate(pending_handle_);
242  preempt_requested_ = false;
243  }
244 
245  current_handle_ = handle;
246 
247  // Return quickly to avoid blocking the executor, so spin up a new thread
248  debug_msg("Executing goal asynchronously.");
249  execution_future_ = std::async(
250  std::launch::async, [this]() {
252  work();
253  });
254  }
255  }
256 
260  void work()
261  {
262  while (rclcpp::ok() && !stop_execution_ && is_active(current_handle_)) {
263  debug_msg("Executing the goal...");
264  try {
265  execute_callback_();
266  } catch (std::exception & ex) {
267  RCLCPP_ERROR(
268  node_logging_interface_->get_logger(),
269  "Action server failed while executing action callback: \"%s\"", ex.what());
270  terminate_all();
271  if (completion_callback_) {completion_callback_();}
272  return;
273  }
274 
275  debug_msg("Blocking processing of new goal handles.");
276  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
277 
278  if (stop_execution_) {
279  warn_msg("Stopping the thread per request.");
280  terminate_all();
281  if (completion_callback_) {completion_callback_();}
282  break;
283  }
284 
285  if (is_active(current_handle_)) {
286  warn_msg("Current goal was not completed successfully.");
287  terminate(current_handle_);
288  if (completion_callback_) {completion_callback_();}
289  }
290 
291  if (is_active(pending_handle_)) {
292  debug_msg("Executing a pending handle on the existing thread.");
294  } else {
295  debug_msg("Done processing available goals.");
296  break;
297  }
298  }
299  debug_msg("Worker thread done.");
300  }
301 
305  void activate()
306  {
307  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
308  server_active_ = true;
309  stop_execution_ = false;
310  }
311 
315  void deactivate()
316  {
317  debug_msg("Deactivating...");
318 
319  {
320  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
321  server_active_ = false;
322  stop_execution_ = true;
323  }
324 
325  if (!execution_future_.valid()) {
326  return;
327  }
328 
329  if (is_running()) {
330  warn_msg(
331  "Requested to deactivate server but goal is still executing."
332  " Should check if action server is running before deactivating.");
333  }
334 
335  using namespace std::chrono; //NOLINT
336  auto start_time = steady_clock::now();
337  while (execution_future_.wait_for(milliseconds(100)) != std::future_status::ready) {
338  info_msg("Waiting for async process to finish.");
339  if (steady_clock::now() - start_time >= server_timeout_) {
340  terminate_all();
341  if (completion_callback_) {completion_callback_();}
342  error_msg("Action callback is still running and missed deadline to stop");
343  }
344  }
345 
346  debug_msg("Deactivation completed.");
347  }
348 
353  bool is_running()
354  {
355  return execution_future_.valid() &&
356  (execution_future_.wait_for(std::chrono::milliseconds(0)) ==
357  std::future_status::timeout);
358  }
359 
365  {
366  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
367  return server_active_;
368  }
369 
374  bool is_preempt_requested() const
375  {
376  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
377  return preempt_requested_;
378  }
379 
384  const std::shared_ptr<const typename ActionT::Goal> accept_pending_goal()
385  {
386  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
387 
388  if (!pending_handle_ || !pending_handle_->is_active()) {
389  error_msg("Attempting to get pending goal when not available");
390  return std::shared_ptr<const typename ActionT::Goal>();
391  }
392 
393  if (is_active(current_handle_) && current_handle_ != pending_handle_) {
394  debug_msg("Cancelling the previous goal");
395  current_handle_->abort(empty_result());
396  }
397 
398  current_handle_ = pending_handle_;
399  pending_handle_.reset();
400  preempt_requested_ = false;
401 
402  debug_msg("Preempted goal");
403 
404  return current_handle_->get_goal();
405  }
406 
411  {
412  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
413 
414  if (!pending_handle_ || !pending_handle_->is_active()) {
415  error_msg("Attempting to terminate pending goal when not available");
416  return;
417  }
418 
419  terminate(pending_handle_);
420  preempt_requested_ = false;
421 
422  debug_msg("Pending goal terminated");
423  }
424 
429  const std::shared_ptr<const typename ActionT::Goal> get_current_goal() const
430  {
431  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
432 
433  if (!is_active(current_handle_)) {
434  error_msg("A goal is not available or has reached a final state");
435  return std::shared_ptr<const typename ActionT::Goal>();
436  }
437 
438  return current_handle_->get_goal();
439  }
440 
441  const rclcpp_action::GoalUUID get_current_goal_id() const
442  {
443  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
444 
445  if (!is_active(current_handle_)) {
446  error_msg("A goal is not available or has reached a final state");
447  return rclcpp_action::GoalUUID();
448  }
449 
450  return current_handle_->get_goal_id();
451  }
452 
457  const std::shared_ptr<const typename ActionT::Goal> get_pending_goal() const
458  {
459  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
460 
461  if (!pending_handle_ || !pending_handle_->is_active()) {
462  error_msg("Attempting to get pending goal when not available");
463  return std::shared_ptr<const typename ActionT::Goal>();
464  }
465 
466  return pending_handle_->get_goal();
467  }
468 
473  bool is_cancel_requested() const
474  {
475  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
476 
477  // A cancel request is assumed if either handle is canceled by the client.
478 
479  if (current_handle_ == nullptr) {
480  error_msg("Checking for cancel but current goal is not available");
481  return false;
482  }
483 
484  if (pending_handle_ != nullptr) {
485  return pending_handle_->is_canceling();
486  }
487 
488  return current_handle_->is_canceling();
489  }
490 
496  typename std::shared_ptr<typename ActionT::Result> result =
497  std::make_shared<typename ActionT::Result>())
498  {
499  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
500  terminate(current_handle_, result);
501  terminate(pending_handle_, result);
502  preempt_requested_ = false;
503  }
504 
510  typename std::shared_ptr<typename ActionT::Result> result =
511  std::make_shared<typename ActionT::Result>())
512  {
513  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
514  terminate(current_handle_, result);
515  }
516 
522  typename std::shared_ptr<typename ActionT::Result> result =
523  std::make_shared<typename ActionT::Result>())
524  {
525  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
526 
527  if (is_active(current_handle_)) {
528  debug_msg("Setting succeed on current goal.");
529  current_handle_->succeed(result);
530  current_handle_.reset();
531  }
532  }
533 
538  void publish_feedback(typename std::shared_ptr<typename ActionT::Feedback> feedback)
539  {
540  if (!is_active(current_handle_)) {
541  error_msg("Trying to publish feedback when the current goal handle is not active");
542  return;
543  }
544 
545  current_handle_->publish_feedback(feedback);
546  }
547 
548 protected:
549  // The SimpleActionServer isn't itself a node, so it needs interfaces to one
550  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface_;
551  rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_interface_;
552  rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_interface_;
553  rclcpp::node_interfaces::NodeWaitablesInterface::SharedPtr node_waitables_interface_;
554  rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_interface_;
555  std::string action_name_;
556 
557  ExecuteCallback execute_callback_;
558  GoalReceivedCallback goal_received_callback_;
559  CompletionCallback completion_callback_;
560  std::future<void> execution_future_;
561  bool stop_execution_{false};
562  bool use_realtime_prioritization_{false};
563 
564  mutable std::recursive_mutex update_mutex_;
565  bool server_active_{false};
566  bool preempt_requested_{false};
567  std::chrono::milliseconds server_timeout_;
568 
569  std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> current_handle_;
570  std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> pending_handle_;
571 
572  typename rclcpp_action::Server<ActionT>::SharedPtr action_server_;
573  bool spin_thread_;
574  rclcpp::CallbackGroup::SharedPtr callback_group_{nullptr};
575  rclcpp::executors::SingleThreadedExecutor::SharedPtr executor_;
576  std::unique_ptr<nav2::NodeThread> executor_thread_;
577 
581  constexpr auto empty_result() const
582  {
583  return std::make_shared<typename ActionT::Result>();
584  }
585 
591  constexpr bool is_active(
592  const std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> handle) const
593  {
594  return handle != nullptr && handle->is_active();
595  }
596 
603  template<typename T, typename = void>
604  struct has_error_msg : std::false_type {};
605  template<typename T>
606  struct has_error_msg<T, std::void_t<decltype(T::error_msg)>>: std::true_type {};
607  template<typename T, typename = void>
608  struct has_error_code : std::false_type {};
609  template<typename T>
610  struct has_error_code<T, std::void_t<decltype(T::error_code)>>: std::true_type {};
611 
612  template<typename T>
613  void log_error_details_if_available(const T & result)
614  {
617  {
618  warn_msg(
619  "Aborting handle. error_code:" + std::to_string(result->error_code) +
620  ", error_msg:'" + result->error_msg + "'.");
621  } else if constexpr (has_error_code<typename ActionT::Result>::value) {
622  warn_msg("Aborting handle. error_code:" + std::to_string(result->error_code) + ".");
623  } else {
624  warn_msg("Aborting handle.");
625  }
626  }
627 
633  void terminate(
634  std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> & handle,
635  typename std::shared_ptr<typename ActionT::Result> result =
636  std::make_shared<typename ActionT::Result>())
637  {
638  std::lock_guard<std::recursive_mutex> lock(update_mutex_);
639 
640  if (is_active(handle)) {
641  if (handle->is_canceling()) {
642  info_msg("Client requested to cancel the goal. Cancelling.");
643  handle->canceled(result);
644  } else {
645  log_error_details_if_available(result);
646  handle->abort(result);
647  }
648  handle.reset();
649  }
650  }
651 
655  void info_msg(const std::string & msg) const
656  {
657  RCLCPP_INFO(
658  node_logging_interface_->get_logger(),
659  "[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
660  }
661 
665  void debug_msg(const std::string & msg) const
666  {
667  RCLCPP_DEBUG(
668  node_logging_interface_->get_logger(),
669  "[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
670  }
671 
675  void error_msg(const std::string & msg) const
676  {
677  RCLCPP_ERROR(
678  node_logging_interface_->get_logger(),
679  "[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
680  }
681 
685  void warn_msg(const std::string & msg) const
686  {
687  RCLCPP_WARN(
688  node_logging_interface_->get_logger(),
689  "[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
690  }
691 };
692 
693 } // namespace nav2
694 
695 #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...