ROS 2 rclcpp + rcl - lyrical  lyrical
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  spinning = false;
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_) && spinning.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_) && spinning.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_) || !spinning.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(this->spinning.store(false); );
434 
435  spin_once_internal(timeout);
436 }
437 
438 
439 void
440 EventsCBGExecutor::spin_some(std::chrono::nanoseconds max_duration)
441 {
442  collect_and_execute_ready_events(max_duration, false);
443 }
444 
445 void EventsCBGExecutor::spin_all(std::chrono::nanoseconds max_duration)
446 {
447  if (max_duration < std::chrono::nanoseconds::zero() ) {
448  throw std::invalid_argument("max_duration must be greater than or equal to 0");
449  }
450 
451  collect_and_execute_ready_events(max_duration, true);
452 }
453 
455  std::chrono::nanoseconds max_duration,
456  bool recollect_if_no_work_available)
457 {
458  if (spinning.exchange(true) ) {
459  throw std::runtime_error("collect_and_execute_ready_events() called while already spinning");
460  }
461  RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
462 
463  const auto start = std::chrono::steady_clock::now();
464  const auto end_time = start + max_duration;
465  auto cur_time = start;
466 
467  bool had_work = false;
468 
469  while (rclcpp::ok(this->context_) && spinning && cur_time <= end_time) {
470  sync_callback_groups();
471 
473  return had_work;
474  }
475 
476  had_work = true;
477 
478  if (!recollect_if_no_work_available) {
479  // we are done
480  return had_work;
481  }
482 
483  cur_time = std::chrono::steady_clock::now();
484  }
485 
486  return had_work;
487 }
488 void
490 {
491  if (spinning.exchange(true)) {
492  throw std::runtime_error("spin() called while already spinning");
493  }
494  RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
495  std::vector<std::thread> threads;
496  size_t thread_id = 0;
497  for ( ; thread_id < number_of_threads_ - 1; ++thread_id) {
498  threads.emplace_back([this, thread_id]()
499  {
500  run(thread_id, true);
501  });
502  }
503 
504  run(thread_id, false);
505  for (auto & thread : threads) {
506  thread.join();
507  }
508 }
509 
511  const std::function<void(const std::exception & e)> & exception_handler)
512 {
513  if (spinning.exchange(true) ) {
514  throw std::runtime_error("spin() called while already spinning");
515  }
516  RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
517  std::vector<std::thread> threads;
518  size_t thread_id = 0;
519  for ( ; thread_id < number_of_threads_ - 1; ++thread_id) {
520  threads.emplace_back([this, thread_id, exception_handler]()
521  {
522  run(thread_id, exception_handler);
523  }
524  );
525  }
526 
527  run(thread_id, exception_handler);
528  for (auto & thread : threads) {
529  thread.join();
530  }
531 }
532 
533 void
535  const rclcpp::CallbackGroup::SharedPtr & group_ptr,
536  const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & /*node_ptr*/,
537  bool notify)
538 {
539  std::atomic_bool & has_executor = group_ptr->get_associated_with_executor_atomic();
540  if (has_executor.exchange(true)) {
541  throw std::runtime_error("Callback group has already been added to an executor.");
542  }
543 
544  {
545  std::lock_guard lock{added_callback_groups_mutex_};
546  added_callback_groups.push_back(group_ptr);
547  }
548  trigger_callback_group_sync();
549 
550  if (notify) {
551  // Interrupt waiting to handle new node
552  try {
553  interrupt_guard_condition_->trigger();
554  } catch (const rclcpp::exceptions::RCLError & ex) {
555  throw std::runtime_error(
556  std::string(
557  "Failed to trigger guard condition on callback group add: ") + ex.what() );
558  }
559  }
560 }
561 
562 void
564 {
565  bool was_spinning = spinning;
566 
567  spinning.store(false);
568 
569  if(was_spinning) {
570  scheduler->release_all_worker_threads();
571  }
572 
573  try {
574  interrupt_guard_condition_->trigger();
575  } catch (const rclcpp::exceptions::RCLError & ex) {
576  throw std::runtime_error(
577  std::string("Failed to trigger guard condition in cancel: ") + ex.what() );
578  }
579 }
580 
581 std::vector<rclcpp::CallbackGroup::WeakPtr>
583 {
584  if (!spinning) {
585  sync_callback_groups();
586  }
587  std::lock_guard lock{callback_groups_mutex};
588  std::vector<rclcpp::CallbackGroup::WeakPtr> ret;
589  ret.reserve(callback_groups.size());
590  for( auto & cbg_data : callback_groups) {
591  ret.push_back(cbg_data.callback_group);
592  }
593  return ret;
594 }
595 
596 std::vector<rclcpp::CallbackGroup::WeakPtr>
598 {
599  if (!spinning) {
600  sync_callback_groups();
601  }
602  std::lock_guard lock{callback_groups_mutex};
603  std::vector<rclcpp::CallbackGroup::WeakPtr> ret;
604  ret.reserve(callback_groups.size());
605  for( auto & cbg_data : callback_groups) {
606  if(cbg_data.origin == CallbackGroupData::Origin::ManualAdded) {
607  ret.push_back(cbg_data.callback_group);
608  }
609  }
610  return ret;
611 }
612 
613 std::vector<rclcpp::CallbackGroup::WeakPtr>
615 {
616  if (!spinning) {
617  sync_callback_groups();
618  }
619  std::lock_guard lock{callback_groups_mutex};
620  std::vector<rclcpp::CallbackGroup::WeakPtr> ret;
621  ret.reserve(callback_groups.size());
622  for( auto & cbg_data : callback_groups) {
623  if(cbg_data.origin == CallbackGroupData::Origin::Node) {
624  ret.push_back(cbg_data.callback_group);
625  }
626  }
627  return ret;
628 }
629 
630 void EventsCBGExecutor::unregister_event_callbacks(const rclcpp::CallbackGroup::SharedPtr & cbg)
631 const
632 {
633  const auto remove_sub = [](const rclcpp::SubscriptionBase::SharedPtr & s) {
634  s->clear_on_new_message_callback();
635  };
636  const auto remove_timer = [this](const rclcpp::TimerBase::SharedPtr & s) {
637  timer_manager->remove_timer(s);
638  };
639 
640  const auto remove_client = [](const rclcpp::ClientBase::SharedPtr & s) {
641  s->clear_on_new_response_callback();
642  };
643 
644  const auto remove_service = [](const rclcpp::ServiceBase::SharedPtr & s) {
645  s->clear_on_new_request_callback();
646  };
647 
648  auto gc_ptr = cbg->get_notify_guard_condition();
649  if (gc_ptr) {
650  gc_ptr->set_on_trigger_callback(std::function<void(size_t)>());
651  }
652 
653  const auto remove_waitable = [](const rclcpp::Waitable::SharedPtr & s) {
654  s->clear_on_ready_callback();
655  };
656 
657 
658  cbg->collect_all_ptrs(remove_sub, remove_service, remove_client, remove_timer, remove_waitable);
659 }
660 
661 
662 void
664  const rclcpp::CallbackGroup::SharedPtr & group_ptr,
665  bool notify)
666 {
667  if (!group_ptr->get_associated_with_executor_atomic().load()) {
668  throw std::runtime_error("Callback group needs to be associated with an executor.");
669  }
670  bool found = false;
671  {
672  std::lock_guard lock{added_callback_groups_mutex_};
673  added_callback_groups.erase(
674  std::remove_if(
675  added_callback_groups.begin(), added_callback_groups.end(),
676  [&group_ptr, &found](const auto & weak_ptr) {
677  auto shr_ptr = weak_ptr.lock();
678  if (!shr_ptr) {
679  return true;
680  }
681 
682  if (group_ptr == shr_ptr) {
683  found = true;
684  return true;
685  }
686  return false;
687  }), added_callback_groups.end() );
688  }
689 
690  if(!found) {
691  throw std::runtime_error("Callback group needs to be associated with this executor.");
692  }
693 
694  // we need to unregister all callbacks
695  unregister_event_callbacks(group_ptr);
696 
697  if (found) {
698  trigger_callback_group_sync();
699  }
700 
701  group_ptr->get_associated_with_executor_atomic().exchange(false);
702 
703  if (notify) {
704  // Interrupt waiting to handle new node
705  try {
706  interrupt_guard_condition_->trigger();
707  } catch (const rclcpp::exceptions::RCLError & ex) {
708  throw std::runtime_error(
709  std::string(
710  "Failed to trigger guard condition on callback group add: ") + ex.what() );
711  }
712  }
713 }
714 
715 void
716 EventsCBGExecutor::add_node(
717  const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr,
718  bool /*notify*/)
719 {
720  // If the node already has an executor
721  std::atomic_bool & has_executor = node_ptr->get_associated_with_executor_atomic();
722  if (has_executor.exchange(true) ) {
723  throw std::runtime_error(
724  std::string("Node '") + node_ptr->get_fully_qualified_name() +
725  "' has already been added to an executor.");
726  }
727 
728  {
729  std::lock_guard lock{added_nodes_mutex_};
730  added_nodes.push_back(node_ptr);
731  }
732 
733  trigger_callback_group_sync();
734 }
735 
736 void
737 EventsCBGExecutor::add_node(const std::shared_ptr<rclcpp::Node> & node_ptr, bool notify)
738 {
739  add_node(node_ptr->get_node_base_interface(), notify);
740 }
741 
742 void
743 EventsCBGExecutor::remove_node(
744  const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr,
745  bool notify)
746 {
747  std::atomic_bool & has_executor = node_ptr->get_associated_with_executor_atomic();
748  if (!has_executor.exchange(false)) {
749  throw std::runtime_error(
750  std::string("Node '") + node_ptr->get_fully_qualified_name() +
751  "' needs to be associated with an executor.");
752  }
753 
754  {
755  std::lock_guard lock{added_nodes_mutex_};
756  added_nodes.erase(
757  std::remove_if(
758  added_nodes.begin(), added_nodes.end(), [&node_ptr](const auto & weak_ptr) {
759  const auto shr_ptr = weak_ptr.lock();
760  return shr_ptr && shr_ptr == node_ptr;
761  }), added_nodes.end());
762  }
763 
764  node_ptr->for_each_callback_group(
765  [this](const rclcpp::CallbackGroup::SharedPtr & cbg)
766  {
767  unregister_event_callbacks(cbg);
768  }
769  );
770 
771  node_ptr->get_shared_notify_guard_condition()->set_on_trigger_callback(
772  std::function<void(size_t)>());
773 
774  trigger_callback_group_sync();
775 
776  if (notify) {
777  scheduler->unblock_one_worker_thread();
778  // Interrupt waiting to handle new node
779  try {
780  interrupt_guard_condition_->trigger();
781  } catch (const rclcpp::exceptions::RCLError & ex) {
782  throw std::runtime_error(
783  std::string(
784  "Failed to trigger guard condition on callback group add: ") + ex.what() );
785  }
786  }
787 
788  node_ptr->get_associated_with_executor_atomic().store(false);
789 }
790 
791 void
792 EventsCBGExecutor::remove_node(const std::shared_ptr<rclcpp::Node> & node_ptr, bool notify)
793 {
794  remove_node(node_ptr->get_node_base_interface(), notify);
795 }
796 
797 // add a callback group to the executor, not bound to any node
798 void EventsCBGExecutor::add_callback_group_only(const rclcpp::CallbackGroup::SharedPtr & group_ptr)
799 {
800  add_callback_group(group_ptr, nullptr, true);
801 }
802 } // namespace rclcpp::executors
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.