ROS 2 rclcpp + rcl - rolling  rolling-e7363f10
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.load(std::memory_order_acquire)) {
243  return;
244  }
245 
246  if (!needs_callback_group_resync.exchange(false) ) {
247  return;
248  }
249 
250  std::scoped_lock l(callback_groups_mutex);
251 
252 
253  std::vector<std::pair<CallbackGroupData *, rclcpp::CallbackGroup::SharedPtr>> cur_group_data;
254  cur_group_data.reserve(callback_groups.size() );
255 
256  for (CallbackGroupData & d : callback_groups) {
257  auto p = d.callback_group.lock();
258  if (p) {
259  cur_group_data.emplace_back(&d, std::move(p) );
260  }
261  }
262 
263  std::vector<CallbackGroupData> next_group_data;
264 
265  std::set<CallbackGroup *> added_cbgs;
266 
267  auto insert_data =
268  [&cur_group_data, &next_group_data, &added_cbgs,
269  this](rclcpp::CallbackGroup::SharedPtr && cbg, CallbackGroupData::Origin origin) {
270  // nodes may share callback groups, therefore we need to make sure we only add them once
271  if (added_cbgs.find(cbg.get() ) != added_cbgs.end() ) {
272  return;
273  }
274 
275  added_cbgs.insert(cbg.get() );
276 
277  for (const auto & pair : cur_group_data) {
278  if (pair.second == cbg) {
279  next_group_data.push_back(std::move(*pair.first) );
280  // call regenerate, in case something changed in the group
281  next_group_data.back().registered_entities->regenerate_events();
282  return;
283  }
284  }
285 
286  CallbackGroupData new_entry{.callback_group = cbg,
287  .registered_entities = std::make_unique<cbg_executor::RegisteredEntityCache>(*scheduler,
288  *timer_manager, cbg), .origin = origin};
289  new_entry.registered_entities->regenerate_events();
290  next_group_data.push_back(std::move(new_entry) );
291  };
292 
293  {
294  std::vector<rclcpp::CallbackGroup::WeakPtr> added_cbgs_cpy;
295  {
296  std::lock_guard lock{added_callback_groups_mutex_};
297  added_cbgs_cpy = added_callback_groups;
298  }
299 
300  std::vector<node_interfaces::NodeBaseInterface::WeakPtr> added_nodes_cpy;
301  {
302  std::lock_guard lock{added_nodes_mutex_};
303  added_nodes_cpy = added_nodes;
304  }
305 
306  // *3 is a rough estimate of how many callback_group a node may have
307  next_group_data.reserve(added_cbgs_cpy.size() + (added_nodes_cpy.size() * 3));
308 
309  nodes_executable_cache->clear();
310 
311  for (const node_interfaces::NodeBaseInterface::WeakPtr & node_weak_ptr : added_nodes_cpy) {
312  auto node_ptr = node_weak_ptr.lock();
313  if (node_ptr) {
314  node_ptr->for_each_callback_group(
315  [&insert_data](rclcpp::CallbackGroup::SharedPtr cbg) {
316  if (cbg->automatically_add_to_executor_with_node() ) {
317  insert_data(std::move(cbg), CallbackGroupData::Origin::Node);
318  }
319  });
320 
321  // register node guard condition, and trigger
322  // resync on node change event
323  nodes_executable_cache->add_guard_condition_event(
324  node_ptr->get_shared_notify_guard_condition(),
325  [this]() {
326  scheduler->trigger_sync();
327  });
328  }
329  }
330 
331  for (const rclcpp::CallbackGroup::WeakPtr & cbg : added_cbgs_cpy) {
332  auto p = cbg.lock();
333  if (p) {
334  insert_data(std::move(p), CallbackGroupData::Origin::ManualAdded);
335  }
336  }
337  }
338 
339  // FIXME inform scheduler about remove cbgs
340 
341  callback_groups.swap(next_group_data);
342 }
343 
344 void
345 EventsCBGExecutor::run(size_t this_thread_number, bool block_initially)
346 {
347  (void) this_thread_number;
348 
349  cbg_executor::Worker worker;
350 
351  while (rclcpp::ok(this->context_) && !cancel_requested_.load() ) {
352  if(block_initially) {
353  block_initially = false;
354  scheduler->block_worker_thread(&worker);
355  }
356 
357  sync_callback_groups();
358 
359  auto ready_entity = scheduler->get_next_ready_entity();
360  if(!ready_entity.entity) {
361  scheduler->block_worker_thread(&worker);
362  continue;
363  }
364 
365  if(ready_entity.moreEntitiesReady) {
366  scheduler->unblock_one_worker_thread();
367  }
368 
369  ready_entity.entity->execute_function();
370 
371  scheduler->suppress_thread_wakeup();
372  scheduler->mark_entity_as_executed(*ready_entity.entity);
373  }
374 }
375 
376 void
377 EventsCBGExecutor::run(
378  size_t this_thread_number,
379  const std::function<void(const std::exception & e)> & exception_handler)
380 {
381  (void) this_thread_number;
382  cbg_executor::Worker worker;
383 
384  while (rclcpp::ok(this->context_) && !cancel_requested_.load() ) {
385  sync_callback_groups();
386 
387  auto ready_entity = scheduler->get_next_ready_entity();
388  if(!ready_entity.entity) {
389  scheduler->block_worker_thread(&worker);
390  continue;
391  }
392 
393  if(ready_entity.moreEntitiesReady) {
394  scheduler->unblock_one_worker_thread();
395  }
396 
397  try {
398  ready_entity.entity->execute_function();
399  } catch (const std::exception & e) {
400  exception_handler(e);
401  }
402 
403  scheduler->suppress_thread_wakeup();
404  scheduler->mark_entity_as_executed(*ready_entity.entity);
405  }
406 }
407 
408 
409 void EventsCBGExecutor::spin_once_internal(std::chrono::nanoseconds timeout)
410 {
411  if (!rclcpp::ok(this->context_) || cancel_requested_.load() ) {
412  return;
413  }
414 
415  sync_callback_groups();
416 
417  auto ready_entity = scheduler->get_next_ready_entity();
418  if(!ready_entity.entity) {
419  if (timeout < std::chrono::nanoseconds::zero()) {
420  // can't use std::chrono::nanoseconds::max, as wait_for
421  // internally computes end time by using ::now() + timeout
422  // as a workaround, we use some absurd high timeout
423  timeout = std::chrono::hours(10000);
424  }
425 
426  cbg_executor::Worker worker;
427 
428  // block_worker_thread_for removes the worker from the queue again
429  // before returning, so the stack allocated worker is safe to use here
430  scheduler->block_worker_thread_for(&worker, timeout);
431 
432  ready_entity = scheduler->get_next_ready_entity();
433 
434  if (!ready_entity.entity) {
435  return;
436  }
437  }
438 
439  ready_entity.entity->execute_function();
440 
441  scheduler->mark_entity_as_executed(*ready_entity.entity);
442 }
443 
444 void
445 EventsCBGExecutor::spin_once(std::chrono::nanoseconds timeout)
446 {
447  if (spinning.exchange(true) ) {
448  throw std::runtime_error("spin_once() called while already spinning");
449  }
450  RCPPUTILS_SCOPE_EXIT(
451  this->spinning.store(false);
452  this->cancel_requested_.store(false););
453  if (cancel_requested_.load()) {
454  return;
455  }
456 
457  spin_once_internal(timeout);
458 }
459 
460 
461 void
462 EventsCBGExecutor::spin_some(std::chrono::nanoseconds max_duration)
463 {
464  collect_and_execute_ready_events(max_duration, false);
465 }
466 
467 void EventsCBGExecutor::spin_all(std::chrono::nanoseconds max_duration)
468 {
469  if (max_duration < std::chrono::nanoseconds::zero() ) {
470  throw std::invalid_argument("max_duration must be greater than or equal to 0");
471  }
472 
473  collect_and_execute_ready_events(max_duration, true);
474 }
475 
477  std::chrono::nanoseconds max_duration,
478  bool recollect_if_no_work_available)
479 {
480  if (spinning.exchange(true) ) {
481  throw std::runtime_error("collect_and_execute_ready_events() called while already spinning");
482  }
483  RCPPUTILS_SCOPE_EXIT(
484  this->spinning.store(false);
485  this->cancel_requested_.store(false););
486  if (cancel_requested_.load()) {
487  return false;
488  }
489 
490  const auto start = std::chrono::steady_clock::now();
491  const auto end_time = start + max_duration;
492  auto cur_time = start;
493 
494  bool had_work = false;
495 
496  while (rclcpp::ok(this->context_) && !cancel_requested_.load() && cur_time <= end_time) {
497  sync_callback_groups();
498 
500  return had_work;
501  }
502 
503  had_work = true;
504 
505  if (!recollect_if_no_work_available) {
506  // we are done
507  return had_work;
508  }
509 
510  cur_time = std::chrono::steady_clock::now();
511  }
512 
513  return had_work;
514 }
515 void
517 {
518  if (spinning.exchange(true)) {
519  throw std::runtime_error("spin() called while already spinning");
520  }
521  RCPPUTILS_SCOPE_EXIT(
522  this->spinning.store(false);
523  this->cancel_requested_.store(false););
524  if (cancel_requested_.load()) {
525  return;
526  }
527  std::vector<std::thread> threads;
528  size_t thread_id = 0;
529  for ( ; thread_id < number_of_threads_ - 1; ++thread_id) {
530  threads.emplace_back([this, thread_id]()
531  {
532  run(thread_id, true);
533  });
534  }
535 
536  run(thread_id, false);
537  for (auto & thread : threads) {
538  thread.join();
539  }
540 }
541 
543  const std::function<void(const std::exception & e)> & exception_handler)
544 {
545  if (spinning.exchange(true) ) {
546  throw std::runtime_error("spin() called while already spinning");
547  }
548  RCPPUTILS_SCOPE_EXIT(
549  this->spinning.store(false);
550  this->cancel_requested_.store(false););
551  if (cancel_requested_.load()) {
552  return;
553  }
554  std::vector<std::thread> threads;
555  size_t thread_id = 0;
556  for ( ; thread_id < number_of_threads_ - 1; ++thread_id) {
557  threads.emplace_back([this, thread_id, exception_handler]()
558  {
559  run(thread_id, exception_handler);
560  }
561  );
562  }
563 
564  run(thread_id, exception_handler);
565  for (auto & thread : threads) {
566  thread.join();
567  }
568 }
569 
570 void
572  const rclcpp::CallbackGroup::SharedPtr & group_ptr,
573  const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & /*node_ptr*/,
574  bool notify)
575 {
576  std::atomic_bool & has_executor = group_ptr->get_associated_with_executor_atomic();
577  if (has_executor.exchange(true)) {
578  throw std::runtime_error("Callback group has already been added to an executor.");
579  }
580 
581  {
582  std::lock_guard lock{added_callback_groups_mutex_};
583  added_callback_groups.push_back(group_ptr);
584  }
585  trigger_callback_group_sync();
586 
587  if (notify) {
588  // Interrupt waiting to handle new node
589  try {
590  interrupt_guard_condition_->trigger();
591  } catch (const rclcpp::exceptions::RCLError & ex) {
592  throw std::runtime_error(
593  std::string(
594  "Failed to trigger guard condition on callback group add: ") + ex.what() );
595  }
596  }
597 }
598 
599 void
601 {
602  // Only request the cancellation; the spinning flag is owned by the spin
603  // functions and is cleared when they actually return. This keeps
604  // is_spinning() true until the executor has really stopped, and a cancel
605  // issued before a spin is "held" until the next spin consumes it.
606  cancel_requested_.store(true);
607 
608  if(spinning) {
609  scheduler->release_all_worker_threads();
610  }
611 
612  try {
613  interrupt_guard_condition_->trigger();
614  } catch (const rclcpp::exceptions::RCLError & ex) {
615  throw std::runtime_error(
616  std::string("Failed to trigger guard condition in cancel: ") + ex.what() );
617  }
618 }
619 
620 std::vector<rclcpp::CallbackGroup::WeakPtr>
622 {
623  if (!spinning) {
624  sync_callback_groups();
625  }
626  std::lock_guard lock{callback_groups_mutex};
627  std::vector<rclcpp::CallbackGroup::WeakPtr> ret;
628  ret.reserve(callback_groups.size());
629  for( auto & cbg_data : callback_groups) {
630  ret.push_back(cbg_data.callback_group);
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::ManualAdded) {
646  ret.push_back(cbg_data.callback_group);
647  }
648  }
649  return ret;
650 }
651 
652 std::vector<rclcpp::CallbackGroup::WeakPtr>
654 {
655  if (!spinning) {
656  sync_callback_groups();
657  }
658  std::lock_guard lock{callback_groups_mutex};
659  std::vector<rclcpp::CallbackGroup::WeakPtr> ret;
660  ret.reserve(callback_groups.size());
661  for( auto & cbg_data : callback_groups) {
662  if(cbg_data.origin == CallbackGroupData::Origin::Node) {
663  ret.push_back(cbg_data.callback_group);
664  }
665  }
666  return ret;
667 }
668 
669 void EventsCBGExecutor::unregister_event_callbacks(const rclcpp::CallbackGroup::SharedPtr & cbg)
670 const
671 {
672  const auto remove_sub = [](const rclcpp::SubscriptionBase::SharedPtr & s) {
673  s->clear_on_new_message_callback();
674  };
675  const auto remove_timer = [this](const rclcpp::TimerBase::SharedPtr & s) {
676  timer_manager->remove_timer(s);
677  };
678 
679  const auto remove_client = [](const rclcpp::ClientBase::SharedPtr & s) {
680  s->clear_on_new_response_callback();
681  };
682 
683  const auto remove_service = [](const rclcpp::ServiceBase::SharedPtr & s) {
684  s->clear_on_new_request_callback();
685  };
686 
687  auto gc_ptr = cbg->get_notify_guard_condition();
688  if (gc_ptr) {
689  gc_ptr->set_on_trigger_callback(std::function<void(size_t)>());
690  }
691 
692  const auto remove_waitable = [](const rclcpp::Waitable::SharedPtr & s) {
693  s->clear_on_ready_callback();
694  };
695 
696 
697  cbg->collect_all_ptrs(remove_sub, remove_service, remove_client, remove_timer, remove_waitable);
698 }
699 
700 
701 void
703  const rclcpp::CallbackGroup::SharedPtr & group_ptr,
704  bool notify)
705 {
706  if (!group_ptr->get_associated_with_executor_atomic().load()) {
707  throw std::runtime_error("Callback group needs to be associated with an executor.");
708  }
709  bool found = false;
710  {
711  std::lock_guard lock{added_callback_groups_mutex_};
712  added_callback_groups.erase(
713  std::remove_if(
714  added_callback_groups.begin(), added_callback_groups.end(),
715  [&group_ptr, &found](const auto & weak_ptr) {
716  auto shr_ptr = weak_ptr.lock();
717  if (!shr_ptr) {
718  return true;
719  }
720 
721  if (group_ptr == shr_ptr) {
722  found = true;
723  return true;
724  }
725  return false;
726  }), added_callback_groups.end() );
727  }
728 
729  if(!found) {
730  throw std::runtime_error("Callback group needs to be associated with this executor.");
731  }
732 
733  // we need to unregister all callbacks
734  unregister_event_callbacks(group_ptr);
735 
736  if (found) {
737  trigger_callback_group_sync();
738  }
739 
740  group_ptr->get_associated_with_executor_atomic().exchange(false);
741 
742  if (notify) {
743  // Interrupt waiting to handle new node
744  try {
745  interrupt_guard_condition_->trigger();
746  } catch (const rclcpp::exceptions::RCLError & ex) {
747  throw std::runtime_error(
748  std::string(
749  "Failed to trigger guard condition on callback group add: ") + ex.what() );
750  }
751  }
752 }
753 
754 void
755 EventsCBGExecutor::add_node(
756  const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr,
757  bool /*notify*/)
758 {
759  // If the node already has an executor
760  std::atomic_bool & has_executor = node_ptr->get_associated_with_executor_atomic();
761  if (has_executor.exchange(true) ) {
762  throw std::runtime_error(
763  std::string("Node '") + node_ptr->get_fully_qualified_name() +
764  "' has already been added to an executor.");
765  }
766 
767  {
768  std::lock_guard lock{added_nodes_mutex_};
769  added_nodes.push_back(node_ptr);
770  }
771 
772  trigger_callback_group_sync();
773 }
774 
775 void
776 EventsCBGExecutor::add_node(const std::shared_ptr<rclcpp::Node> & node_ptr, bool notify)
777 {
778  add_node(node_ptr->get_node_base_interface(), notify);
779 }
780 
781 void
782 EventsCBGExecutor::remove_node(
783  const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr,
784  bool notify)
785 {
786  std::atomic_bool & has_executor = node_ptr->get_associated_with_executor_atomic();
787  if (!has_executor.exchange(false)) {
788  throw std::runtime_error(
789  std::string("Node '") + node_ptr->get_fully_qualified_name() +
790  "' needs to be associated with an executor.");
791  }
792 
793  {
794  std::lock_guard lock{added_nodes_mutex_};
795  added_nodes.erase(
796  std::remove_if(
797  added_nodes.begin(), added_nodes.end(), [&node_ptr](const auto & weak_ptr) {
798  const auto shr_ptr = weak_ptr.lock();
799  return shr_ptr && shr_ptr == node_ptr;
800  }), added_nodes.end());
801  }
802 
803  node_ptr->for_each_callback_group(
804  [this](const rclcpp::CallbackGroup::SharedPtr & cbg)
805  {
806  unregister_event_callbacks(cbg);
807  }
808  );
809 
810  node_ptr->get_shared_notify_guard_condition()->set_on_trigger_callback(
811  std::function<void(size_t)>());
812 
813  trigger_callback_group_sync();
814 
815  if (notify) {
816  scheduler->unblock_one_worker_thread();
817  // Interrupt waiting to handle new node
818  try {
819  interrupt_guard_condition_->trigger();
820  } catch (const rclcpp::exceptions::RCLError & ex) {
821  throw std::runtime_error(
822  std::string(
823  "Failed to trigger guard condition on callback group add: ") + ex.what() );
824  }
825  }
826 
827  node_ptr->get_associated_with_executor_atomic().store(false);
828 }
829 
830 void
831 EventsCBGExecutor::remove_node(const std::shared_ptr<rclcpp::Node> & node_ptr, bool notify)
832 {
833  remove_node(node_ptr->get_node_base_interface(), notify);
834 }
835 
836 // add a callback group to the executor, not bound to any node
837 void EventsCBGExecutor::add_callback_group_only(const rclcpp::CallbackGroup::SharedPtr & group_ptr)
838 {
839  add_callback_group(group_ptr, nullptr, true);
840 }
841 } // 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.