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.load(std::memory_order_acquire)) {
246 if (!needs_callback_group_resync.exchange(
false) ) {
250 std::scoped_lock l(callback_groups_mutex);
253 std::vector<std::pair<CallbackGroupData *, rclcpp::CallbackGroup::SharedPtr>> cur_group_data;
254 cur_group_data.reserve(callback_groups.size() );
256 for (CallbackGroupData & d : callback_groups) {
257 auto p = d.callback_group.lock();
259 cur_group_data.emplace_back(&d, std::move(p) );
263 std::vector<CallbackGroupData> next_group_data;
265 std::set<CallbackGroup *> added_cbgs;
268 [&cur_group_data, &next_group_data, &added_cbgs,
269 this](rclcpp::CallbackGroup::SharedPtr && cbg, CallbackGroupData::Origin origin) {
271 if (added_cbgs.find(cbg.get() ) != added_cbgs.end() ) {
275 added_cbgs.insert(cbg.get() );
277 for (
const auto & pair : cur_group_data) {
278 if (pair.second == cbg) {
279 next_group_data.push_back(std::move(*pair.first) );
281 next_group_data.back().registered_entities->regenerate_events();
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) );
294 std::vector<rclcpp::CallbackGroup::WeakPtr> added_cbgs_cpy;
296 std::lock_guard lock{added_callback_groups_mutex_};
297 added_cbgs_cpy = added_callback_groups;
300 std::vector<node_interfaces::NodeBaseInterface::WeakPtr> added_nodes_cpy;
302 std::lock_guard lock{added_nodes_mutex_};
303 added_nodes_cpy = added_nodes;
307 next_group_data.reserve(added_cbgs_cpy.size() + (added_nodes_cpy.size() * 3));
309 nodes_executable_cache->clear();
311 for (
const node_interfaces::NodeBaseInterface::WeakPtr & node_weak_ptr : added_nodes_cpy) {
312 auto node_ptr = node_weak_ptr.lock();
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);
323 nodes_executable_cache->add_guard_condition_event(
324 node_ptr->get_shared_notify_guard_condition(),
326 scheduler->trigger_sync();
331 for (
const rclcpp::CallbackGroup::WeakPtr & cbg : added_cbgs_cpy) {
334 insert_data(std::move(p), CallbackGroupData::Origin::ManualAdded);
341 callback_groups.swap(next_group_data);
345 EventsCBGExecutor::run(
size_t this_thread_number,
bool block_initially)
347 (void) this_thread_number;
349 cbg_executor::Worker worker;
352 if(block_initially) {
353 block_initially =
false;
354 scheduler->block_worker_thread(&worker);
357 sync_callback_groups();
359 auto ready_entity = scheduler->get_next_ready_entity();
360 if(!ready_entity.entity) {
361 scheduler->block_worker_thread(&worker);
365 if(ready_entity.moreEntitiesReady) {
366 scheduler->unblock_one_worker_thread();
369 ready_entity.entity->execute_function();
371 scheduler->suppress_thread_wakeup();
372 scheduler->mark_entity_as_executed(*ready_entity.entity);
377 EventsCBGExecutor::run(
378 size_t this_thread_number,
379 const std::function<
void(
const std::exception & e)> & exception_handler)
381 (void) this_thread_number;
382 cbg_executor::Worker worker;
385 sync_callback_groups();
387 auto ready_entity = scheduler->get_next_ready_entity();
388 if(!ready_entity.entity) {
389 scheduler->block_worker_thread(&worker);
393 if(ready_entity.moreEntitiesReady) {
394 scheduler->unblock_one_worker_thread();
398 ready_entity.entity->execute_function();
399 }
catch (
const std::exception & e) {
400 exception_handler(e);
403 scheduler->suppress_thread_wakeup();
404 scheduler->mark_entity_as_executed(*ready_entity.entity);
409 void EventsCBGExecutor::spin_once_internal(std::chrono::nanoseconds timeout)
415 sync_callback_groups();
417 auto ready_entity = scheduler->get_next_ready_entity();
418 if(!ready_entity.entity) {
419 if (timeout < std::chrono::nanoseconds::zero()) {
423 timeout = std::chrono::hours(10000);
426 cbg_executor::Worker worker;
430 scheduler->block_worker_thread_for(&worker, timeout);
432 ready_entity = scheduler->get_next_ready_entity();
434 if (!ready_entity.entity) {
439 ready_entity.entity->execute_function();
441 scheduler->mark_entity_as_executed(*ready_entity.entity);
447 if (spinning.exchange(
true) ) {
448 throw std::runtime_error(
"spin_once() called while already spinning");
450 RCPPUTILS_SCOPE_EXIT(
451 this->spinning.store(
false);
452 this->cancel_requested_.store(
false););
457 spin_once_internal(timeout);
469 if (max_duration < std::chrono::nanoseconds::zero() ) {
470 throw std::invalid_argument(
"max_duration must be greater than or equal to 0");
477 std::chrono::nanoseconds max_duration,
478 bool recollect_if_no_work_available)
480 if (spinning.exchange(
true) ) {
481 throw std::runtime_error(
"collect_and_execute_ready_events() called while already spinning");
483 RCPPUTILS_SCOPE_EXIT(
484 this->spinning.store(
false);
485 this->cancel_requested_.store(
false););
490 const auto start = std::chrono::steady_clock::now();
491 const auto end_time = start + max_duration;
492 auto cur_time = start;
494 bool had_work =
false;
497 sync_callback_groups();
505 if (!recollect_if_no_work_available) {
510 cur_time = std::chrono::steady_clock::now();
518 if (spinning.exchange(
true)) {
519 throw std::runtime_error(
"spin() called while already spinning");
521 RCPPUTILS_SCOPE_EXIT(
522 this->spinning.store(
false);
523 this->cancel_requested_.store(
false););
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]()
532 run(thread_id,
true);
536 run(thread_id,
false);
537 for (
auto & thread : threads) {
543 const std::function<
void(
const std::exception & e)> & exception_handler)
545 if (spinning.exchange(
true) ) {
546 throw std::runtime_error(
"spin() called while already spinning");
548 RCPPUTILS_SCOPE_EXIT(
549 this->spinning.store(
false);
550 this->cancel_requested_.store(
false););
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]()
559 run(thread_id, exception_handler);
564 run(thread_id, exception_handler);
565 for (
auto & thread : threads) {
572 const rclcpp::CallbackGroup::SharedPtr & group_ptr,
573 const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & ,
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.");
582 std::lock_guard lock{added_callback_groups_mutex_};
583 added_callback_groups.push_back(group_ptr);
585 trigger_callback_group_sync();
590 interrupt_guard_condition_->trigger();
592 throw std::runtime_error(
594 "Failed to trigger guard condition on callback group add: ") + ex.what() );
609 scheduler->release_all_worker_threads();
613 interrupt_guard_condition_->trigger();
615 throw std::runtime_error(
616 std::string(
"Failed to trigger guard condition in cancel: ") + ex.what() );
620 std::vector<rclcpp::CallbackGroup::WeakPtr>
624 sync_callback_groups();
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);
635 std::vector<rclcpp::CallbackGroup::WeakPtr>
639 sync_callback_groups();
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);
652 std::vector<rclcpp::CallbackGroup::WeakPtr>
656 sync_callback_groups();
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);
669 void EventsCBGExecutor::unregister_event_callbacks(
const rclcpp::CallbackGroup::SharedPtr & cbg)
672 const auto remove_sub = [](
const rclcpp::SubscriptionBase::SharedPtr & s) {
673 s->clear_on_new_message_callback();
675 const auto remove_timer = [
this](
const rclcpp::TimerBase::SharedPtr & s) {
676 timer_manager->remove_timer(s);
679 const auto remove_client = [](
const rclcpp::ClientBase::SharedPtr & s) {
680 s->clear_on_new_response_callback();
683 const auto remove_service = [](
const rclcpp::ServiceBase::SharedPtr & s) {
684 s->clear_on_new_request_callback();
687 auto gc_ptr = cbg->get_notify_guard_condition();
689 gc_ptr->set_on_trigger_callback(std::function<
void(
size_t)>());
692 const auto remove_waitable = [](
const rclcpp::Waitable::SharedPtr & s) {
693 s->clear_on_ready_callback();
697 cbg->collect_all_ptrs(remove_sub, remove_service, remove_client, remove_timer, remove_waitable);
703 const rclcpp::CallbackGroup::SharedPtr & group_ptr,
706 if (!group_ptr->get_associated_with_executor_atomic().load()) {
707 throw std::runtime_error(
"Callback group needs to be associated with an executor.");
711 std::lock_guard lock{added_callback_groups_mutex_};
712 added_callback_groups.erase(
714 added_callback_groups.begin(), added_callback_groups.end(),
715 [&group_ptr, &found](
const auto & weak_ptr) {
716 auto shr_ptr = weak_ptr.lock();
721 if (group_ptr == shr_ptr) {
726 }), added_callback_groups.end() );
730 throw std::runtime_error(
"Callback group needs to be associated with this executor.");
734 unregister_event_callbacks(group_ptr);
737 trigger_callback_group_sync();
740 group_ptr->get_associated_with_executor_atomic().exchange(
false);
745 interrupt_guard_condition_->trigger();
747 throw std::runtime_error(
749 "Failed to trigger guard condition on callback group add: ") + ex.what() );
755 EventsCBGExecutor::add_node(
756 const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr,
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.");
768 std::lock_guard lock{added_nodes_mutex_};
769 added_nodes.push_back(node_ptr);
772 trigger_callback_group_sync();
776 EventsCBGExecutor::add_node(
const std::shared_ptr<rclcpp::Node> & node_ptr,
bool notify)
778 add_node(node_ptr->get_node_base_interface(), notify);
782 EventsCBGExecutor::remove_node(
783 const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr & node_ptr,
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.");
794 std::lock_guard lock{added_nodes_mutex_};
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());
803 node_ptr->for_each_callback_group(
804 [
this](
const rclcpp::CallbackGroup::SharedPtr & cbg)
806 unregister_event_callbacks(cbg);
810 node_ptr->get_shared_notify_guard_condition()->set_on_trigger_callback(
811 std::function<
void(
size_t)>());
813 trigger_callback_group_sync();
816 scheduler->unblock_one_worker_thread();
819 interrupt_guard_condition_->trigger();
821 throw std::runtime_error(
823 "Failed to trigger guard condition on callback group add: ") + ex.what() );
827 node_ptr->get_associated_with_executor_atomic().store(
false);
831 EventsCBGExecutor::remove_node(
const std::shared_ptr<rclcpp::Node> & node_ptr,
bool notify)
833 remove_node(node_ptr->get_node_base_interface(), notify);
837 void EventsCBGExecutor::add_callback_group_only(
const rclcpp::CallbackGroup::SharedPtr & group_ptr)
839 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.