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