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{.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) );
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;
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;
377 sync_callback_groups();
379 auto ready_entity = scheduler->get_next_ready_entity();
380 if(!ready_entity.entity) {
381 scheduler->block_worker_thread();
385 if(ready_entity.moreEntitiesReady) {
386 scheduler->unblock_one_worker_thread();
390 ready_entity.entity->execute_function();
391 }
catch (
const std::exception & e) {
392 exception_handler(e);
395 scheduler->mark_entity_as_executed(*ready_entity.entity);
400 void EventsCBGExecutor::spin_once_internal(std::chrono::nanoseconds timeout)
406 sync_callback_groups();
408 auto ready_entity = scheduler->get_next_ready_entity();
409 if(!ready_entity.entity) {
410 if (timeout < std::chrono::nanoseconds::zero()) {
414 timeout = std::chrono::hours(10000);
417 scheduler->block_worker_thread_for(timeout);
419 ready_entity = scheduler->get_next_ready_entity();
421 if (!ready_entity.entity) {
426 ready_entity.entity->execute_function();
428 scheduler->mark_entity_as_executed(*ready_entity.entity);
434 if (spinning.exchange(
true) ) {
435 throw std::runtime_error(
"spin_once() called while already spinning");
437 RCPPUTILS_SCOPE_EXIT(
438 this->spinning.store(
false);
439 this->cancel_requested_.store(
false););
444 spin_once_internal(timeout);
456 if (max_duration < std::chrono::nanoseconds::zero() ) {
457 throw std::invalid_argument(
"max_duration must be greater than or equal to 0");
464 std::chrono::nanoseconds max_duration,
465 bool recollect_if_no_work_available)
467 if (spinning.exchange(
true) ) {
468 throw std::runtime_error(
"collect_and_execute_ready_events() called while already spinning");
470 RCPPUTILS_SCOPE_EXIT(
471 this->spinning.store(
false);
472 this->cancel_requested_.store(
false););
477 const auto start = std::chrono::steady_clock::now();
478 const auto end_time = start + max_duration;
479 auto cur_time = start;
481 bool had_work =
false;
484 sync_callback_groups();
492 if (!recollect_if_no_work_available) {
497 cur_time = std::chrono::steady_clock::now();
505 if (spinning.exchange(
true)) {
506 throw std::runtime_error(
"spin() called while already spinning");
508 RCPPUTILS_SCOPE_EXIT(
509 this->spinning.store(
false);
510 this->cancel_requested_.store(
false););
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]()
519 run(thread_id,
true);
523 run(thread_id,
false);
524 for (
auto & thread : threads) {
530 const std::function<
void(
const std::exception & e)> & exception_handler)
532 if (spinning.exchange(
true) ) {
533 throw std::runtime_error(
"spin() called while already spinning");
535 RCPPUTILS_SCOPE_EXIT(
536 this->spinning.store(
false);
537 this->cancel_requested_.store(
false););
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]()
546 run(thread_id, exception_handler);
551 run(thread_id, exception_handler);
552 for (
auto & thread : threads) {
559 const rclcpp::CallbackGroup::SharedPtr & group_ptr,
560 const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & ,
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.");
569 std::lock_guard lock{added_callback_groups_mutex_};
570 added_callback_groups.push_back(group_ptr);
572 trigger_callback_group_sync();
577 interrupt_guard_condition_->trigger();
579 throw std::runtime_error(
581 "Failed to trigger guard condition on callback group add: ") + ex.what() );
596 scheduler->release_all_worker_threads();
600 interrupt_guard_condition_->trigger();
602 throw std::runtime_error(
603 std::string(
"Failed to trigger guard condition in cancel: ") + ex.what() );
607 std::vector<rclcpp::CallbackGroup::WeakPtr>
611 sync_callback_groups();
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);
622 std::vector<rclcpp::CallbackGroup::WeakPtr>
626 sync_callback_groups();
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);
639 std::vector<rclcpp::CallbackGroup::WeakPtr>
643 sync_callback_groups();
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);
656 void EventsCBGExecutor::unregister_event_callbacks(
const rclcpp::CallbackGroup::SharedPtr & cbg)
659 const auto remove_sub = [](
const rclcpp::SubscriptionBase::SharedPtr & s) {
660 s->clear_on_new_message_callback();
662 const auto remove_timer = [
this](
const rclcpp::TimerBase::SharedPtr & s) {
663 timer_manager->remove_timer(s);
666 const auto remove_client = [](
const rclcpp::ClientBase::SharedPtr & s) {
667 s->clear_on_new_response_callback();
670 const auto remove_service = [](
const rclcpp::ServiceBase::SharedPtr & s) {
671 s->clear_on_new_request_callback();
674 auto gc_ptr = cbg->get_notify_guard_condition();
676 gc_ptr->set_on_trigger_callback(std::function<
void(
size_t)>());
679 const auto remove_waitable = [](
const rclcpp::Waitable::SharedPtr & s) {
680 s->clear_on_ready_callback();
684 cbg->collect_all_ptrs(remove_sub, remove_service, remove_client, remove_timer, remove_waitable);
690 const rclcpp::CallbackGroup::SharedPtr & group_ptr,
693 if (!group_ptr->get_associated_with_executor_atomic().load()) {
694 throw std::runtime_error(
"Callback group needs to be associated with an executor.");
698 std::lock_guard lock{added_callback_groups_mutex_};
699 added_callback_groups.erase(
701 added_callback_groups.begin(), added_callback_groups.end(),
702 [&group_ptr, &found](
const auto & weak_ptr) {
703 auto shr_ptr = weak_ptr.lock();
708 if (group_ptr == shr_ptr) {
713 }), added_callback_groups.end() );
717 throw std::runtime_error(
"Callback group needs to be associated with this executor.");
721 unregister_event_callbacks(group_ptr);
724 trigger_callback_group_sync();
727 group_ptr->get_associated_with_executor_atomic().exchange(
false);
732 interrupt_guard_condition_->trigger();
734 throw std::runtime_error(
736 "Failed to trigger guard condition on callback group add: ") + ex.what() );
742 EventsCBGExecutor::add_node(
743 const 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(
true) ) {
749 throw std::runtime_error(
750 std::string(
"Node '") + node_ptr->get_fully_qualified_name() +
751 "' has already been added to an executor.");
755 std::lock_guard lock{added_nodes_mutex_};
756 added_nodes.push_back(node_ptr);
759 trigger_callback_group_sync();
763 EventsCBGExecutor::add_node(
const std::shared_ptr<rclcpp::Node> & node_ptr,
bool notify)
765 add_node(node_ptr->get_node_base_interface(), notify);
769 EventsCBGExecutor::remove_node(
770 const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr,
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.");
781 std::lock_guard lock{added_nodes_mutex_};
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());
790 node_ptr->for_each_callback_group(
791 [
this](
const rclcpp::CallbackGroup::SharedPtr & cbg)
793 unregister_event_callbacks(cbg);
797 node_ptr->get_shared_notify_guard_condition()->set_on_trigger_callback(
798 std::function<
void(
size_t)>());
800 trigger_callback_group_sync();
803 scheduler->unblock_one_worker_thread();
806 interrupt_guard_condition_->trigger();
808 throw std::runtime_error(
810 "Failed to trigger guard condition on callback group add: ") + ex.what() );
814 node_ptr->get_associated_with_executor_atomic().store(
false);
818 EventsCBGExecutor::remove_node(
const std::shared_ptr<rclcpp::Node> & node_ptr,
bool notify)
820 remove_node(node_ptr->get_node_base_interface(), notify);
824 void EventsCBGExecutor::add_callback_group_only(
const rclcpp::CallbackGroup::SharedPtr & group_ptr)
826 add_callback_group(group_ptr,
nullptr,
true);
std::atomic_bool cancel_requested_
Tracks a pending cancel request that has not yet been consumed by a spin.
Created when the return code does not match one of the other specialized exceptions.
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.