21 #include "rcpputils/scope_exit.hpp"
22 #include "rclcpp/exceptions/exceptions.hpp"
23 #include "rclcpp/node.hpp"
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"
31 namespace rclcpp::executors
33 namespace cbg_executor
37 std::vector<cbg_executor::GuardConditionWithFunction> guard_conditions;
44 for (
const auto & gc_ref : guard_conditions) {
45 gc_ref.guard_condition->set_on_trigger_callback(
nullptr);
52 void add_guard_condition_event(
53 const rclcpp::GuardCondition::SharedPtr & ptr,
54 std::function<
void(
void)> fun)
56 guard_conditions.emplace_back(ptr, std::move(fun));
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();
74 guard_conditions.clear();
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;
86 next_exec_timeout_(next_exec_timeout),
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>() )
95 global_executable_cache->add_guard_condition_event (
96 interrupt_guard_condition_,
97 std::function<
void(
void)>() );
99 global_executable_cache->add_guard_condition_event(
100 shutdown_guard_condition_, [
this]() {
104 number_of_threads_ = number_of_threads > 0 ?
106 std::max(std::thread::hardware_concurrency(), 2U);
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();
112 strong_gc->trigger();
117 EventsCBGExecutor::~EventsCBGExecutor()
130 timer_manager->stop();
133 bool was_spinning = spinning;
139 scheduler->release_all_worker_threads();
142 remove_all_nodes_and_callback_groups();
145 std::scoped_lock l(callback_groups_mutex);
146 callback_groups.clear();
150 if (!context_->remove_on_shutdown_callback(shutdown_callback_handle_) ) {
151 RCUTILS_LOG_ERROR_NAMED(
153 "failed to remove registered on_shutdown callback");
159 timer_manager.reset();
162 void EventsCBGExecutor::remove_all_nodes_and_callback_groups()
164 std::vector<node_interfaces::NodeBaseInterface::WeakPtr> added_nodes_cpy;
166 std::lock_guard lock{added_nodes_mutex_};
167 added_nodes_cpy = added_nodes;
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();
177 std::vector<rclcpp::CallbackGroup::WeakPtr> added_cbgs_cpy;
179 std::lock_guard lock{added_callback_groups_mutex_};
180 added_cbgs_cpy = added_callback_groups;
183 for (
const auto & weak_ptr : added_cbgs_cpy) {
184 auto shr_ptr = weak_ptr.lock();
192 const std::chrono::time_point<std::chrono::steady_clock> & stop_time)
194 bool found_work =
false;
196 const uint64_t last_ready_id = cbg_executor::GlobalEventIdProvider::get_last_id();
199 auto ready_entity = scheduler->get_next_ready_entity(last_ready_id);
200 if(!ready_entity.entity) {
206 ready_entity.entity->execute_function();
208 scheduler->mark_entity_as_executed(*ready_entity.entity);
210 if(std::chrono::steady_clock::now() >= stop_time) {
220 EventsCBGExecutor::get_number_of_threads()
const
222 return number_of_threads_;
225 void EventsCBGExecutor::trigger_callback_group_sync()
231 needs_callback_group_resync =
true;
234 sync_callback_groups();
236 scheduler->unblock_one_worker_thread();
240 void EventsCBGExecutor::sync_callback_groups()
242 if (!needs_callback_group_resync.exchange(
false) ) {
246 std::scoped_lock l(callback_groups_mutex);
249 std::vector<std::pair<CallbackGroupData *, rclcpp::CallbackGroup::SharedPtr>> cur_group_data;
250 cur_group_data.reserve(callback_groups.size() );
252 for (CallbackGroupData & d : callback_groups) {
253 auto p = d.callback_group.lock();
255 cur_group_data.emplace_back(&d, std::move(p) );
259 std::vector<CallbackGroupData> next_group_data;
261 std::set<CallbackGroup *> added_cbgs;
264 [&cur_group_data, &next_group_data, &added_cbgs,
265 this](rclcpp::CallbackGroup::SharedPtr && cbg, CallbackGroupData::Origin origin) {
267 if (added_cbgs.find(cbg.get() ) != added_cbgs.end() ) {
271 added_cbgs.insert(cbg.get() );
273 for (
const auto & pair : cur_group_data) {
274 if (pair.second == cbg) {
275 next_group_data.push_back(std::move(*pair.first) );
277 next_group_data.back().registered_entities->regenerate_events();
282 CallbackGroupData new_entry{cbg,
283 std::make_unique<cbg_executor::RegisteredEntityCache>(*scheduler,
284 *timer_manager, cbg), origin};
285 new_entry.registered_entities->regenerate_events();
286 next_group_data.push_back(std::move(new_entry) );
290 std::vector<rclcpp::CallbackGroup::WeakPtr> added_cbgs_cpy;
292 std::lock_guard lock{added_callback_groups_mutex_};
293 added_cbgs_cpy = added_callback_groups;
296 std::vector<node_interfaces::NodeBaseInterface::WeakPtr> added_nodes_cpy;
298 std::lock_guard lock{added_nodes_mutex_};
299 added_nodes_cpy = added_nodes;
303 next_group_data.reserve(added_cbgs_cpy.size() + (added_nodes_cpy.size() * 3));
305 nodes_executable_cache->clear();
307 for (
const node_interfaces::NodeBaseInterface::WeakPtr & node_weak_ptr : added_nodes_cpy) {
308 auto node_ptr = node_weak_ptr.lock();
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);
319 nodes_executable_cache->add_guard_condition_event(
320 node_ptr->get_shared_notify_guard_condition(),
322 scheduler->trigger_sync();
327 for (
const rclcpp::CallbackGroup::WeakPtr & cbg : added_cbgs_cpy) {
330 insert_data(std::move(p), CallbackGroupData::Origin::ManualAdded);
337 callback_groups.swap(next_group_data);
341 EventsCBGExecutor::run(
size_t this_thread_number,
bool block_initially)
343 (void) this_thread_number;
345 while (
rclcpp::ok(this->context_) && spinning.load() ) {
346 if(block_initially) {
347 block_initially =
false;
348 scheduler->block_worker_thread();
351 sync_callback_groups();
353 auto ready_entity = scheduler->get_next_ready_entity();
354 if(!ready_entity.entity) {
355 scheduler->block_worker_thread();
359 if(ready_entity.moreEntitiesReady) {
360 scheduler->unblock_one_worker_thread();
363 ready_entity.entity->execute_function();
365 scheduler->mark_entity_as_executed(*ready_entity.entity);
370 EventsCBGExecutor::run(
371 size_t this_thread_number,
372 const std::function<
void(
const std::exception & e)> & exception_handler)
374 (void) this_thread_number;
376 while (
rclcpp::ok(this->context_) && spinning.load() ) {
377 sync_callback_groups();
379 auto ready_entity = scheduler->get_next_ready_entity();
380 if(!ready_entity.entity) {
381 scheduler->block_worker_thread();
386 ready_entity.entity->execute_function();
387 }
catch (
const std::exception & e) {
388 exception_handler(e);
391 scheduler->mark_entity_as_executed(*ready_entity.entity);
396 void EventsCBGExecutor::spin_once_internal(std::chrono::nanoseconds timeout)
398 if (!
rclcpp::ok(this->context_) || !spinning.load() ) {
402 sync_callback_groups();
404 auto ready_entity = scheduler->get_next_ready_entity();
405 if(!ready_entity.entity) {
406 if (timeout < std::chrono::nanoseconds::zero()) {
410 timeout = std::chrono::hours(10000);
413 scheduler->block_worker_thread_for(timeout);
415 ready_entity = scheduler->get_next_ready_entity();
417 if (!ready_entity.entity) {
422 ready_entity.entity->execute_function();
424 scheduler->mark_entity_as_executed(*ready_entity.entity);
430 if (spinning.exchange(
true) ) {
431 throw std::runtime_error(
"spin_once() called while already spinning");
433 RCPPUTILS_SCOPE_EXIT(this->spinning.store(
false); );
435 spin_once_internal(timeout);
447 if (max_duration < std::chrono::nanoseconds::zero() ) {
448 throw std::invalid_argument(
"max_duration must be greater than or equal to 0");
455 std::chrono::nanoseconds max_duration,
456 bool recollect_if_no_work_available)
458 if (spinning.exchange(
true) ) {
459 throw std::runtime_error(
"collect_and_execute_ready_events() called while already spinning");
461 RCPPUTILS_SCOPE_EXIT(this->spinning.store(
false); );
463 const auto start = std::chrono::steady_clock::now();
464 const auto end_time = start + max_duration;
465 auto cur_time = start;
467 bool had_work =
false;
469 while (
rclcpp::ok(this->context_) && spinning && cur_time <= end_time) {
470 sync_callback_groups();
478 if (!recollect_if_no_work_available) {
483 cur_time = std::chrono::steady_clock::now();
491 if (spinning.exchange(
true)) {
492 throw std::runtime_error(
"spin() called while already spinning");
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]()
500 run(thread_id,
true);
504 run(thread_id,
false);
505 for (
auto & thread : threads) {
511 const std::function<
void(
const std::exception & e)> & exception_handler)
513 if (spinning.exchange(
true) ) {
514 throw std::runtime_error(
"spin() called while already spinning");
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]()
522 run(thread_id, exception_handler);
527 run(thread_id, exception_handler);
528 for (
auto & thread : threads) {
535 rclcpp::CallbackGroup::SharedPtr group_ptr,
536 rclcpp::node_interfaces::NodeBaseInterface::SharedPtr ,
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.");
545 std::lock_guard lock{added_callback_groups_mutex_};
546 added_callback_groups.push_back(group_ptr);
548 trigger_callback_group_sync();
553 interrupt_guard_condition_->trigger();
555 throw std::runtime_error(
557 "Failed to trigger guard condition on callback group add: ") + ex.what() );
565 bool was_spinning = spinning;
567 spinning.store(
false);
570 scheduler->release_all_worker_threads();
574 interrupt_guard_condition_->trigger();
576 throw std::runtime_error(
577 std::string(
"Failed to trigger guard condition in cancel: ") + ex.what() );
581 std::vector<rclcpp::CallbackGroup::WeakPtr>
585 sync_callback_groups();
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);
596 std::vector<rclcpp::CallbackGroup::WeakPtr>
600 sync_callback_groups();
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);
613 std::vector<rclcpp::CallbackGroup::WeakPtr>
617 sync_callback_groups();
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);
630 void EventsCBGExecutor::unregister_event_callbacks(
const rclcpp::CallbackGroup::SharedPtr & cbg)
633 const auto remove_sub = [](
const rclcpp::SubscriptionBase::SharedPtr & s) {
634 s->clear_on_new_message_callback();
636 const auto remove_timer = [
this](
const rclcpp::TimerBase::SharedPtr & s) {
637 timer_manager->remove_timer(s);
640 const auto remove_client = [](
const rclcpp::ClientBase::SharedPtr & s) {
641 s->clear_on_new_response_callback();
644 const auto remove_service = [](
const rclcpp::ServiceBase::SharedPtr & s) {
645 s->clear_on_new_request_callback();
648 auto gc_ptr = cbg->get_notify_guard_condition();
650 gc_ptr->set_on_trigger_callback(std::function<
void(
size_t)>());
653 const auto remove_waitable = [](
const rclcpp::Waitable::SharedPtr & s) {
654 s->clear_on_ready_callback();
658 cbg->collect_all_ptrs(remove_sub, remove_service, remove_client, remove_timer, remove_waitable);
664 rclcpp::CallbackGroup::SharedPtr group_ptr,
667 if (!group_ptr->get_associated_with_executor_atomic().load()) {
668 throw std::runtime_error(
"Callback group needs to be associated with an executor.");
672 std::lock_guard lock{added_callback_groups_mutex_};
673 added_callback_groups.erase(
675 added_callback_groups.begin(), added_callback_groups.end(),
676 [&group_ptr, &found](
const auto & weak_ptr) {
677 auto shr_ptr = weak_ptr.lock();
682 if (group_ptr == shr_ptr) {
687 }), added_callback_groups.end() );
691 throw std::runtime_error(
"Callback group needs to be associated with this executor.");
695 unregister_event_callbacks(group_ptr);
698 trigger_callback_group_sync();
701 group_ptr->get_associated_with_executor_atomic().exchange(
false);
706 interrupt_guard_condition_->trigger();
708 throw std::runtime_error(
710 "Failed to trigger guard condition on callback group add: ") + ex.what() );
716 EventsCBGExecutor::add_node(
717 rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
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.");
729 std::lock_guard lock{added_nodes_mutex_};
730 added_nodes.push_back(node_ptr);
733 trigger_callback_group_sync();
737 EventsCBGExecutor::add_node(std::shared_ptr<rclcpp::Node> node_ptr,
bool notify)
739 add_node(node_ptr->get_node_base_interface(), notify);
743 EventsCBGExecutor::remove_node(
744 rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
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.");
755 std::lock_guard lock{added_nodes_mutex_};
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());
764 node_ptr->for_each_callback_group(
765 [
this](
const rclcpp::CallbackGroup::SharedPtr & cbg)
767 unregister_event_callbacks(cbg);
771 node_ptr->get_shared_notify_guard_condition()->set_on_trigger_callback(
772 std::function<
void(
size_t)>());
774 trigger_callback_group_sync();
777 scheduler->unblock_one_worker_thread();
780 interrupt_guard_condition_->trigger();
782 throw std::runtime_error(
784 "Failed to trigger guard condition on callback group add: ") + ex.what() );
788 node_ptr->get_associated_with_executor_atomic().store(
false);
792 EventsCBGExecutor::remove_node(std::shared_ptr<rclcpp::Node> node_ptr,
bool notify)
794 remove_node(node_ptr->get_node_base_interface(), notify);
798 void EventsCBGExecutor::add_callback_group_only(
const rclcpp::CallbackGroup::SharedPtr & group_ptr)
800 add_callback_group(group_ptr,
nullptr,
true);
Created when the return code does not match one of the other specialized exceptions.
RCLCPP_PUBLIC void remove_callback_group(rclcpp::CallbackGroup::SharedPtr group_ptr, bool notify=true) override
Remove a callback group from the executor.
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 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 add_callback_group(rclcpp::CallbackGroup::SharedPtr group_ptr, rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr, bool notify=true) override
Add a callback group to an executor.
RCLCPP_PUBLIC void remove_node(rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr, bool notify=true) override
Remove a node from the executor.
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 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 bool shutdown(rclcpp::Context::SharedPtr context=nullptr, const std::string &reason="user called rclcpp::shutdown()")
Shutdown rclcpp context, invalidating it for derived entities.
RCLCPP_PUBLIC bool ok(rclcpp::Context::SharedPtr context=nullptr)
Check rclcpp's status.
Options to be passed to the executor constructor.