17#ifndef REALTIME_TOOLS__ASYNC_FUNCTION_HANDLER_HPP_
18#define REALTIME_TOOLS__ASYNC_FUNCTION_HANDLER_HPP_
20#include <fmt/format.h>
21#include <fmt/ranges.h>
24#include <condition_variable>
35#include "rclcpp/clock.hpp"
36#include "rclcpp/duration.hpp"
37#include "rclcpp/logging.hpp"
38#include "rclcpp/time.hpp"
39#include "realtime_tools/realtime_helpers.hpp"
62 explicit AsyncSchedulingPolicy(
const std::string & data_type)
64 if (data_type ==
"synchronized") {
66 }
else if (data_type ==
"detached") {
73 operator Value()
const {
return value_; }
75 explicit operator bool()
const =
delete;
77 constexpr bool operator==(AsyncSchedulingPolicy other)
const {
return value_ == other.value_; }
78 constexpr bool operator!=(AsyncSchedulingPolicy other)
const {
return value_ != other.value_; }
80 constexpr bool operator==(
Value other)
const {
return value_ == other; }
81 constexpr bool operator!=(
Value other)
const {
return value_ != other; }
83 std::string to_string()
const
87 return "synchronized";
95 AsyncSchedulingPolicy from_string(
const std::string & data_type)
97 return AsyncSchedulingPolicy(data_type);
101 Value value_ = UNKNOWN;
138 if (thread_priority < 0 || thread_priority > 99) {
140 logger,
"Invalid thread priority: %d. It should be between 0 and 99.", thread_priority);
145 RCLCPP_ERROR(logger,
"Clock must be set when using DETACHED scheduling policy.");
148 if (exec_rate == 0u) {
149 RCLCPP_ERROR(logger,
"Execution rate must be set when using DETACHED scheduling policy.");
153 if (scheduling_policy == AsyncSchedulingPolicy::UNKNOWN) {
154 throw std::runtime_error(
155 "AsyncFunctionHandlerParams: scheduling policy is unknown. "
156 "Please set it to either 'synchronized' or 'detached'.");
158 if (trigger_predicate ==
nullptr) {
159 RCLCPP_ERROR(logger,
"The parsed trigger predicate is not valid!");
162 for (
const int & core : cpu_affinity_cores) {
164 RCLCPP_ERROR(logger,
"Invalid CPU core id: %d. It should be a non-negative integer.", core);
188 template <
typename NodeT>
191 if (node->has_parameter(prefix +
"thread_priority")) {
192 thread_priority =
static_cast<int>(node->get_parameter(prefix +
"thread_priority").as_int());
194 if (node->has_parameter(prefix +
"cpu_affinity")) {
195 const auto cpu_affinity_param =
196 node->get_parameter(prefix +
"cpu_affinity").as_integer_array();
197 for (
const auto & core : cpu_affinity_param) {
198 cpu_affinity_cores.push_back(
static_cast<int>(core));
201 if (node->has_parameter(prefix +
"scheduling_policy")) {
207 node->has_parameter(prefix +
"execution_rate")) {
208 const int execution_rate =
209 static_cast<int>(node->get_parameter(prefix +
"execution_rate").as_int());
210 if (execution_rate <= 0) {
211 throw std::runtime_error(
212 "AsyncFunctionHandler: execution_rate parameter must be positive.");
214 exec_rate =
static_cast<unsigned int>(execution_rate);
216 if (node->has_parameter(prefix +
"wait_until_initial_trigger")) {
217 wait_until_initial_trigger =
218 node->get_parameter(prefix +
"wait_until_initial_trigger").as_bool();
220 if (node->has_parameter(prefix +
"print_warnings")) {
221 print_warnings = node->get_parameter(prefix +
"print_warnings").as_bool();
223 if (node->has_parameter(prefix +
"thread_name")) {
224 thread_name = node->get_parameter(prefix +
"thread_name").as_string();
228 int thread_priority = 50;
229 std::vector<int> cpu_affinity_cores = {};
231 unsigned int exec_rate = 0u;
232 rclcpp::Clock::SharedPtr clock =
nullptr;
233 rclcpp::Logger logger = rclcpp::get_logger(
"AsyncFunctionHandler");
234 std::function<bool()> trigger_predicate = []() {
return true; };
235 bool wait_until_initial_trigger =
true;
236 bool print_warnings =
true;
237 std::string thread_name =
"";
261 std::function<T(
const rclcpp::Time &,
const rclcpp::Duration &)> callback,
262 int thread_priority = 50)
264 if (callback ==
nullptr) {
265 throw std::runtime_error(
266 "AsyncFunctionHandler: parsed function to call asynchronously is not valid!");
268 if (thread_.joinable()) {
269 throw std::runtime_error(
270 "AsyncFunctionHandler: Cannot reinitialize while the thread is "
271 "running. Please stop the async callback first!");
273 async_function_ = callback;
274 thread_priority_ = thread_priority;
292 std::function<T(
const rclcpp::Time &,
const rclcpp::Duration &)> callback,
293 std::function<
bool()> trigger_predicate,
int thread_priority = 50)
295 if (trigger_predicate ==
nullptr) {
296 throw std::runtime_error(
"AsyncFunctionHandler: parsed trigger predicate is not valid!");
298 init(callback, thread_priority);
299 trigger_predicate_ = trigger_predicate;
303 std::function<T(
const rclcpp::Time &,
const rclcpp::Duration &)> callback,
307 init(callback, params.trigger_predicate, params.thread_priority);
309 pause_thread_ = params.wait_until_initial_trigger;
333 const rclcpp::Time & time,
const rclcpp::Duration & period)
336 throw std::runtime_error(
"AsyncFunctionHandler: need to be initialized first!");
338 if (async_exception_ptr_) {
340 params_.logger,
"AsyncFunctionHandler: Exception caught in the async callback thread!");
341 std::rethrow_exception(async_exception_ptr_);
346 "AsyncFunctionHandler is configured with DETACHED scheduling policy. "
347 "This means that the async callback may not be synchronized with the main thread. ");
348 if (pause_thread_.load(std::memory_order_relaxed)) {
350 std::unique_lock<std::mutex> lock(async_mtx_);
351 pause_thread_ =
false;
352 RCLCPP_INFO(params_.logger,
"AsyncFunctionHandler: Resuming the async callback thread.");
353 async_callback_return_ = T();
354 auto const sync_period = std::chrono::nanoseconds(1'000'000'000 / params_.exec_rate);
355 previous_time_ = params_.clock->now() - rclcpp::Duration(sync_period);
357 async_callback_condition_.notify_one();
359 return std::make_pair(
true, async_callback_return_.load(std::memory_order_relaxed));
362 throw std::runtime_error(
363 "AsyncFunctionHandler: need to start the async callback thread first before triggering!");
365 std::unique_lock<std::mutex> lock(async_mtx_, std::try_to_lock);
366 bool trigger_status =
false;
367 if (lock.owns_lock() && !trigger_in_progress_ && trigger_predicate_()) {
369 std::unique_lock<std::mutex> scoped_lock(std::move(lock));
370 trigger_in_progress_ =
true;
371 current_callback_time_ = time;
372 current_callback_period_ = period;
374 async_callback_condition_.notify_one();
375 trigger_status =
true;
377 const T return_value = async_callback_return_;
378 return std::make_pair(trigger_status, return_value);
409 std::unique_lock<std::mutex> lock(async_mtx_);
410 stop_async_callback_ =
false;
411 trigger_in_progress_ =
false;
412 current_callback_time_ = rclcpp::Time(0, 0, RCL_CLOCK_UNINITIALIZED);
413 current_callback_period_ = rclcpp::Duration(0, 0);
414 last_execution_time_ = std::chrono::nanoseconds(0);
415 async_callback_return_ = T();
416 async_exception_ptr_ =
nullptr;
426 std::unique_lock<std::mutex> lock(async_mtx_);
427 cycle_end_condition_.wait(lock, [
this] {
return !trigger_in_progress_; });
443 RCLCPP_INFO_EXPRESSION(
444 params_.logger, !pause_thread_,
"AsyncFunctionHandler: Pausing the async callback thread.");
446 pause_thread_ =
true;
450 pause_thread_.store(
true, std::memory_order_relaxed);
451 std::unique_lock<std::mutex> lock(async_mtx_);
455 return pause_thread_.load(std::memory_order_relaxed);
486 bool is_stopped()
const {
return stop_async_callback_.load(std::memory_order_relaxed); }
492 bool is_paused()
const {
return pause_thread_.load(std::memory_order_relaxed); }
527 stop_async_callback_.store(
true, std::memory_order_relaxed);
528 std::unique_lock<std::mutex> lock(async_mtx_);
530 async_callback_condition_.notify_one();
541 return last_execution_time_.load(std::memory_order_relaxed);
553 throw std::runtime_error(
"AsyncFunctionHandler: need to be initialized first!");
555 if (!thread_.joinable()) {
557 thread_ = std::thread([
this]() ->
void {
561 "Could not enable FIFO RT scheduling policy. Consider setting up your user to do FIFO "
562 "RT scheduling. See "
563 "[https://control.ros.org/master/doc/ros2_control/controller_manager/doc/userdoc.html] "
566 if (!params_.cpu_affinity_cores.empty()) {
567 const auto affinity_result =
569 RCLCPP_WARN_EXPRESSION(
570 params_.logger, !affinity_result.first,
571 "Could not set CPU affinity for the async worker thread. Error: %s",
572 affinity_result.second.c_str());
574 std::string cores_list_print =
575 fmt::format(
"[{}]", fmt::join(params_.cpu_affinity_cores,
", "));
577 RCLCPP_WARN_EXPRESSION(
578 params_.logger, affinity_result.first,
579 "Async worker thread successfully pinned to Cores: %s", cores_list_print.c_str());
581 if (!params_.thread_name.empty()) {
584 if (!rename_result.first) {
586 params_.logger,
"Could not set thread name for the async worker thread. Error: %s",
587 rename_result.second.c_str());
589 RCLCPP_INFO(params_.logger,
"%s", rename_result.second.c_str());
593 execute_synchronized_callback();
595 execute_detached_callback();
602 void execute_synchronized_callback()
604 while (!stop_async_callback_.load(std::memory_order_relaxed)) {
606 std::unique_lock<std::mutex> lock(async_mtx_);
607 async_callback_condition_.wait(
608 lock, [
this] {
return trigger_in_progress_ || stop_async_callback_; });
609 if (!stop_async_callback_) {
610 const auto start_time = std::chrono::steady_clock::now();
612 async_callback_return_ =
613 async_function_(current_callback_time_, current_callback_period_);
615 async_exception_ptr_ = std::current_exception();
617 const auto end_time = std::chrono::steady_clock::now();
618 last_execution_time_ =
619 std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time);
621 trigger_in_progress_ =
false;
623 cycle_end_condition_.notify_all();
627 void execute_detached_callback()
629 if (!params_.clock) {
630 throw std::runtime_error(
631 "AsyncFunctionHandler: Clock must be set when using DETACHED scheduling policy.");
633 if (params_.exec_rate == 0u) {
634 throw std::runtime_error(
635 "AsyncFunctionHandler: Execution rate must be set when using DETACHED scheduling policy.");
638 auto const period = std::chrono::nanoseconds(1'000'000'000 / params_.exec_rate);
641 std::unique_lock<std::mutex> lock(async_mtx_);
642 async_callback_condition_.wait(
643 lock, [
this] {
return !pause_thread_ || stop_async_callback_; });
646 previous_time_ = params_.clock->now();
647 std::this_thread::sleep_for(period);
648 std::chrono::steady_clock::time_point next_iteration_time{std::chrono::steady_clock::now()};
649 while (!stop_async_callback_.load(std::memory_order_relaxed)) {
651 std::unique_lock<std::mutex> lock(async_mtx_);
652 async_callback_condition_.wait(
653 lock, [
this] {
return !pause_thread_ || stop_async_callback_; });
654 if (!stop_async_callback_) {
656 auto const current_time = params_.clock->now();
657 auto const measured_period = current_time - previous_time_;
658 previous_time_ = current_time;
659 current_callback_time_ = current_time;
660 current_callback_period_ = measured_period;
662 const auto start_time = std::chrono::steady_clock::now();
664 async_callback_return_ = async_function_(current_time, measured_period);
666 async_exception_ptr_ = std::current_exception();
668 last_execution_time_ = std::chrono::duration_cast<std::chrono::nanoseconds>(
669 std::chrono::steady_clock::now() - start_time);
671 next_iteration_time += period;
672 const auto time_now = std::chrono::steady_clock::now();
673 if (next_iteration_time < time_now) {
674 const double time_diff =
675 std::chrono::duration<double, std::milli>(time_now - next_iteration_time).count();
676 const double cm_period = 1.e3 /
static_cast<double>(params_.exec_rate);
677 const int overrun_count =
static_cast<int>(std::ceil(time_diff / cm_period));
678 if (params_.print_warnings) {
679 RCLCPP_WARN_THROTTLE(
680 params_.logger, *params_.clock, 1000,
681 "Overrun detected! The async callback missed its desired rate of %d Hz. The loop "
682 "took %f ms (missed cycles : %d).",
683 params_.exec_rate, time_diff + cm_period, overrun_count + 1);
685 next_iteration_time += (overrun_count * period);
687 std::this_thread::sleep_until(next_iteration_time);
689 trigger_in_progress_ =
false;
691 cycle_end_condition_.notify_all();
695 rclcpp::Time current_callback_time_ = rclcpp::Time(0, 0, RCL_CLOCK_UNINITIALIZED);
696 rclcpp::Duration current_callback_period_{0, 0};
698 std::function<T(
const rclcpp::Time &,
const rclcpp::Duration &)> async_function_;
699 std::function<bool()> trigger_predicate_ = []() {
return true; };
703 AsyncFunctionHandlerParams params_;
704 rclcpp::Time previous_time_{0, 0, RCL_CLOCK_UNINITIALIZED};
705 int thread_priority_ = std::numeric_limits<int>::quiet_NaN();
706 std::atomic_bool stop_async_callback_{
false};
707 std::atomic_bool trigger_in_progress_{
false};
708 std::atomic_bool pause_thread_{
false};
709 std::atomic<T> async_callback_return_;
710 std::condition_variable async_callback_condition_;
711 std::condition_variable cycle_end_condition_;
712 std::mutex async_mtx_;
713 std::atomic<std::chrono::nanoseconds> last_execution_time_;
714 std::atomic<double> periodicity_;
715 std::exception_ptr async_exception_ptr_;