ROS 2 rclcpp + rcl - jazzy  jazzy
ROS 2 C++ Client Library with ROS Client Library
graph_listener.cpp
1 // Copyright 2016 Open Source Robotics Foundation, Inc.
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 #include "rclcpp/graph_listener.hpp"
16 
17 #include <cstdio>
18 #include <exception>
19 #include <memory>
20 #include <string>
21 #include <vector>
22 
23 #include "rcl/error_handling.h"
24 #include "rcl/types.h"
25 #include "rclcpp/detail/add_guard_condition_to_rcl_wait_set.hpp"
26 #include "rclcpp/exceptions.hpp"
27 #include "rclcpp/logging.hpp"
28 #include "rclcpp/node.hpp"
29 #include "rmw/impl/cpp/demangle.hpp"
30 
31 #include "rcutils/logging_macros.h"
32 
33 using rclcpp::exceptions::throw_from_rcl_error;
34 
35 namespace rclcpp
36 {
37 namespace graph_listener
38 {
39 
40 GraphListener::GraphListener(const std::shared_ptr<Context> & parent_context)
41 : weak_parent_context_(parent_context),
42  rcl_parent_context_(parent_context->get_rcl_context()),
43  is_started_(false),
44  is_shutdown_(false),
45  interrupt_guard_condition_(parent_context)
46 {
47 }
48 
49 GraphListener::~GraphListener()
50 {
51  GraphListener::shutdown(std::nothrow);
52 }
53 
54 void GraphListener::init_wait_set()
55 {
57  &wait_set_,
58  0, // number_of_subscriptions
59  2, // number_of_guard_conditions
60  0, // number_of_timers
61  0, // number_of_clients
62  0, // number_of_services
63  0, // number_of_events
64  rcl_parent_context_.get(),
66  if (RCL_RET_OK != ret) {
67  throw_from_rcl_error(ret, "failed to initialize wait set");
68  }
69 }
70 
71 void
72 GraphListener::start_if_not_started()
73 {
74  auto parent_context = weak_parent_context_.lock();
75  {
76  std::lock_guard<std::mutex> shutdown_lock(shutdown_mutex_);
77  if (is_shutdown_.load()) {
79  }
80  if (is_started_ || !parent_context) {
81  return;
82  }
83  }
84  // Register an on_shutdown hook to shutdown the graph listener.
85  // This is important to ensure that the wait set is finalized before
86  // destruction of static objects occurs.
87  // The hook is deliberately registered without holding shutdown_mutex_:
88  // to prevent inverting the lock order with Context::shutdown()
89  std::weak_ptr<GraphListener> weak_this = shared_from_this();
90  auto callback_handle = parent_context->add_on_shutdown_callback(
91  [weak_this]() {
92  auto shared_this = weak_this.lock();
93  if (shared_this) {
94  // should not throw from on_shutdown if it can be avoided
95  shared_this->shutdown(std::nothrow);
96  }
97  });
98 
99  bool started_on_this_thread = false;
100  {
101  std::lock_guard<std::mutex> shutdown_lock(shutdown_mutex_);
102  if (!is_shutdown_.load() && !is_started_) {
103  // Initialize the wait set before starting.
104  init_wait_set();
105  // Start the listener thread.
106  listener_thread_ = std::thread(&GraphListener::run, this);
107  is_started_ = true;
108  started_on_this_thread = true;
109  }
110  }
111  if (!started_on_this_thread) {
112  // Another thread has started the listener, or the listener was
113  // shut down between registration and the lock. Drop this registration.
114  parent_context->remove_on_shutdown_callback(callback_handle);
115  if (is_shutdown_.load()) {
117  }
118  }
119 }
120 
121 void
122 GraphListener::run()
123 {
124  try {
125  run_loop();
126  } catch (const std::exception & exc) {
127  RCUTILS_LOG_ERROR_NAMED(
128  "rclcpp",
129  "caught %s exception in GraphListener thread: %s",
130  rmw::impl::cpp::demangle(exc).c_str(),
131  exc.what());
132  std::rethrow_exception(std::current_exception());
133  } catch (...) {
134  RCUTILS_LOG_ERROR_NAMED(
135  "rclcpp",
136  "unknown error in GraphListener thread");
137  std::rethrow_exception(std::current_exception());
138  }
139 }
140 
141 void
142 GraphListener::run_loop()
143 {
144  while (true) {
145  // If shutdown() was called, exit.
146  if (is_shutdown_.load()) {
147  return;
148  }
149  rcl_ret_t ret;
150  {
151  // This "barrier" lock ensures that other functions can acquire the
152  // node_graph_interfaces_mutex_ after waking up rcl_wait.
153  std::lock_guard<std::mutex> nodes_barrier_lock(node_graph_interfaces_barrier_mutex_);
154  // This is ownership is passed to nodes_lock in the next line.
155  node_graph_interfaces_mutex_.lock();
156  }
157  // This lock is released when the loop continues or exits.
158  std::lock_guard<std::mutex> nodes_lock(node_graph_interfaces_mutex_, std::adopt_lock);
159  // Resize the wait set if necessary.
160  const size_t node_graph_interfaces_size = node_graph_interfaces_.size();
161  // Add 2 for the interrupt and shutdown guard conditions
162  if (wait_set_.size_of_guard_conditions < (node_graph_interfaces_size + 2)) {
163  ret = rcl_wait_set_resize(&wait_set_, 0, node_graph_interfaces_size + 2, 0, 0, 0, 0);
164  if (RCL_RET_OK != ret) {
165  throw_from_rcl_error(ret, "failed to resize wait set");
166  }
167  }
168  // Clear the wait set.
169  ret = rcl_wait_set_clear(&wait_set_);
170  if (RCL_RET_OK != ret) {
171  throw_from_rcl_error(ret, "failed to clear wait set");
172  }
173  // Put the interrupt guard condition in the wait set.
174  detail::add_guard_condition_to_rcl_wait_set(wait_set_, interrupt_guard_condition_);
175 
176  // Put graph guard conditions for each node into the wait set.
177  std::vector<size_t> graph_gc_indexes(node_graph_interfaces_size, 0u);
178  for (size_t i = 0u; i < node_graph_interfaces_size; ++i) {
179  auto node_ptr = node_graph_interfaces_[i];
180  // Only wait on graph changes if some user of the node is watching.
181  if (node_ptr->count_graph_users() == 0) {
182  continue;
183  }
184  // Add the graph guard condition for the node to the wait set.
185  auto graph_gc = node_ptr->get_graph_guard_condition();
186  if (!graph_gc) {
187  throw_from_rcl_error(RCL_RET_ERROR, "failed to get graph guard condition");
188  }
189  ret = rcl_wait_set_add_guard_condition(&wait_set_, graph_gc, &graph_gc_indexes[i]);
190  if (RCL_RET_OK != ret) {
191  throw_from_rcl_error(ret, "failed to add graph guard condition to wait set");
192  }
193  }
194 
195  // Wait for: graph changes, interrupt, or shutdown/SIGINT
196  ret = rcl_wait(&wait_set_, -1); // block for ever until a guard condition is triggered
197  if (RCL_RET_TIMEOUT == ret) {
198  throw std::runtime_error("rcl_wait unexpectedly timed out");
199  }
200  if (RCL_RET_OK != ret) {
201  throw_from_rcl_error(ret, "failed to wait on wait set");
202  }
203 
204  // Notify nodes who's guard conditions are set (triggered).
205  for (size_t i = 0u; i < node_graph_interfaces_size; ++i) {
206  const auto node_ptr = node_graph_interfaces_[i];
207  auto graph_gc = node_ptr->get_graph_guard_condition();
208  if (!graph_gc) {
209  throw_from_rcl_error(RCL_RET_ERROR, "failed to get graph guard condition");
210  }
211  if (graph_gc == wait_set_.guard_conditions[graph_gc_indexes[i]]) {
212  node_ptr->notify_graph_change();
213  }
214  if (is_shutdown_) {
215  // If shutdown, then notify the node of this as well.
216  node_ptr->notify_shutdown();
217  }
218  }
219  } // while (true)
220 }
221 
222 static void
223 interrupt_(GuardCondition * interrupt_guard_condition)
224 {
225  interrupt_guard_condition->trigger();
226 }
227 
228 static void
229 acquire_nodes_lock_(
230  std::mutex * node_graph_interfaces_barrier_mutex,
231  std::mutex * node_graph_interfaces_mutex,
232  GuardCondition * interrupt_guard_condition)
233 {
234  {
235  // Acquire this lock to prevent the run loop from re-locking the
236  // nodes_mutext_ after being woken up.
237  std::lock_guard<std::mutex> nodes_barrier_lock(*node_graph_interfaces_barrier_mutex);
238  // Trigger the interrupt guard condition to wake up rcl_wait.
239  interrupt_(interrupt_guard_condition);
240  node_graph_interfaces_mutex->lock();
241  }
242 }
243 
244 static bool
245 has_node_(
246  std::vector<rclcpp::node_interfaces::NodeGraphInterface *> * node_graph_interfaces,
248 {
249  for (const auto node_ptr : (*node_graph_interfaces)) {
250  if (node_graph == node_ptr) {
251  return true;
252  }
253  }
254  return false;
255 }
256 
257 bool
258 GraphListener::has_node(rclcpp::node_interfaces::NodeGraphInterface * node_graph)
259 {
260  if (!node_graph) {
261  return false;
262  }
263  // Acquire the nodes mutex using the barrier to prevent the run loop from
264  // re-locking the nodes mutex after being interrupted.
265  acquire_nodes_lock_(
266  &node_graph_interfaces_barrier_mutex_,
267  &node_graph_interfaces_mutex_,
268  &interrupt_guard_condition_);
269  // Store the now acquired node_graph_interfaces_mutex_ in the scoped lock using adopt_lock.
270  std::lock_guard<std::mutex> nodes_lock(node_graph_interfaces_mutex_, std::adopt_lock);
271  return has_node_(&node_graph_interfaces_, node_graph);
272 }
273 
274 void
275 GraphListener::add_node(rclcpp::node_interfaces::NodeGraphInterface * node_graph)
276 {
277  if (!node_graph) {
278  throw std::invalid_argument("node is nullptr");
279  }
280  std::lock_guard<std::mutex> shutdown_lock(shutdown_mutex_);
281  if (is_shutdown_.load()) {
283  }
284 
285  // Acquire the nodes mutex using the barrier to prevent the run loop from
286  // re-locking the nodes mutex after being interrupted.
287  acquire_nodes_lock_(
288  &node_graph_interfaces_barrier_mutex_,
289  &node_graph_interfaces_mutex_,
290  &interrupt_guard_condition_);
291  // Store the now acquired node_graph_interfaces_mutex_ in the scoped lock using adopt_lock.
292  std::lock_guard<std::mutex> nodes_lock(node_graph_interfaces_mutex_, std::adopt_lock);
293  if (has_node_(&node_graph_interfaces_, node_graph)) {
294  throw NodeAlreadyAddedError();
295  }
296  node_graph_interfaces_.push_back(node_graph);
297  // The run loop has already been interrupted by acquire_nodes_lock_() and
298  // will evaluate the new node when nodes_lock releases the node_graph_interfaces_mutex_.
299 }
300 
301 static void
302 remove_node_(
303  std::vector<rclcpp::node_interfaces::NodeGraphInterface *> * node_graph_interfaces,
305 {
306  // Remove the node if it is found.
307  for (auto it = node_graph_interfaces->begin(); it != node_graph_interfaces->end(); ++it) {
308  if (node_graph == *it) {
309  // Found the node, remove it.
310  node_graph_interfaces->erase(it);
311  // Now trigger the interrupt guard condition to make sure
312  return;
313  }
314  }
315  // Not found in the loop.
316  throw NodeNotFoundError();
317 }
318 
319 void
320 GraphListener::remove_node(rclcpp::node_interfaces::NodeGraphInterface * node_graph)
321 {
322  if (!node_graph) {
323  throw std::invalid_argument("node is nullptr");
324  }
325  std::lock_guard<std::mutex> shutdown_lock(shutdown_mutex_);
326  if (is_shutdown()) {
327  // If shutdown, then the run loop has been joined, so we can remove them directly.
328  return remove_node_(&node_graph_interfaces_, node_graph);
329  }
330  // Otherwise, first interrupt and lock against the run loop to safely remove the node.
331  // Acquire the nodes mutex using the barrier to prevent the run loop from
332  // re-locking the nodes mutex after being interrupted.
333  acquire_nodes_lock_(
334  &node_graph_interfaces_barrier_mutex_,
335  &node_graph_interfaces_mutex_,
336  &interrupt_guard_condition_);
337  // Store the now acquired node_graph_interfaces_mutex_ in the scoped lock using adopt_lock.
338  std::lock_guard<std::mutex> nodes_lock(node_graph_interfaces_mutex_, std::adopt_lock);
339  remove_node_(&node_graph_interfaces_, node_graph);
340 }
341 
342 void
343 GraphListener::cleanup_wait_set()
344 {
345  rcl_ret_t ret = rcl_wait_set_fini(&wait_set_);
346  if (RCL_RET_OK != ret) {
347  throw_from_rcl_error(ret, "failed to finalize wait set");
348  }
349 }
350 
351 void
352 GraphListener::__shutdown()
353 {
354  std::lock_guard<std::mutex> shutdown_lock(shutdown_mutex_);
355  if (!is_shutdown_.exchange(true)) {
356  if (is_started_) {
357  interrupt_(&interrupt_guard_condition_);
358  listener_thread_.join();
359  }
360  if (is_started_) {
361  cleanup_wait_set();
362  }
363  }
364 }
365 
366 void
367 GraphListener::shutdown()
368 {
369  this->__shutdown();
370 }
371 
372 void
373 GraphListener::shutdown(const std::nothrow_t &) noexcept
374 {
375  try {
376  this->__shutdown();
377  } catch (const std::exception & exc) {
378  RCLCPP_ERROR(
379  rclcpp::get_logger("rclcpp"),
380  "caught %s exception when shutting down GraphListener: %s",
381  rmw::impl::cpp::demangle(exc).c_str(), exc.what());
382  } catch (...) {
383  RCLCPP_ERROR(
384  rclcpp::get_logger("rclcpp"),
385  "caught unknown exception when shutting down GraphListener");
386  }
387 }
388 
389 bool
390 GraphListener::is_shutdown()
391 {
392  return is_shutdown_.load();
393 }
394 
395 } // namespace graph_listener
396 } // namespace rclcpp
#define rcl_get_default_allocator
Return a properly initialized rcl_allocator_t with default values.
Definition: allocator.h:37
Thrown when a function is called on a GraphListener that is already shutdown.
Thrown when a node has already been added to the GraphListener.
Pure virtual interface class for the NodeGraph part of the Node API.
Versions of rosidl_typesupport_cpp::get_message_type_support_handle that handle adapted types.
RCLCPP_PUBLIC Logger get_logger(const std::string &name)
Return a named logger.
Definition: logger.cpp:33
#define RCL_RET_OK
Success return code.
Definition: types.h:27
#define RCL_RET_ERROR
Unspecified error return code.
Definition: types.h:29
#define RCL_RET_TIMEOUT
Timeout occurred return code.
Definition: types.h:31
rmw_ret_t rcl_ret_t
The type that holds an rcl return code.
Definition: types.h:24
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_wait_set_init(rcl_wait_set_t *wait_set, size_t number_of_subscriptions, size_t number_of_guard_conditions, size_t number_of_timers, size_t number_of_clients, size_t number_of_services, size_t number_of_events, rcl_context_t *context, rcl_allocator_t allocator)
Initialize a rcl wait set with space for items to be waited on.
Definition: wait.c:103
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_wait_set_clear(rcl_wait_set_t *wait_set)
Remove (sets to NULL) all entities in the wait set.
Definition: wait.c:334
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_wait_set_fini(rcl_wait_set_t *wait_set)
Finalize a rcl wait set.
Definition: wait.c:189
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_wait(rcl_wait_set_t *wait_set, int64_t timeout)
Block until the wait set is ready or until the timeout has been exceeded.
Definition: wait.c:522
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_wait_set_resize(rcl_wait_set_t *wait_set, size_t subscriptions_size, size_t guard_conditions_size, size_t timers_size, size_t clients_size, size_t services_size, size_t events_size)
Reallocate space for entities in the wait set.
Definition: wait.c:376
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_wait_set_add_guard_condition(rcl_wait_set_t *wait_set, const rcl_guard_condition_t *guard_condition, size_t *index)
Store a pointer to the guard condition in the next empty spot in the set.
Definition: wait.c:454