Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
lifecycle_manager.cpp
1 // Copyright (c) 2019 Intel Corporation
2 // Copyright (c) 2022 Samsung Research America
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 #include "nav2_lifecycle_manager/lifecycle_manager.hpp"
17 
18 #include <chrono>
19 #include <memory>
20 #include <string>
21 #include <vector>
22 
23 #include "rclcpp/callback_group.hpp"
24 #include "rclcpp/context.hpp"
25 #include "rclcpp/duration.hpp"
26 #include "rclcpp/executors/single_threaded_executor.hpp"
27 #include "rclcpp/logging.hpp"
28 #include "rclcpp/node.hpp"
29 #include "rclcpp/node_options.hpp"
30 #include "rclcpp/time.hpp"
31 #include "rclcpp/utilities.hpp"
32 #include "nav2_ros_common/interface_factories.hpp"
33 
34 using namespace std::chrono_literals;
35 using namespace std::placeholders;
36 
37 using lifecycle_msgs::msg::Transition;
38 using lifecycle_msgs::msg::State;
40 
41 namespace nav2_lifecycle_manager
42 {
43 
44 LifecycleManager::LifecycleManager(const rclcpp::NodeOptions & options)
45 : Node("lifecycle_manager", options), diagnostics_updater_(this)
46 {
47  RCLCPP_INFO(get_logger(), "Creating");
48 
49  // Node names are parameterized, allowing this module to be used with a different set of nodes
50  auto node = this;
51  node_names_ = nav2::declare_or_get_parameter<std::vector<std::string>>(node, "node_names");
52  autostart_ = nav2::declare_or_get_parameter(node, "autostart", false);
53  double bond_timeout_s = nav2::declare_or_get_parameter(node, "bond_timeout", 4.0);
54  double service_timeout_s = nav2::declare_or_get_parameter(node, "service_timeout", 5.0);
55  double respawn_timeout_s = nav2::declare_or_get_parameter(
56  node, "bond_respawn_max_duration", 10.0);
57  attempt_respawn_reconnection_ = nav2::declare_or_get_parameter(
58  node, "attempt_respawn_reconnection", true);
59  bond_heartbeat_period_ = nav2::declare_or_get_parameter(node, "bond_heartbeat_period", 0.25);
60 
62 
63  bond_timeout_ = std::chrono::duration_cast<std::chrono::milliseconds>(
64  std::chrono::duration<double>(bond_timeout_s));
65  service_timeout_ = std::chrono::duration_cast<std::chrono::milliseconds>(
66  std::chrono::duration<double>(service_timeout_s));
67  bond_respawn_max_duration_ = rclcpp::Duration::from_seconds(respawn_timeout_s);
68 
69  callback_group_ = create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive, false);
70 
71  transition_state_map_[Transition::TRANSITION_CONFIGURE] = State::PRIMARY_STATE_INACTIVE;
72  transition_state_map_[Transition::TRANSITION_CLEANUP] = State::PRIMARY_STATE_UNCONFIGURED;
73  transition_state_map_[Transition::TRANSITION_ACTIVATE] = State::PRIMARY_STATE_ACTIVE;
74  transition_state_map_[Transition::TRANSITION_DEACTIVATE] = State::PRIMARY_STATE_INACTIVE;
75  transition_state_map_[Transition::TRANSITION_UNCONFIGURED_SHUTDOWN] =
76  State::PRIMARY_STATE_FINALIZED;
77 
78  transition_label_map_[Transition::TRANSITION_CONFIGURE] = std::string("Configuring ");
79  transition_label_map_[Transition::TRANSITION_CLEANUP] = std::string("Cleaning up ");
80  transition_label_map_[Transition::TRANSITION_ACTIVATE] = std::string("Activating ");
81  transition_label_map_[Transition::TRANSITION_DEACTIVATE] = std::string("Deactivating ");
82  transition_label_map_[Transition::TRANSITION_UNCONFIGURED_SHUTDOWN] =
83  std::string("Shutting down ");
84 
85  init_timer_ = nav2::create_timer(
86  this,
87  0s,
88  [this]() -> void {
89  init_timer_->cancel();
93  if (autostart_) {
94  init_timer_ = nav2::create_timer(
95  this,
96  0s,
97  [this]() -> void {
98  init_timer_->cancel();
99  startup();
100  },
101  callback_group_);
102  }
103  auto executor = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
104  executor->add_callback_group(callback_group_, get_node_base_interface());
105  service_thread_ = std::make_unique<nav2::NodeThread>(executor);
106  });
107  diagnostics_updater_.setHardwareID("Nav2");
108  diagnostics_updater_.add("Nav2 Health", this, &LifecycleManager::CreateDiagnostic);
109 }
110 
112 {
113  RCLCPP_INFO(get_logger(), "Destroying %s", get_name());
114  service_thread_.reset();
115 }
116 
117 void
119  const std::shared_ptr<rmw_request_id_t>/*request_header*/,
120  const std::shared_ptr<ManageLifecycleNodes::Request> request,
121  std::shared_ptr<ManageLifecycleNodes::Response> response)
122 {
123  switch (request->command) {
124  case ManageLifecycleNodes::Request::STARTUP:
125  response->success = startup();
126  break;
127  case ManageLifecycleNodes::Request::CONFIGURE:
128  response->success = configure();
129  break;
130  case ManageLifecycleNodes::Request::CLEANUP:
131  response->success = cleanup();
132  break;
133  case ManageLifecycleNodes::Request::RESET:
134  response->success = reset();
135  break;
136  case ManageLifecycleNodes::Request::SHUTDOWN:
137  response->success = shutdown();
138  break;
139  case ManageLifecycleNodes::Request::PAUSE:
140  response->success = pause();
141  break;
142  case ManageLifecycleNodes::Request::RESUME:
143  response->success = resume();
144  break;
145  }
146 }
147 
148 void
149 LifecycleManager::setState(const NodeState & state)
150 {
151  managed_nodes_state_ = state;
153 }
154 
155 inline bool
157 {
158  return managed_nodes_state_ == NodeState::ACTIVE;
159 }
160 
161 void
163 {
164  if (is_active_pub_ && is_active_pub_->is_activated()) {
165  auto message = std::make_unique<std_msgs::msg::Bool>();
166  message->data = isActive();
167  is_active_pub_->publish(std::move(message));
168  }
169 }
170 
171 void
173  const std::shared_ptr<rmw_request_id_t>/*request_header*/,
174  const std::shared_ptr<std_srvs::srv::Trigger::Request>/*request*/,
175  std::shared_ptr<std_srvs::srv::Trigger::Response> response)
176 {
177  response->success = isActive();
178 }
179 
180 void
181 LifecycleManager::CreateDiagnostic(diagnostic_updater::DiagnosticStatusWrapper & stat)
182 {
183  unsigned char error_level;
184  std::string message;
185  switch (managed_nodes_state_) {
186  case NodeState::ACTIVE:
187  error_level = diagnostic_msgs::msg::DiagnosticStatus::OK;
188  message = "Managed nodes are active";
189  break;
190  case NodeState::INACTIVE:
191  error_level = diagnostic_msgs::msg::DiagnosticStatus::OK;
192  message = "Managed nodes are inactive";
193  break;
194  case NodeState::UNCONFIGURED:
195  error_level = diagnostic_msgs::msg::DiagnosticStatus::OK;
196  message = "Managed nodes are unconfigured";
197  break;
198  case NodeState::FINALIZED:
199  error_level = diagnostic_msgs::msg::DiagnosticStatus::WARN;
200  message = "Managed nodes have been shut down";
201  break;
202  default: // NodeState::UNKNOWN
203  error_level = diagnostic_msgs::msg::DiagnosticStatus::ERROR;
204  message = "An error has occurred during a node state transition";
205  break;
206  }
207  stat.summary(error_level, message);
208 }
209 
210 void
212 {
213  message("Creating and initializing lifecycle service clients");
214  for (auto & node_name : node_names_) {
215  node_map_[node_name] =
216  std::make_shared<LifecycleServiceClient>(node_name, shared_from_this());
217  }
218 }
219 
220 void
222 {
223  message("Creating and initializing lifecycle service servers");
224  // Since the LifecycleManager is a special node in that it manages other nodes,
225  // this is an rclcpp::Node, meaning we can't use the node->create_server API.
226  // to make a Nav2 ServiceServer. This must be constructed manually using the interfaces.
227  manager_srv_ = nav2::interfaces::create_service<ManageLifecycleNodes>(
228  shared_from_this(),
229  get_name() + std::string("/manage_nodes"),
230  std::bind(&LifecycleManager::managerCallback, this, _1, _2, _3),
231  callback_group_);
232 
233  is_active_srv_ = nav2::interfaces::create_service<std_srvs::srv::Trigger>(
234  shared_from_this(),
235  get_name() + std::string("/is_active"),
236  std::bind(&LifecycleManager::isActiveCallback, this, _1, _2, _3),
237  callback_group_);
238 }
239 
240 void
242 {
243  message("Creating and initializing lifecycle publishers");
244 
245  is_active_pub_ = nav2::interfaces::create_publisher<std_msgs::msg::Bool>(
246  shared_from_this(),
247  get_name() + std::string("/managed_nodes_activated"),
249  callback_group_);
250  is_active_pub_->on_activate();
251  // Publish the initial state once at startup
253 }
254 
255 void
257 {
258  message("Destroying lifecycle service clients");
259  for (auto & kv : node_map_) {
260  kv.second.reset();
261  }
262 }
263 
264 void
266 {
267  message("Destroying lifecycle publishers");
268  if (is_active_pub_) {
269  is_active_pub_->on_deactivate();
270  is_active_pub_.reset();
271  }
272 }
273 
274 bool
275 LifecycleManager::createBondConnection(const std::string & node_name)
276 {
277  const double timeout_ns =
278  std::chrono::duration_cast<std::chrono::nanoseconds>(bond_timeout_).count();
279  const double timeout_s = timeout_ns / 1e9;
280 
281  if (bond_map_.find(node_name) == bond_map_.end() && bond_timeout_.count() > 0.0) {
282  bond_map_[node_name] =
283  std::make_shared<bond::Bond>("bond", node_name, shared_from_this());
284  bond_map_[node_name]->setHeartbeatTimeout(timeout_s);
285  bond_map_[node_name]->setHeartbeatPeriod(bond_heartbeat_period_);
286  bond_map_[node_name]->start();
287  if (
288  !bond_map_[node_name]->waitUntilFormed(
289  rclcpp::Duration(rclcpp::Duration::from_nanoseconds(timeout_ns / 2))))
290  {
291  RCLCPP_ERROR(
292  get_logger(),
293  "Server %s was unable to be reached after %0.2fs by bond. "
294  "This server may be misconfigured.",
295  node_name.c_str(), timeout_s);
296  return false;
297  }
298  RCLCPP_INFO(get_logger(), "Server %s connected with bond.", node_name.c_str());
299  }
300 
301  return true;
302 }
303 
304 bool
305 LifecycleManager::changeStateForNode(const std::string & node_name, std::uint8_t transition)
306 {
307  message(transition_label_map_[transition] + node_name);
308 
309  if (!node_map_[node_name]->change_state(
310  transition, std::chrono::milliseconds(-1),
311  service_timeout_) ||
312  !(node_map_[node_name]->get_state(service_timeout_) == transition_state_map_[transition]))
313  {
314  RCLCPP_ERROR(get_logger(), "Failed to change state for node: %s", node_name.c_str());
315  return false;
316  }
317 
318  if (transition == Transition::TRANSITION_ACTIVATE) {
319  return createBondConnection(node_name);
320  } else if (transition == Transition::TRANSITION_DEACTIVATE) {
321  bond_map_.erase(node_name);
322  }
323 
324  return true;
325 }
326 
327 bool
328 LifecycleManager::changeStateForAllNodes(std::uint8_t transition, bool hard_change)
329 {
330  // Hard change will continue even if a node fails
331  if (transition == Transition::TRANSITION_CONFIGURE ||
332  transition == Transition::TRANSITION_ACTIVATE)
333  {
334  for (auto & node_name : node_names_) {
335  try {
336  if (!changeStateForNode(node_name, transition) && !hard_change) {
337  return false;
338  }
339  } catch (const std::runtime_error & e) {
340  RCLCPP_ERROR(
341  get_logger(),
342  "Failed to change state for node: %s. Exception: %s.", node_name.c_str(), e.what());
343  return false;
344  }
345  }
346  } else {
347  std::vector<std::string>::reverse_iterator rit;
348  for (rit = node_names_.rbegin(); rit != node_names_.rend(); ++rit) {
349  try {
350  if (!changeStateForNode(*rit, transition) && !hard_change) {
351  return false;
352  }
353  } catch (const std::runtime_error & e) {
354  RCLCPP_ERROR(
355  get_logger(),
356  "Failed to change state for node: %s. Exception: %s.", (*rit).c_str(), e.what());
357  return false;
358  }
359  }
360  }
361  return true;
362 }
363 
364 void
366 {
367  message("Deactivate, cleanup, and shutdown nodes");
368  setState(NodeState::FINALIZED);
369  changeStateForAllNodes(Transition::TRANSITION_DEACTIVATE);
370  changeStateForAllNodes(Transition::TRANSITION_CLEANUP);
371  changeStateForAllNodes(Transition::TRANSITION_UNCONFIGURED_SHUTDOWN);
372 }
373 
374 bool
376 {
377  message("Starting managed nodes bringup...");
378  if (!changeStateForAllNodes(Transition::TRANSITION_CONFIGURE) ||
379  !changeStateForAllNodes(Transition::TRANSITION_ACTIVATE))
380  {
381  RCLCPP_ERROR(get_logger(), "Failed to bring up all requested nodes. Aborting bringup.");
382  setState(NodeState::UNKNOWN);
383  return false;
384  }
385  message("Managed nodes are active");
386  setState(NodeState::ACTIVE);
387  createBondTimer();
388  return true;
389 }
390 
391 bool
393 {
394  message("Configuring managed nodes...");
395  if (!changeStateForAllNodes(Transition::TRANSITION_CONFIGURE)) {
396  RCLCPP_ERROR(get_logger(), "Failed to configure all requested nodes. Aborting bringup.");
397  setState(NodeState::UNKNOWN);
398  return false;
399  }
400  message("Managed nodes are now configured");
401  setState(NodeState::INACTIVE);
402  return true;
403 }
404 
405 bool
407 {
408  message("Cleaning up managed nodes...");
409  if (!changeStateForAllNodes(Transition::TRANSITION_CLEANUP)) {
410  RCLCPP_ERROR(get_logger(), "Failed to cleanup all requested nodes. Aborting cleanup.");
411  setState(NodeState::UNKNOWN);
412  return false;
413  }
414  message("Managed nodes have been cleaned up");
415  setState(NodeState::UNCONFIGURED);
416  return true;
417 }
418 
419 bool
421 {
423 
424  message("Shutting down managed nodes...");
428  message("Managed nodes have been shut down");
429  return true;
430 }
431 
432 bool
433 LifecycleManager::reset(bool hard_reset)
434 {
436 
437  message("Resetting managed nodes...");
438  // Should transition in reverse order
439  if (!changeStateForAllNodes(Transition::TRANSITION_DEACTIVATE, hard_reset) ||
440  !changeStateForAllNodes(Transition::TRANSITION_CLEANUP, hard_reset))
441  {
442  if (!hard_reset) {
443  RCLCPP_ERROR(get_logger(), "Failed to reset nodes: aborting reset");
444  setState(NodeState::UNKNOWN);
445  return false;
446  }
447  }
448 
449  message("Managed nodes have been reset");
450  setState(NodeState::UNCONFIGURED);
451  return true;
452 }
453 
454 bool
456 {
458 
459  message("Pausing managed nodes...");
460  if (!changeStateForAllNodes(Transition::TRANSITION_DEACTIVATE)) {
461  RCLCPP_ERROR(get_logger(), "Failed to pause nodes: aborting pause");
462  setState(NodeState::UNKNOWN);
463  return false;
464  }
465 
466  message("Managed nodes have been paused");
467  setState(NodeState::INACTIVE);
468  return true;
469 }
470 
471 bool
473 {
474  message("Resuming managed nodes...");
475  if (!changeStateForAllNodes(Transition::TRANSITION_ACTIVATE)) {
476  RCLCPP_ERROR(get_logger(), "Failed to resume nodes: aborting resume");
477  setState(NodeState::UNKNOWN);
478  return false;
479  }
480 
481  message("Managed nodes are active");
482  setState(NodeState::ACTIVE);
483  createBondTimer();
484  return true;
485 }
486 
487 void
489 {
490  if (bond_timeout_.count() <= 0) {
491  return;
492  }
493 
494  message("Creating bond timer...");
495  bond_timer_ = nav2::create_timer(
496  this,
497  200ms,
498  std::bind(&LifecycleManager::checkBondConnections, this),
499  callback_group_);
500 }
501 
502 void
504 {
505  if (bond_timer_) {
506  message("Terminating bond timer...");
507  bond_timer_->cancel();
508  bond_timer_.reset();
509  }
510 }
511 
512 void
514 {
515  RCLCPP_INFO(
516  get_logger(), "Running Nav2 LifecycleManager rcl preshutdown (%s)",
517  this->get_name());
518 
520 
521  /*
522  * Dropping the bond map is what we really need here, but we drop the others
523  * to prevent the bond map being used. Likewise, squash the service thread.
524  */
525  service_thread_.reset();
526  node_names_.clear();
527  node_map_.clear();
528  bond_map_.clear();
529 }
530 
531 void
533 {
534  rclcpp::Context::SharedPtr context = get_node_base_interface()->get_context();
535 
536  context->add_pre_shutdown_callback(
537  std::bind(&LifecycleManager::onRclPreshutdown, this)
538  );
539 }
540 
541 void
543 {
544  if (!isActive() || !rclcpp::ok() || bond_map_.empty()) {
545  return;
546  }
547 
548  for (auto & node_name : node_names_) {
549  if (!rclcpp::ok()) {
550  return;
551  }
552 
553  if (bond_map_[node_name]->isBroken()) {
554  message(
555  std::string(
556  "Have not received a heartbeat from " + node_name + "."));
557 
558  // if one is down, bring them all down
559  RCLCPP_ERROR(
560  get_logger(),
561  "CRITICAL FAILURE: SERVER %s IS DOWN after not receiving a heartbeat for %i ms."
562  " Shutting down related nodes.",
563  node_name.c_str(), static_cast<int>(bond_timeout_.count()));
564  reset(true); // hard reset to transition all still active down
565  // if a server crashed, it won't get cleared due to failed transition, clear manually
566  bond_map_.clear();
567 
568  // Initialize the bond respawn timer to check if server comes back online
569  // after a failure, within a maximum timeout period.
570  if (attempt_respawn_reconnection_) {
571  bond_respawn_timer_ = nav2::create_timer(
572  this,
573  1s,
575  callback_group_);
576  }
577  return;
578  }
579  }
580 }
581 
582 void
584 {
585  // First attempt in respawn, start maximum duration to respawn
586  if (bond_respawn_start_time_.nanoseconds() == 0) {
587  bond_respawn_start_time_ = now();
588  }
589 
590  // Note: isActive() is inverted since this should be in a failure
591  // condition. If another outside user actives the system again, this should not process.
592  if (isActive() || !rclcpp::ok() || node_names_.empty()) {
593  bond_respawn_start_time_ = rclcpp::Time(0);
594  bond_respawn_timer_.reset();
595  return;
596  }
597 
598  // Check number of live connections after a bond failure
599  int live_servers = 0;
600  const int max_live_servers = node_names_.size();
601  for (auto & node_name : node_names_) {
602  if (!rclcpp::ok()) {
603  return;
604  }
605 
606  try {
607  node_map_[node_name]->get_state(service_timeout_); // Only won't throw if the server exists
608  live_servers++;
609  } catch (...) {
610  break;
611  }
612  }
613 
614  // If all are alive, kill timer and retransition system to active
615  // Else, check if maximum timeout has occurred
616  if (live_servers == max_live_servers) {
617  message("Successfully re-established connections from server respawns, starting back up.");
618  bond_respawn_start_time_ = rclcpp::Time(0);
619  bond_respawn_timer_.reset();
620  startup();
621  } else if (now() - bond_respawn_start_time_ >= bond_respawn_max_duration_) {
622  message("Failed to re-establish connection from a server crash after maximum timeout.");
623  bond_respawn_start_time_ = rclcpp::Time(0);
624  bond_respawn_timer_.reset();
625  }
626 }
627 
628 #define ANSI_COLOR_RESET "\x1b[0m"
629 #define ANSI_COLOR_BLUE "\x1b[34m"
630 
631 void
632 LifecycleManager::message(const std::string & msg)
633 {
634  RCLCPP_INFO(get_logger(), ANSI_COLOR_BLUE "\33[1m%s\33[0m" ANSI_COLOR_RESET, msg.c_str());
635 }
636 
637 } // namespace nav2_lifecycle_manager
638 
639 #include "rclcpp_components/register_node_macro.hpp"
640 RCLCPP_COMPONENTS_REGISTER_NODE(nav2_lifecycle_manager::LifecycleManager)
A QoS profile for latched, reliable topics with a history of 1 messages.
Implements service interface to transition the lifecycle nodes of Nav2 stack. It receives transition ...
void createBondTimer()
Support function for creating bond timer.
void destroyLifecycleServiceClients()
Destroy all the lifecycle service clients.
void destroyBondTimer()
Support function for killing bond connections.
bool cleanup()
Cleanups the managed nodes.
bool pause()
Pause all the managed nodes.
bool shutdown()
Deactivate, clean up and shut down all the managed nodes.
bool resume()
Resume all the managed nodes.
void createLifecycleServiceServers()
Support function for creating service servers.
void onRclPreshutdown()
Perform preshutdown activities before our Context is shutdown. Note that this is related to our Conte...
void destroyLifecyclePublishers()
Destroy all the lifecycle publishers.
bool isActive()
function to check if managed nodes are active
bool createBondConnection(const std::string &node_name)
Support function for creating bond connections.
void createLifecyclePublishers()
Support function for creating publishers.
void CreateDiagnostic(diagnostic_updater::DiagnosticStatusWrapper &stat)
function to check the state of Nav2 nodes
void publishIsActiveState()
Publish the is_active state.
void message(const std::string &msg)
Helper function to highlight the output on the console.
void setState(const NodeState &state)
Set the state of managed nodes.
~LifecycleManager()
A destructor for nav2_lifecycle_manager::LifecycleManager.
bool reset(bool hard_reset=false)
Reset all the managed nodes.
void shutdownAllNodes()
Support function for shutdown.
void managerCallback(const std::shared_ptr< rmw_request_id_t > request_header, const std::shared_ptr< ManageLifecycleNodes::Request > request, std::shared_ptr< ManageLifecycleNodes::Response > response)
Lifecycle node manager callback function.
bool changeStateForNode(const std::string &node_name, std::uint8_t transition)
For a node, transition to the new target state.
bool configure()
Configures the managed nodes.
bool changeStateForAllNodes(std::uint8_t transition, bool hard_change=false)
For each node in the map, transition to the new target state.
void createLifecycleServiceClients()
Support function for creating service clients.
void isActiveCallback(const std::shared_ptr< rmw_request_id_t > request_header, const std::shared_ptr< std_srvs::srv::Trigger::Request > request, std::shared_ptr< std_srvs::srv::Trigger::Response > response)
Trigger callback function checks if the managed nodes are in active state.
Helper functions to interact with a lifecycle node.