ROS 2 rclcpp + rcl - rolling  rolling-20536064
ROS 2 C++ Client Library with ROS Client Library
events_cbg_executor.cpp
1 // Copyright 2024 Cellumation GmbH
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 <chrono>
16 #include <functional>
17 #include <memory>
18 #include <set>
19 #include <vector>
20 
21 #include "rcpputils/scope_exit.hpp"
22 #include "rclcpp/exceptions/exceptions.hpp"
23 #include "rclcpp/node.hpp"
24 
25 #include "first_in_first_out_scheduler.hpp"
26 #include "timer_manager.hpp"
27 #include "registered_entity_cache.hpp"
28 #include "rclcpp/executors/events_cbg_executor/events_cbg_executor.hpp"
29 
30 
31 namespace rclcpp::executors
32 {
33 namespace cbg_executor
34 {
36 {
37  std::vector<cbg_executor::GuardConditionWithFunction> guard_conditions;
38 
39  GlobalWeakExecutableCache() = default;
43  {
44  for (const auto & gc_ref : guard_conditions) {
45  gc_ref.guard_condition->set_on_trigger_callback(nullptr);
46  }
47  }
48 
49  GlobalWeakExecutableCache & operator=(const GlobalWeakExecutableCache &) = default;
50  GlobalWeakExecutableCache & operator=(GlobalWeakExecutableCache &&) = default;
51 
52  void add_guard_condition_event(
53  const rclcpp::GuardCondition::SharedPtr & ptr,
54  std::function<void(void)> fun)
55  {
56  guard_conditions.emplace_back(ptr, std::move(fun));
57 
58  // reset all lambdas in case the guard_conditions vector
59  // was resized and the entry ptrs were moved
60  for (auto & entry : guard_conditions) {
61  entry.guard_condition->set_on_trigger_callback(
62  [ptr = &entry](size_t nr_events) {
63  for (size_t i = 0; i < nr_events; i++) {
64  if (ptr->handle_guard_condition_fun) {
65  ptr->handle_guard_condition_fun();
66  }
67  }
68  });
69  }
70  }
71 
72  void clear()
73  {
74  guard_conditions.clear();
75  }
76 };
77 } // namespace cbg_executor
78 
80  const rclcpp::ExecutorOptions & options,
81  size_t number_of_threads,
82  std::chrono::nanoseconds next_exec_timeout)
83 : scheduler(std::make_unique<cbg_executor::FirstInFirstOutScheduler>([this] () {
84  needs_callback_group_resync = true;
85  })),
86  next_exec_timeout_(next_exec_timeout),
87  spinning(false),
88  interrupt_guard_condition_(std::make_shared<rclcpp::GuardCondition>(options.context) ),
89  shutdown_guard_condition_(std::make_shared<rclcpp::GuardCondition>(options.context) ),
90  context_(options.context),
91  timer_manager(std::make_unique<cbg_executor::TimerManager>(context_)),
92  global_executable_cache(std::make_unique<cbg_executor::GlobalWeakExecutableCache>() ),
93  nodes_executable_cache(std::make_unique<cbg_executor::GlobalWeakExecutableCache>() )
94 {
95  global_executable_cache->add_guard_condition_event (
96  interrupt_guard_condition_,
97  std::function<void(void)>() );
98 
99  global_executable_cache->add_guard_condition_event(
100  shutdown_guard_condition_, [this]() {
101  shutdown();
102  });
103 
104  number_of_threads_ = number_of_threads > 0 ?
105  number_of_threads :
106  std::max(std::thread::hardware_concurrency(), 2U);
107 
108  shutdown_callback_handle_ = context_->add_on_shutdown_callback(
109  [weak_gc = std::weak_ptr<rclcpp::GuardCondition> {shutdown_guard_condition_}]() {
110  auto strong_gc = weak_gc.lock();
111  if (strong_gc) {
112  strong_gc->trigger();
113  }
114  });
115 }
116 
117 EventsCBGExecutor::~EventsCBGExecutor()
118 {
119  shutdown();
120 }
121 
123 {
124  if(!timer_manager) {
125  // already shut down
126  return;
127  }
128 
129  // we need to shut down the timer manager first, as it might access the Schedulers
130  timer_manager->stop();
131 
132  in_shutdown = true;
133  bool was_spinning = spinning;
134 
135  // signal all processing threads to shut down
136  cancel_requested_ = true;
137 
138  if(was_spinning) {
139  scheduler->release_all_worker_threads();
140  }
141 
142  remove_all_nodes_and_callback_groups();
143 
144  {
145  std::scoped_lock l(callback_groups_mutex);
146  callback_groups.clear();
147  }
148 
149  // Remove shutdown callback handle registered to Context
150  if (!context_->remove_on_shutdown_callback(shutdown_callback_handle_) ) {
151  RCUTILS_LOG_ERROR_NAMED(
152  "rclcpp",
153  "failed to remove registered on_shutdown callback");
154  rcl_reset_error();
155  }
156 
157  // now we may release the memory of the timer_manager,
158  // as we know no thread is working on it any more
159  timer_manager.reset();
160 }
161 
162 void EventsCBGExecutor::remove_all_nodes_and_callback_groups()
163 {
164  std::vector<node_interfaces::NodeBaseInterface::WeakPtr> added_nodes_cpy;
165  {
166  std::lock_guard lock{added_nodes_mutex_};
167  added_nodes_cpy = added_nodes;
168  }
169 
170  for (const node_interfaces::NodeBaseInterface::WeakPtr & node_weak_ptr : added_nodes_cpy) {
171  const node_interfaces::NodeBaseInterface::SharedPtr & node_ptr = node_weak_ptr.lock();
172  if (node_ptr) {
173  remove_node(node_ptr, false);
174  }
175  }
176 
177  std::vector<rclcpp::CallbackGroup::WeakPtr> added_cbgs_cpy;
178  {
179  std::lock_guard lock{added_callback_groups_mutex_};
180  added_cbgs_cpy = added_callback_groups;
181  }
182 
183  for (const auto & weak_ptr : added_cbgs_cpy) {
184  auto shr_ptr = weak_ptr.lock();
185  if (shr_ptr) {
186  remove_callback_group(shr_ptr, false);
187  }
188  }
189 }
190 
192  const std::chrono::time_point<std::chrono::steady_clock> & stop_time)
193 {
194  bool found_work = false;
195 
196  const uint64_t last_ready_id = cbg_executor::GlobalEventIdProvider::get_last_id();
197 
198  while(true) {
199  auto ready_entity = scheduler->get_next_ready_entity(last_ready_id);
200  if(!ready_entity.entity) {
201  break;
202  }
203 
204  found_work = true;
205 
206  ready_entity.entity->execute_function();
207 
208  scheduler->mark_entity_as_executed(*ready_entity.entity);
209 
210  if(std::chrono::steady_clock::now() >= stop_time) {
211  break;
212  }
213  }
214 
215  return found_work;
216 }
217 
218 
219 size_t
220 EventsCBGExecutor::get_number_of_threads() const
221 {
222  return number_of_threads_;
223 }
224 
225 void EventsCBGExecutor::trigger_callback_group_sync()
226 {
227  if(in_shutdown) {
228  return;
229  }
230 
231  needs_callback_group_resync = true;
232 
233  if (!spinning) {
234  sync_callback_groups();
235  } else {
236  scheduler->unblock_one_worker_thread();
237  }
238 }
239 
240 void EventsCBGExecutor::sync_callback_groups()
241 {
242  if (!needs_callback_group_resync.exchange(false) ) {
243  return;
244  }
245 
246  std::scoped_lock l(callback_groups_mutex);
247 
248 
249  std::vector<std::pair<CallbackGroupData *, rclcpp::CallbackGroup::SharedPtr>> cur_group_data;
250  cur_group_data.reserve(callback_groups.size() );
251 
252  for (CallbackGroupData & d : callback_groups) {
253  auto p = d.callback_group.lock();
254  if (p) {
255  cur_group_data.emplace_back(&d, std::move(p) );
256  }
257  }
258 
259  std::vector<CallbackGroupData> next_group_data;
260 
261  std::set<CallbackGroup *> added_cbgs;
262 
263  auto insert_data =
264  [&cur_group_data, &next_group_data, &added_cbgs,
265  this](rclcpp::CallbackGroup::SharedPtr && cbg, CallbackGroupData::Origin origin) {
266  // nodes may share callback groups, therefore we need to make sure we only add them once
267  if (added_cbgs.find(cbg.get() ) != added_cbgs.end() ) {
268  return;
269  }
270 
271  added_cbgs.insert(cbg.get() );
272 
273  for (const auto & pair : cur_group_data) {
274  if (pair.second == cbg) {
275  next_group_data.push_back(std::move(*pair.first) );
276  // call regenerate, in case something changed in the group
277  next_group_data.back().registered_entities->regenerate_events();
278  return;
279  }
280  }
281 
282  CallbackGroupData new_entry{.callback_group = cbg,
283  .registered_entities = std::make_unique<cbg_executor::RegisteredEntityCache>(*scheduler,
284  *timer_manager, cbg), .origin = origin};
285  new_entry.registered_entities->regenerate_events();
286  next_group_data.push_back(std::move(new_entry) );
287  };
288 
289  {
290  std::vector<rclcpp::CallbackGroup::WeakPtr> added_cbgs_cpy;
291  {
292  std::lock_guard lock{added_callback_groups_mutex_};
293  added_cbgs_cpy = added_callback_groups;
294  }
295 
296  std::vector<node_interfaces::NodeBaseInterface::WeakPtr> added_nodes_cpy;
297  {
298  std::lock_guard lock{added_nodes_mutex_};
299  added_nodes_cpy = added_nodes;
300  }
301 
302  // *3 is a rough estimate of how many callback_group a node may have
303  next_group_data.reserve(added_cbgs_cpy.size() + (added_nodes_cpy.size() * 3));
304 
305  nodes_executable_cache->clear();
306 
307  for (const node_interfaces::NodeBaseInterface::WeakPtr & node_weak_ptr : added_nodes_cpy) {
308  auto node_ptr = node_weak_ptr.lock();
309  if (node_ptr) {
310  node_ptr->for_each_callback_group(
311  [&insert_data](rclcpp::CallbackGroup::SharedPtr cbg) {
312  if (cbg->automatically_add_to_executor_with_node() ) {
313  insert_data(std::move(cbg), CallbackGroupData::Origin::Node);
314  }
315  });
316 
317  // register node guard condition, and trigger
318  // resync on node change event
319  nodes_executable_cache->add_guard_condition_event(
320  node_ptr->get_shared_notify_guard_condition(),
321  [this]() {
322  scheduler->trigger_sync();
323  });
324  }
325  }
326 
327  for (const rclcpp::CallbackGroup::WeakPtr & cbg : added_cbgs_cpy) {
328  auto p = cbg.lock();
329  if (p) {
330  insert_data(std::move(p), CallbackGroupData::Origin::ManualAdded);
331  }
332  }
333  }
334 
335  // FIXME inform scheduler about remove cbgs
336 
337  callback_groups.swap(next_group_data);
338 }
339 
340 void
341 EventsCBGExecutor::run(size_t this_thread_number, bool block_initially)
342 {
343  (void) this_thread_number;
344 
345  while (rclcpp::ok(this->context_) && !cancel_requested_.load() ) {
346  if(block_initially) {
347  block_initially = false;
348  scheduler->block_worker_thread();
349  }
350 
351  sync_callback_groups();
352 
353  auto ready_entity = scheduler->get_next_ready_entity();
354  if(!ready_entity.entity) {
355  scheduler->block_worker_thread();
356  continue;
357  }
358 
359  if(ready_entity.moreEntitiesReady) {
360  scheduler->unblock_one_worker_thread();
361  }
362 
363  ready_entity.entity->execute_function();
364 
365  scheduler->mark_entity_as_executed(*ready_entity.entity);
366  }
367 }
368 
369 void
370 EventsCBGExecutor::run(
371  size_t this_thread_number,
372  const std::function<void(const std::exception & e)> & exception_handler)
373 {
374  (void) this_thread_number;
375 
376  while (rclcpp::ok(this->context_) && !cancel_requested_.load() ) {
377  sync_callback_groups();
378 
379  auto ready_entity = scheduler->get_next_ready_entity();
380  if(!ready_entity.entity) {
381  scheduler->block_worker_thread();
382  continue;
383  }
384 
385  try {
386  ready_entity.entity->execute_function();
387  } catch (const std::exception & e) {
388  exception_handler(e);
389  }
390 
391  scheduler->mark_entity_as_executed(*ready_entity.entity);
392  }
393 }
394 
395 
396 void EventsCBGExecutor::spin_once_internal(std::chrono::nanoseconds timeout)
397 {
398  if (!rclcpp::ok(this->context_) || cancel_requested_.load() ) {
399  return;
400  }
401 
402  sync_callback_groups();
403 
404  auto ready_entity = scheduler->get_next_ready_entity();
405  if(!ready_entity.entity) {
406  if (timeout < std::chrono::nanoseconds::zero()) {
407  // can't use std::chrono::nanoseconds::max, as wait_for
408  // internally computes end time by using ::now() + timeout
409  // as a workaround, we use some absurd high timeout
410  timeout = std::chrono::hours(10000);
411  }
412 
413  scheduler->block_worker_thread_for(timeout);
414 
415  ready_entity = scheduler->get_next_ready_entity();
416 
417  if (!ready_entity.entity) {
418  return;
419  }
420  }
421 
422  ready_entity.entity->execute_function();
423 
424  scheduler->mark_entity_as_executed(*ready_entity.entity);
425 }
426 
427 void
428 EventsCBGExecutor::spin_once(std::chrono::nanoseconds timeout)
429 {
430  if (spinning.exchange(true) ) {
431  throw std::runtime_error("spin_once() called while already spinning");
432  }
433  RCPPUTILS_SCOPE_EXIT(
434  this->spinning.store(false);
435  this->cancel_requested_.store(false););
436  if (cancel_requested_.load()) {
437  return;
438  }
439 
440  spin_once_internal(timeout);
441 }
442 
443 
444 void
445 EventsCBGExecutor::spin_some(std::chrono::nanoseconds max_duration)
446 {
447  collect_and_execute_ready_events(max_duration, false);
448 }
449 
450 void EventsCBGExecutor::spin_all(std::chrono::nanoseconds max_duration)
451 {
452  if (max_duration < std::chrono::nanoseconds::zero() ) {
453  throw std::invalid_argument("max_duration must be greater than or equal to 0");
454  }
455 
456  collect_and_execute_ready_events(max_duration, true);
457 }
458 
460  std::chrono::nanoseconds max_duration,
461  bool recollect_if_no_work_available)
462 {
463  if (spinning.exchange(true) ) {
464  throw std::runtime_error("collect_and_execute_ready_events() called while already spinning");
465  }
466  RCPPUTILS_SCOPE_EXIT(
467  this->spinning.store(false);
468  this->cancel_requested_.store(false););
469  if (cancel_requested_.load()) {
470  return false;
471  }
472 
473  const auto start = std::chrono::steady_clock::now();
474  const auto end_time = start + max_duration;
475  auto cur_time = start;
476 
477  bool had_work = false;
478 
479  while (rclcpp::ok(this->context_) && !cancel_requested_.load() && cur_time <= end_time) {
480  sync_callback_groups();
481 
483  return had_work;
484  }
485 
486  had_work = true;
487 
488  if (!recollect_if_no_work_available) {
489  // we are done
490  return had_work;
491  }
492 
493  cur_time = std::chrono::steady_clock::now();
494  }
495 
496  return had_work;
497 }
498 void
500 {
501  if (spinning.exchange(true)) {
502  throw std::runtime_error("spin() called while already spinning");
503  }
504  RCPPUTILS_SCOPE_EXIT(
505  this->spinning.store(false);
506  this->cancel_requested_.store(false););
507  if (cancel_requested_.load()) {
508  return;
509  }
510  std::vector<std::thread> threads;
511  size_t thread_id = 0;
512  for ( ; thread_id < number_of_threads_ - 1; ++thread_id) {
513  threads.emplace_back([this, thread_id]()
514  {
515  run(thread_id, true);
516  });
517  }
518 
519  run(thread_id, false);
520  for (auto & thread : threads) {
521  thread.join();
522  }
523 }
524 
526  const std::function<void(const std::exception & e)> & exception_handler)
527 {
528  if (spinning.exchange(true) ) {
529  throw std::runtime_error("spin() called while already spinning");
530  }
531  RCPPUTILS_SCOPE_EXIT(
532  this->spinning.store(false);
533  this->cancel_requested_.store(false););
534  if (cancel_requested_.load()) {
535  return;
536  }
537  std::vector<std::thread> threads;
538  size_t thread_id = 0;
539  for ( ; thread_id < number_of_threads_ - 1; ++thread_id) {
540  threads.emplace_back([this, thread_id, exception_handler]()
541  {
542  run(thread_id, exception_handler);
543  }
544  );
545  }
546 
547  run(thread_id, exception_handler);
548  for (auto & thread : threads) {
549  thread.join();
550  }
551 }
552 
553 void
555  const rclcpp::CallbackGroup::SharedPtr & group_ptr,
556  const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & /*node_ptr*/,
557  bool notify)
558 {
559  std::atomic_bool & has_executor = group_ptr->get_associated_with_executor_atomic();
560  if (has_executor.exchange(true)) {
561  throw std::runtime_error("Callback group has already been added to an executor.");
562  }
563 
564  {
565  std::lock_guard lock{added_callback_groups_mutex_};
566  added_callback_groups.push_back(group_ptr);
567  }
568  trigger_callback_group_sync();
569 
570  if (notify) {
571  // Interrupt waiting to handle new node
572  try {
573  interrupt_guard_condition_->trigger();
574  } catch (const rclcpp::exceptions::RCLError & ex) {
575  throw std::runtime_error(
576  std::string(
577  "Failed to trigger guard condition on callback group add: ") + ex.what() );
578  }
579  }
580 }
581 
582 void
584 {
585  // Only request the cancellation; the spinning flag is owned by the spin
586  // functions and is cleared when they actually return. This keeps
587  // is_spinning() true until the executor has really stopped, and a cancel
588  // issued before a spin is "held" until the next spin consumes it.
589  cancel_requested_.store(true);
590 
591  if(spinning) {
592  scheduler->release_all_worker_threads();
593  }
594 
595  try {
596  interrupt_guard_condition_->trigger();
597  } catch (const rclcpp::exceptions::RCLError & ex) {
598  throw std::runtime_error(
599  std::string("Failed to trigger guard condition in cancel: ") + ex.what() );
600  }
601 }
602 
603 std::vector<rclcpp::CallbackGroup::WeakPtr>
605 {
606  if (!spinning) {
607  sync_callback_groups();
608  }
609  std::lock_guard lock{callback_groups_mutex};
610  std::vector<rclcpp::CallbackGroup::WeakPtr> ret;
611  ret.reserve(callback_groups.size());
612  for( auto & cbg_data : callback_groups) {
613  ret.push_back(cbg_data.callback_group);
614  }
615  return ret;
616 }
617 
618 std::vector<rclcpp::CallbackGroup::WeakPtr>
620 {
621  if (!spinning) {
622  sync_callback_groups();
623  }
624  std::lock_guard lock{callback_groups_mutex};
625  std::vector<rclcpp::CallbackGroup::WeakPtr> ret;
626  ret.reserve(callback_groups.size());
627  for( auto & cbg_data : callback_groups) {
628  if(cbg_data.origin == CallbackGroupData::Origin::ManualAdded) {
629  ret.push_back(cbg_data.callback_group);
630  }
631  }
632  return ret;
633 }
634 
635 std::vector<rclcpp::CallbackGroup::WeakPtr>
637 {
638  if (!spinning) {
639  sync_callback_groups();
640  }
641  std::lock_guard lock{callback_groups_mutex};
642  std::vector<rclcpp::CallbackGroup::WeakPtr> ret;
643  ret.reserve(callback_groups.size());
644  for( auto & cbg_data : callback_groups) {
645  if(cbg_data.origin == CallbackGroupData::Origin::Node) {
646  ret.push_back(cbg_data.callback_group);
647  }
648  }
649  return ret;
650 }
651 
652 void EventsCBGExecutor::unregister_event_callbacks(const rclcpp::CallbackGroup::SharedPtr & cbg)
653 const
654 {
655  const auto remove_sub = [](const rclcpp::SubscriptionBase::SharedPtr & s) {
656  s->clear_on_new_message_callback();
657  };
658  const auto remove_timer = [this](const rclcpp::TimerBase::SharedPtr & s) {
659  timer_manager->remove_timer(s);
660  };
661 
662  const auto remove_client = [](const rclcpp::ClientBase::SharedPtr & s) {
663  s->clear_on_new_response_callback();
664  };
665 
666  const auto remove_service = [](const rclcpp::ServiceBase::SharedPtr & s) {
667  s->clear_on_new_request_callback();
668  };
669 
670  auto gc_ptr = cbg->get_notify_guard_condition();
671  if (gc_ptr) {
672  gc_ptr->set_on_trigger_callback(std::function<void(size_t)>());
673  }
674 
675  const auto remove_waitable = [](const rclcpp::Waitable::SharedPtr & s) {
676  s->clear_on_ready_callback();
677  };
678 
679 
680  cbg->collect_all_ptrs(remove_sub, remove_service, remove_client, remove_timer, remove_waitable);
681 }
682 
683 
684 void
686  const rclcpp::CallbackGroup::SharedPtr & group_ptr,
687  bool notify)
688 {
689  if (!group_ptr->get_associated_with_executor_atomic().load()) {
690  throw std::runtime_error("Callback group needs to be associated with an executor.");
691  }
692  bool found = false;
693  {
694  std::lock_guard lock{added_callback_groups_mutex_};
695  added_callback_groups.erase(
696  std::remove_if(
697  added_callback_groups.begin(), added_callback_groups.end(),
698  [&group_ptr, &found](const auto & weak_ptr) {
699  auto shr_ptr = weak_ptr.lock();
700  if (!shr_ptr) {
701  return true;
702  }
703 
704  if (group_ptr == shr_ptr) {
705  found = true;
706  return true;
707  }
708  return false;
709  }), added_callback_groups.end() );
710  }
711 
712  if(!found) {
713  throw std::runtime_error("Callback group needs to be associated with this executor.");
714  }
715 
716  // we need to unregister all callbacks
717  unregister_event_callbacks(group_ptr);
718 
719  if (found) {
720  trigger_callback_group_sync();
721  }
722 
723  group_ptr->get_associated_with_executor_atomic().exchange(false);
724 
725  if (notify) {
726  // Interrupt waiting to handle new node
727  try {
728  interrupt_guard_condition_->trigger();
729  } catch (const rclcpp::exceptions::RCLError & ex) {
730  throw std::runtime_error(
731  std::string(
732  "Failed to trigger guard condition on callback group add: ") + ex.what() );
733  }
734  }
735 }
736 
737 void
738 EventsCBGExecutor::add_node(
739  const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr,
740  bool /*notify*/)
741 {
742  // If the node already has an executor
743  std::atomic_bool & has_executor = node_ptr->get_associated_with_executor_atomic();
744  if (has_executor.exchange(true) ) {
745  throw std::runtime_error(
746  std::string("Node '") + node_ptr->get_fully_qualified_name() +
747  "' has already been added to an executor.");
748  }
749 
750  {
751  std::lock_guard lock{added_nodes_mutex_};
752  added_nodes.push_back(node_ptr);
753  }
754 
755  trigger_callback_group_sync();
756 }
757 
758 void
759 EventsCBGExecutor::add_node(const std::shared_ptr<rclcpp::Node> & node_ptr, bool notify)
760 {
761  add_node(node_ptr->get_node_base_interface(), notify);
762 }
763 
764 void
765 EventsCBGExecutor::remove_node(
766  const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr,
767  bool notify)
768 {
769  std::atomic_bool & has_executor = node_ptr->get_associated_with_executor_atomic();
770  if (!has_executor.exchange(false)) {
771  throw std::runtime_error(
772  std::string("Node '") + node_ptr->get_fully_qualified_name() +
773  "' needs to be associated with an executor.");
774  }
775 
776  {
777  std::lock_guard lock{added_nodes_mutex_};
778  added_nodes.erase(
779  std::remove_if(
780  added_nodes.begin(), added_nodes.end(), [&node_ptr](const auto & weak_ptr) {
781  const auto shr_ptr = weak_ptr.lock();
782  return shr_ptr && shr_ptr == node_ptr;
783  }), added_nodes.end());
784  }
785 
786  node_ptr->for_each_callback_group(
787  [this](const rclcpp::CallbackGroup::SharedPtr & cbg)
788  {
789  unregister_event_callbacks(cbg);
790  }
791  );
792 
793  node_ptr->get_shared_notify_guard_condition()->set_on_trigger_callback(
794  std::function<void(size_t)>());
795 
796  trigger_callback_group_sync();
797 
798  if (notify) {
799  scheduler->unblock_one_worker_thread();
800  // Interrupt waiting to handle new node
801  try {
802  interrupt_guard_condition_->trigger();
803  } catch (const rclcpp::exceptions::RCLError & ex) {
804  throw std::runtime_error(
805  std::string(
806  "Failed to trigger guard condition on callback group add: ") + ex.what() );
807  }
808  }
809 
810  node_ptr->get_associated_with_executor_atomic().store(false);
811 }
812 
813 void
814 EventsCBGExecutor::remove_node(const std::shared_ptr<rclcpp::Node> & node_ptr, bool notify)
815 {
816  remove_node(node_ptr->get_node_base_interface(), notify);
817 }
818 
819 // add a callback group to the executor, not bound to any node
820 void EventsCBGExecutor::add_callback_group_only(const rclcpp::CallbackGroup::SharedPtr & group_ptr)
821 {
822  add_callback_group(group_ptr, nullptr, true);
823 }
824 } // namespace rclcpp::executors
std::atomic_bool cancel_requested_
Tracks a pending cancel request that has not yet been consumed by a spin.
Definition: executor.hpp:578
Created when the return code does not match one of the other specialized exceptions.
Definition: exceptions.hpp:162
RCLCPP_PUBLIC std::vector< rclcpp::CallbackGroup::WeakPtr > get_all_callback_groups() override
Get callback groups that belong to executor.
RCLCPP_PUBLIC void spin_once(std::chrono::nanoseconds timeout=std::chrono::nanoseconds(-1)) override
Collect work once and execute the next available work, optionally within a duration.
RCLCPP_PUBLIC void add_callback_group(const rclcpp::CallbackGroup::SharedPtr &group_ptr, const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr &node_ptr, bool notify=true) override
Add a callback group to an executor.
RCLCPP_PUBLIC void spin_some(std::chrono::nanoseconds max_duration=std::chrono::nanoseconds(0)) override
Collect work once and execute all available work, optionally within a max duration.
bool execute_previous_ready_executables_until(const std::chrono::time_point< std::chrono::steady_clock > &stop_time)
RCLCPP_PUBLIC EventsCBGExecutor(const rclcpp::ExecutorOptions &options=rclcpp::ExecutorOptions(), size_t number_of_threads=0, std::chrono::nanoseconds timeout=std::chrono::nanoseconds(-1))
RCLCPP_PUBLIC void spin_all(std::chrono::nanoseconds max_duration) override
Collect and execute work repeatedly within a duration or until no more work is available.
RCLCPP_PUBLIC void remove_node(const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr &node_ptr, bool notify=true) override
Remove a node from the executor.
RCLCPP_PUBLIC bool collect_and_execute_ready_events(std::chrono::nanoseconds max_duration, bool recollect_if_no_work_available)
RCLCPP_PUBLIC void cancel() override
Cancel any running spin* function, causing it to return.
RCLCPP_PUBLIC std::vector< rclcpp::CallbackGroup::WeakPtr > get_manually_added_callback_groups() override
Get callback groups that belong to executor.
RCLCPP_PUBLIC std::vector< rclcpp::CallbackGroup::WeakPtr > get_automatically_added_callback_groups_from_nodes() override
Get callback groups that belong to executor.
RCLCPP_PUBLIC void spin() override
RCLCPP_PUBLIC void remove_callback_group(const rclcpp::CallbackGroup::SharedPtr &group_ptr, bool notify=true) override
Remove a callback group from the executor.
RCLCPP_PUBLIC bool ok(const rclcpp::Context::SharedPtr &context=rclcpp::contexts::get_global_default_context())
Check rclcpp's status.
RCLCPP_PUBLIC bool shutdown(const rclcpp::Context::SharedPtr &context=rclcpp::contexts::get_global_default_context(), const std::string &reason="user called rclcpp::shutdown()")
Shutdown rclcpp context, invalidating it for derived entities.
Options to be passed to the executor constructor.