ROS 2 rclcpp + rcl - rolling  rolling-e47c0848
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  if(ready_entity.moreEntitiesReady) {
386  scheduler->unblock_one_worker_thread();
387  }
388 
389  try {
390  ready_entity.entity->execute_function();
391  } catch (const std::exception & e) {
392  exception_handler(e);
393  }
394 
395  scheduler->mark_entity_as_executed(*ready_entity.entity);
396  }
397 }
398 
399 
400 void EventsCBGExecutor::spin_once_internal(std::chrono::nanoseconds timeout)
401 {
402  if (!rclcpp::ok(this->context_) || cancel_requested_.load() ) {
403  return;
404  }
405 
406  sync_callback_groups();
407 
408  auto ready_entity = scheduler->get_next_ready_entity();
409  if(!ready_entity.entity) {
410  if (timeout < std::chrono::nanoseconds::zero()) {
411  // can't use std::chrono::nanoseconds::max, as wait_for
412  // internally computes end time by using ::now() + timeout
413  // as a workaround, we use some absurd high timeout
414  timeout = std::chrono::hours(10000);
415  }
416 
417  scheduler->block_worker_thread_for(timeout);
418 
419  ready_entity = scheduler->get_next_ready_entity();
420 
421  if (!ready_entity.entity) {
422  return;
423  }
424  }
425 
426  ready_entity.entity->execute_function();
427 
428  scheduler->mark_entity_as_executed(*ready_entity.entity);
429 }
430 
431 void
432 EventsCBGExecutor::spin_once(std::chrono::nanoseconds timeout)
433 {
434  if (spinning.exchange(true) ) {
435  throw std::runtime_error("spin_once() called while already spinning");
436  }
437  RCPPUTILS_SCOPE_EXIT(
438  this->spinning.store(false);
439  this->cancel_requested_.store(false););
440  if (cancel_requested_.load()) {
441  return;
442  }
443 
444  spin_once_internal(timeout);
445 }
446 
447 
448 void
449 EventsCBGExecutor::spin_some(std::chrono::nanoseconds max_duration)
450 {
451  collect_and_execute_ready_events(max_duration, false);
452 }
453 
454 void EventsCBGExecutor::spin_all(std::chrono::nanoseconds max_duration)
455 {
456  if (max_duration < std::chrono::nanoseconds::zero() ) {
457  throw std::invalid_argument("max_duration must be greater than or equal to 0");
458  }
459 
460  collect_and_execute_ready_events(max_duration, true);
461 }
462 
464  std::chrono::nanoseconds max_duration,
465  bool recollect_if_no_work_available)
466 {
467  if (spinning.exchange(true) ) {
468  throw std::runtime_error("collect_and_execute_ready_events() called while already spinning");
469  }
470  RCPPUTILS_SCOPE_EXIT(
471  this->spinning.store(false);
472  this->cancel_requested_.store(false););
473  if (cancel_requested_.load()) {
474  return false;
475  }
476 
477  const auto start = std::chrono::steady_clock::now();
478  const auto end_time = start + max_duration;
479  auto cur_time = start;
480 
481  bool had_work = false;
482 
483  while (rclcpp::ok(this->context_) && !cancel_requested_.load() && cur_time <= end_time) {
484  sync_callback_groups();
485 
487  return had_work;
488  }
489 
490  had_work = true;
491 
492  if (!recollect_if_no_work_available) {
493  // we are done
494  return had_work;
495  }
496 
497  cur_time = std::chrono::steady_clock::now();
498  }
499 
500  return had_work;
501 }
502 void
504 {
505  if (spinning.exchange(true)) {
506  throw std::runtime_error("spin() called while already spinning");
507  }
508  RCPPUTILS_SCOPE_EXIT(
509  this->spinning.store(false);
510  this->cancel_requested_.store(false););
511  if (cancel_requested_.load()) {
512  return;
513  }
514  std::vector<std::thread> threads;
515  size_t thread_id = 0;
516  for ( ; thread_id < number_of_threads_ - 1; ++thread_id) {
517  threads.emplace_back([this, thread_id]()
518  {
519  run(thread_id, true);
520  });
521  }
522 
523  run(thread_id, false);
524  for (auto & thread : threads) {
525  thread.join();
526  }
527 }
528 
530  const std::function<void(const std::exception & e)> & exception_handler)
531 {
532  if (spinning.exchange(true) ) {
533  throw std::runtime_error("spin() called while already spinning");
534  }
535  RCPPUTILS_SCOPE_EXIT(
536  this->spinning.store(false);
537  this->cancel_requested_.store(false););
538  if (cancel_requested_.load()) {
539  return;
540  }
541  std::vector<std::thread> threads;
542  size_t thread_id = 0;
543  for ( ; thread_id < number_of_threads_ - 1; ++thread_id) {
544  threads.emplace_back([this, thread_id, exception_handler]()
545  {
546  run(thread_id, exception_handler);
547  }
548  );
549  }
550 
551  run(thread_id, exception_handler);
552  for (auto & thread : threads) {
553  thread.join();
554  }
555 }
556 
557 void
559  const rclcpp::CallbackGroup::SharedPtr & group_ptr,
560  const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & /*node_ptr*/,
561  bool notify)
562 {
563  std::atomic_bool & has_executor = group_ptr->get_associated_with_executor_atomic();
564  if (has_executor.exchange(true)) {
565  throw std::runtime_error("Callback group has already been added to an executor.");
566  }
567 
568  {
569  std::lock_guard lock{added_callback_groups_mutex_};
570  added_callback_groups.push_back(group_ptr);
571  }
572  trigger_callback_group_sync();
573 
574  if (notify) {
575  // Interrupt waiting to handle new node
576  try {
577  interrupt_guard_condition_->trigger();
578  } catch (const rclcpp::exceptions::RCLError & ex) {
579  throw std::runtime_error(
580  std::string(
581  "Failed to trigger guard condition on callback group add: ") + ex.what() );
582  }
583  }
584 }
585 
586 void
588 {
589  // Only request the cancellation; the spinning flag is owned by the spin
590  // functions and is cleared when they actually return. This keeps
591  // is_spinning() true until the executor has really stopped, and a cancel
592  // issued before a spin is "held" until the next spin consumes it.
593  cancel_requested_.store(true);
594 
595  if(spinning) {
596  scheduler->release_all_worker_threads();
597  }
598 
599  try {
600  interrupt_guard_condition_->trigger();
601  } catch (const rclcpp::exceptions::RCLError & ex) {
602  throw std::runtime_error(
603  std::string("Failed to trigger guard condition in cancel: ") + ex.what() );
604  }
605 }
606 
607 std::vector<rclcpp::CallbackGroup::WeakPtr>
609 {
610  if (!spinning) {
611  sync_callback_groups();
612  }
613  std::lock_guard lock{callback_groups_mutex};
614  std::vector<rclcpp::CallbackGroup::WeakPtr> ret;
615  ret.reserve(callback_groups.size());
616  for( auto & cbg_data : callback_groups) {
617  ret.push_back(cbg_data.callback_group);
618  }
619  return ret;
620 }
621 
622 std::vector<rclcpp::CallbackGroup::WeakPtr>
624 {
625  if (!spinning) {
626  sync_callback_groups();
627  }
628  std::lock_guard lock{callback_groups_mutex};
629  std::vector<rclcpp::CallbackGroup::WeakPtr> ret;
630  ret.reserve(callback_groups.size());
631  for( auto & cbg_data : callback_groups) {
632  if(cbg_data.origin == CallbackGroupData::Origin::ManualAdded) {
633  ret.push_back(cbg_data.callback_group);
634  }
635  }
636  return ret;
637 }
638 
639 std::vector<rclcpp::CallbackGroup::WeakPtr>
641 {
642  if (!spinning) {
643  sync_callback_groups();
644  }
645  std::lock_guard lock{callback_groups_mutex};
646  std::vector<rclcpp::CallbackGroup::WeakPtr> ret;
647  ret.reserve(callback_groups.size());
648  for( auto & cbg_data : callback_groups) {
649  if(cbg_data.origin == CallbackGroupData::Origin::Node) {
650  ret.push_back(cbg_data.callback_group);
651  }
652  }
653  return ret;
654 }
655 
656 void EventsCBGExecutor::unregister_event_callbacks(const rclcpp::CallbackGroup::SharedPtr & cbg)
657 const
658 {
659  const auto remove_sub = [](const rclcpp::SubscriptionBase::SharedPtr & s) {
660  s->clear_on_new_message_callback();
661  };
662  const auto remove_timer = [this](const rclcpp::TimerBase::SharedPtr & s) {
663  timer_manager->remove_timer(s);
664  };
665 
666  const auto remove_client = [](const rclcpp::ClientBase::SharedPtr & s) {
667  s->clear_on_new_response_callback();
668  };
669 
670  const auto remove_service = [](const rclcpp::ServiceBase::SharedPtr & s) {
671  s->clear_on_new_request_callback();
672  };
673 
674  auto gc_ptr = cbg->get_notify_guard_condition();
675  if (gc_ptr) {
676  gc_ptr->set_on_trigger_callback(std::function<void(size_t)>());
677  }
678 
679  const auto remove_waitable = [](const rclcpp::Waitable::SharedPtr & s) {
680  s->clear_on_ready_callback();
681  };
682 
683 
684  cbg->collect_all_ptrs(remove_sub, remove_service, remove_client, remove_timer, remove_waitable);
685 }
686 
687 
688 void
690  const rclcpp::CallbackGroup::SharedPtr & group_ptr,
691  bool notify)
692 {
693  if (!group_ptr->get_associated_with_executor_atomic().load()) {
694  throw std::runtime_error("Callback group needs to be associated with an executor.");
695  }
696  bool found = false;
697  {
698  std::lock_guard lock{added_callback_groups_mutex_};
699  added_callback_groups.erase(
700  std::remove_if(
701  added_callback_groups.begin(), added_callback_groups.end(),
702  [&group_ptr, &found](const auto & weak_ptr) {
703  auto shr_ptr = weak_ptr.lock();
704  if (!shr_ptr) {
705  return true;
706  }
707 
708  if (group_ptr == shr_ptr) {
709  found = true;
710  return true;
711  }
712  return false;
713  }), added_callback_groups.end() );
714  }
715 
716  if(!found) {
717  throw std::runtime_error("Callback group needs to be associated with this executor.");
718  }
719 
720  // we need to unregister all callbacks
721  unregister_event_callbacks(group_ptr);
722 
723  if (found) {
724  trigger_callback_group_sync();
725  }
726 
727  group_ptr->get_associated_with_executor_atomic().exchange(false);
728 
729  if (notify) {
730  // Interrupt waiting to handle new node
731  try {
732  interrupt_guard_condition_->trigger();
733  } catch (const rclcpp::exceptions::RCLError & ex) {
734  throw std::runtime_error(
735  std::string(
736  "Failed to trigger guard condition on callback group add: ") + ex.what() );
737  }
738  }
739 }
740 
741 void
742 EventsCBGExecutor::add_node(
743  const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr,
744  bool /*notify*/)
745 {
746  // If the node already has an executor
747  std::atomic_bool & has_executor = node_ptr->get_associated_with_executor_atomic();
748  if (has_executor.exchange(true) ) {
749  throw std::runtime_error(
750  std::string("Node '") + node_ptr->get_fully_qualified_name() +
751  "' has already been added to an executor.");
752  }
753 
754  {
755  std::lock_guard lock{added_nodes_mutex_};
756  added_nodes.push_back(node_ptr);
757  }
758 
759  trigger_callback_group_sync();
760 }
761 
762 void
763 EventsCBGExecutor::add_node(const std::shared_ptr<rclcpp::Node> & node_ptr, bool notify)
764 {
765  add_node(node_ptr->get_node_base_interface(), notify);
766 }
767 
768 void
769 EventsCBGExecutor::remove_node(
770  const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr,
771  bool notify)
772 {
773  std::atomic_bool & has_executor = node_ptr->get_associated_with_executor_atomic();
774  if (!has_executor.exchange(false)) {
775  throw std::runtime_error(
776  std::string("Node '") + node_ptr->get_fully_qualified_name() +
777  "' needs to be associated with an executor.");
778  }
779 
780  {
781  std::lock_guard lock{added_nodes_mutex_};
782  added_nodes.erase(
783  std::remove_if(
784  added_nodes.begin(), added_nodes.end(), [&node_ptr](const auto & weak_ptr) {
785  const auto shr_ptr = weak_ptr.lock();
786  return shr_ptr && shr_ptr == node_ptr;
787  }), added_nodes.end());
788  }
789 
790  node_ptr->for_each_callback_group(
791  [this](const rclcpp::CallbackGroup::SharedPtr & cbg)
792  {
793  unregister_event_callbacks(cbg);
794  }
795  );
796 
797  node_ptr->get_shared_notify_guard_condition()->set_on_trigger_callback(
798  std::function<void(size_t)>());
799 
800  trigger_callback_group_sync();
801 
802  if (notify) {
803  scheduler->unblock_one_worker_thread();
804  // Interrupt waiting to handle new node
805  try {
806  interrupt_guard_condition_->trigger();
807  } catch (const rclcpp::exceptions::RCLError & ex) {
808  throw std::runtime_error(
809  std::string(
810  "Failed to trigger guard condition on callback group add: ") + ex.what() );
811  }
812  }
813 
814  node_ptr->get_associated_with_executor_atomic().store(false);
815 }
816 
817 void
818 EventsCBGExecutor::remove_node(const std::shared_ptr<rclcpp::Node> & node_ptr, bool notify)
819 {
820  remove_node(node_ptr->get_node_base_interface(), notify);
821 }
822 
823 // add a callback group to the executor, not bound to any node
824 void EventsCBGExecutor::add_callback_group_only(const rclcpp::CallbackGroup::SharedPtr & group_ptr)
825 {
826  add_callback_group(group_ptr, nullptr, true);
827 }
828 } // 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:575
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.