17#ifndef REALTIME_TOOLS__ASYNC_FUNCTION_HANDLER_HPP_
18#define REALTIME_TOOLS__ASYNC_FUNCTION_HANDLER_HPP_
22#include <condition_variable>
33#include "rclcpp/clock.hpp"
34#include "rclcpp/duration.hpp"
35#include "rclcpp/logging.hpp"
36#include "rclcpp/time.hpp"
37#include "realtime_tools/realtime_helpers.hpp"
60 explicit AsyncSchedulingPolicy(
const std::string & data_type)
62 if (data_type ==
"synchronized") {
64 }
else if (data_type ==
"detached") {
71 operator Value()
const {
return value_; }
73 explicit operator bool()
const =
delete;
75 constexpr bool operator==(AsyncSchedulingPolicy other)
const {
return value_ == other.value_; }
76 constexpr bool operator!=(AsyncSchedulingPolicy other)
const {
return value_ != other.value_; }
78 constexpr bool operator==(
Value other)
const {
return value_ == other; }
79 constexpr bool operator!=(
Value other)
const {
return value_ != other; }
81 std::string to_string()
const
85 return "synchronized";
93 AsyncSchedulingPolicy from_string(
const std::string & data_type)
95 return AsyncSchedulingPolicy(data_type);
99 Value value_ = UNKNOWN;
136 if (thread_priority < 0 || thread_priority > 99) {
138 logger,
"Invalid thread priority: %d. It should be between 0 and 99.", thread_priority);
143 RCLCPP_ERROR(logger,
"Clock must be set when using DETACHED scheduling policy.");
146 if (exec_rate == 0u) {
147 RCLCPP_ERROR(logger,
"Execution rate must be set when using DETACHED scheduling policy.");
151 if (scheduling_policy == AsyncSchedulingPolicy::UNKNOWN) {
152 throw std::runtime_error(
153 "AsyncFunctionHandlerParams: scheduling policy is unknown. "
154 "Please set it to either 'synchronized' or 'detached'.");
156 if (trigger_predicate ==
nullptr) {
157 RCLCPP_ERROR(logger,
"The parsed trigger predicate is not valid!");
160 for (
const int & core : cpu_affinity_cores) {
162 RCLCPP_ERROR(logger,
"Invalid CPU core id: %d. It should be a non-negative integer.", core);
186 template <
typename NodeT>
189 if (node->has_parameter(prefix +
"thread_priority")) {
190 thread_priority =
static_cast<int>(node->get_parameter(prefix +
"thread_priority").as_int());
192 if (node->has_parameter(prefix +
"cpu_affinity")) {
193 const auto cpu_affinity_param =
194 node->get_parameter(prefix +
"cpu_affinity").as_integer_array();
195 for (
const auto & core : cpu_affinity_param) {
196 cpu_affinity_cores.push_back(
static_cast<int>(core));
199 if (node->has_parameter(prefix +
"scheduling_policy")) {
205 node->has_parameter(prefix +
"execution_rate")) {
206 const int execution_rate =
207 static_cast<int>(node->get_parameter(prefix +
"execution_rate").as_int());
208 if (execution_rate <= 0) {
209 throw std::runtime_error(
210 "AsyncFunctionHandler: execution_rate parameter must be positive.");
212 exec_rate =
static_cast<unsigned int>(execution_rate);
214 if (node->has_parameter(prefix +
"wait_until_initial_trigger")) {
215 wait_until_initial_trigger =
216 node->get_parameter(prefix +
"wait_until_initial_trigger").as_bool();
218 if (node->has_parameter(prefix +
"print_warnings")) {
219 print_warnings = node->get_parameter(prefix +
"print_warnings").as_bool();
221 if (node->has_parameter(prefix +
"thread_name")) {
222 thread_name = node->get_parameter(prefix +
"thread_name").as_string();
226 int thread_priority = 50;
227 std::vector<int> cpu_affinity_cores = {};
229 unsigned int exec_rate = 0u;
230 rclcpp::Clock::SharedPtr clock =
nullptr;
231 rclcpp::Logger logger = rclcpp::get_logger(
"AsyncFunctionHandler");
232 std::function<bool()> trigger_predicate = []() {
return true; };
233 bool wait_until_initial_trigger =
true;
234 bool print_warnings =
true;
235 std::string thread_name =
"";
259 std::function<T(
const rclcpp::Time &,
const rclcpp::Duration &)> callback,
260 int thread_priority = 50)
262 if (callback ==
nullptr) {
263 throw std::runtime_error(
264 "AsyncFunctionHandler: parsed function to call asynchronously is not valid!");
266 if (thread_.joinable()) {
267 throw std::runtime_error(
268 "AsyncFunctionHandler: Cannot reinitialize while the thread is "
269 "running. Please stop the async callback first!");
271 async_function_ = callback;
272 thread_priority_ = thread_priority;
290 std::function<T(
const rclcpp::Time &,
const rclcpp::Duration &)> callback,
291 std::function<
bool()> trigger_predicate,
int thread_priority = 50)
293 if (trigger_predicate ==
nullptr) {
294 throw std::runtime_error(
"AsyncFunctionHandler: parsed trigger predicate is not valid!");
296 init(callback, thread_priority);
297 trigger_predicate_ = trigger_predicate;
301 std::function<T(
const rclcpp::Time &,
const rclcpp::Duration &)> callback,
305 init(callback, params.trigger_predicate, params.thread_priority);
307 pause_thread_ = params.wait_until_initial_trigger;
331 const rclcpp::Time & time,
const rclcpp::Duration & period)
334 throw std::runtime_error(
"AsyncFunctionHandler: need to be initialized first!");
336 if (async_exception_ptr_) {
338 params_.logger,
"AsyncFunctionHandler: Exception caught in the async callback thread!");
339 std::rethrow_exception(async_exception_ptr_);
344 "AsyncFunctionHandler is configured with DETACHED scheduling policy. "
345 "This means that the async callback may not be synchronized with the main thread. ");
346 if (pause_thread_.load(std::memory_order_relaxed)) {
348 std::unique_lock<std::mutex> lock(async_mtx_);
349 pause_thread_ =
false;
350 RCLCPP_INFO(params_.logger,
"AsyncFunctionHandler: Resuming the async callback thread.");
351 async_callback_return_ = T();
352 auto const sync_period = std::chrono::nanoseconds(1'000'000'000 / params_.exec_rate);
353 previous_time_ = params_.clock->now() - rclcpp::Duration(sync_period);
355 async_callback_condition_.notify_one();
357 return std::make_pair(
true, async_callback_return_.load(std::memory_order_relaxed));
360 throw std::runtime_error(
361 "AsyncFunctionHandler: need to start the async callback thread first before triggering!");
363 std::unique_lock<std::mutex> lock(async_mtx_, std::try_to_lock);
364 bool trigger_status =
false;
365 if (lock.owns_lock() && !trigger_in_progress_ && trigger_predicate_()) {
367 std::unique_lock<std::mutex> scoped_lock(std::move(lock));
368 trigger_in_progress_ =
true;
369 current_callback_time_ = time;
370 current_callback_period_ = period;
372 async_callback_condition_.notify_one();
373 trigger_status =
true;
375 const T return_value = async_callback_return_;
376 return std::make_pair(trigger_status, return_value);
407 std::unique_lock<std::mutex> lock(async_mtx_);
408 stop_async_callback_ =
false;
409 trigger_in_progress_ =
false;
410 current_callback_time_ = rclcpp::Time(0, 0, RCL_CLOCK_UNINITIALIZED);
411 current_callback_period_ = rclcpp::Duration(0, 0);
412 last_execution_time_ = std::chrono::nanoseconds(0);
413 async_callback_return_ = T();
414 async_exception_ptr_ =
nullptr;
424 std::unique_lock<std::mutex> lock(async_mtx_);
425 cycle_end_condition_.wait(lock, [
this] {
return !trigger_in_progress_; });
441 RCLCPP_INFO_EXPRESSION(
442 params_.logger, !pause_thread_,
"AsyncFunctionHandler: Pausing the async callback thread.");
444 pause_thread_ =
true;
448 pause_thread_.store(
true, std::memory_order_relaxed);
449 std::unique_lock<std::mutex> lock(async_mtx_);
453 return pause_thread_.load(std::memory_order_relaxed);
484 bool is_stopped()
const {
return stop_async_callback_.load(std::memory_order_relaxed); }
490 bool is_paused()
const {
return pause_thread_.load(std::memory_order_relaxed); }
525 stop_async_callback_.store(
true, std::memory_order_relaxed);
526 std::unique_lock<std::mutex> lock(async_mtx_);
528 async_callback_condition_.notify_one();
539 return last_execution_time_.load(std::memory_order_relaxed);
551 throw std::runtime_error(
"AsyncFunctionHandler: need to be initialized first!");
553 if (!thread_.joinable()) {
555 thread_ = std::thread([
this]() ->
void {
559 "Could not enable FIFO RT scheduling policy. Consider setting up your user to do FIFO "
560 "RT scheduling. See "
561 "[https://control.ros.org/master/doc/ros2_control/controller_manager/doc/userdoc.html] "
564 if (!params_.cpu_affinity_cores.empty()) {
565 const auto affinity_result =
567 RCLCPP_WARN_EXPRESSION(
568 params_.logger, !affinity_result.first,
569 "Could not set CPU affinity for the async worker thread. Error: %s",
570 affinity_result.second.c_str());
571 RCLCPP_WARN_EXPRESSION(
572 params_.logger, affinity_result.first,
573 "Async worker thread is successfully pinned to the requested CPU cores!");
575 if (!params_.thread_name.empty()) {
578 if (!rename_result.first) {
580 params_.logger,
"Could not set thread name for the async worker thread. Error: %s",
581 rename_result.second.c_str());
583 RCLCPP_INFO(params_.logger,
"%s", rename_result.second.c_str());
587 execute_synchronized_callback();
589 execute_detached_callback();
596 void execute_synchronized_callback()
598 while (!stop_async_callback_.load(std::memory_order_relaxed)) {
600 std::unique_lock<std::mutex> lock(async_mtx_);
601 async_callback_condition_.wait(
602 lock, [
this] {
return trigger_in_progress_ || stop_async_callback_; });
603 if (!stop_async_callback_) {
604 const auto start_time = std::chrono::steady_clock::now();
606 async_callback_return_ =
607 async_function_(current_callback_time_, current_callback_period_);
609 async_exception_ptr_ = std::current_exception();
611 const auto end_time = std::chrono::steady_clock::now();
612 last_execution_time_ =
613 std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time);
615 trigger_in_progress_ =
false;
617 cycle_end_condition_.notify_all();
621 void execute_detached_callback()
623 if (!params_.clock) {
624 throw std::runtime_error(
625 "AsyncFunctionHandler: Clock must be set when using DETACHED scheduling policy.");
627 if (params_.exec_rate == 0u) {
628 throw std::runtime_error(
629 "AsyncFunctionHandler: Execution rate must be set when using DETACHED scheduling policy.");
632 auto const period = std::chrono::nanoseconds(1'000'000'000 / params_.exec_rate);
635 std::unique_lock<std::mutex> lock(async_mtx_);
636 async_callback_condition_.wait(
637 lock, [
this] {
return !pause_thread_ || stop_async_callback_; });
640 previous_time_ = params_.clock->now();
641 std::this_thread::sleep_for(period);
642 std::chrono::steady_clock::time_point next_iteration_time{std::chrono::steady_clock::now()};
643 while (!stop_async_callback_.load(std::memory_order_relaxed)) {
645 std::unique_lock<std::mutex> lock(async_mtx_);
646 async_callback_condition_.wait(
647 lock, [
this] {
return !pause_thread_ || stop_async_callback_; });
648 if (!stop_async_callback_) {
650 auto const current_time = params_.clock->now();
651 auto const measured_period = current_time - previous_time_;
652 previous_time_ = current_time;
653 current_callback_time_ = current_time;
654 current_callback_period_ = measured_period;
656 const auto start_time = std::chrono::steady_clock::now();
658 async_callback_return_ = async_function_(current_time, measured_period);
660 async_exception_ptr_ = std::current_exception();
662 last_execution_time_ = std::chrono::duration_cast<std::chrono::nanoseconds>(
663 std::chrono::steady_clock::now() - start_time);
665 next_iteration_time += period;
666 const auto time_now = std::chrono::steady_clock::now();
667 if (next_iteration_time < time_now) {
668 const double time_diff =
669 std::chrono::duration<double, std::milli>(time_now - next_iteration_time).count();
670 const double cm_period = 1.e3 /
static_cast<double>(params_.exec_rate);
671 const int overrun_count =
static_cast<int>(std::ceil(time_diff / cm_period));
672 if (params_.print_warnings) {
673 RCLCPP_WARN_THROTTLE(
674 params_.logger, *params_.clock, 1000,
675 "Overrun detected! The async callback missed its desired rate of %d Hz. The loop "
676 "took %f ms (missed cycles : %d).",
677 params_.exec_rate, time_diff + cm_period, overrun_count + 1);
679 next_iteration_time += (overrun_count * period);
681 std::this_thread::sleep_until(next_iteration_time);
683 trigger_in_progress_ =
false;
685 cycle_end_condition_.notify_all();
689 rclcpp::Time current_callback_time_ = rclcpp::Time(0, 0, RCL_CLOCK_UNINITIALIZED);
690 rclcpp::Duration current_callback_period_{0, 0};
692 std::function<T(
const rclcpp::Time &,
const rclcpp::Duration &)> async_function_;
693 std::function<bool()> trigger_predicate_ = []() {
return true; };
697 AsyncFunctionHandlerParams params_;
698 rclcpp::Time previous_time_{0, 0, RCL_CLOCK_UNINITIALIZED};
699 int thread_priority_ = std::numeric_limits<int>::quiet_NaN();
700 std::atomic_bool stop_async_callback_{
false};
701 std::atomic_bool trigger_in_progress_{
false};
702 std::atomic_bool pause_thread_{
false};
703 std::atomic<T> async_callback_return_;
704 std::condition_variable async_callback_condition_;
705 std::condition_variable cycle_end_condition_;
706 std::mutex async_mtx_;
707 std::atomic<std::chrono::nanoseconds> last_execution_time_;
708 std::atomic<double> periodicity_;
709 std::exception_ptr async_exception_ptr_;