ros2_control - rolling
Loading...
Searching...
No Matches
async_function_handler.hpp
1// Copyright 2024 PAL Robotics S.L.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
16
17#ifndef REALTIME_TOOLS__ASYNC_FUNCTION_HANDLER_HPP_
18#define REALTIME_TOOLS__ASYNC_FUNCTION_HANDLER_HPP_
19
20#include <atomic>
21#include <cmath>
22#include <condition_variable>
23#include <functional>
24#include <limits>
25#include <memory>
26#include <mutex>
27#include <stdexcept>
28#include <string>
29#include <thread>
30#include <utility>
31#include <vector>
32
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"
38
40{
50{
51public:
52 enum Value : int8_t {
53 UNKNOWN = -1,
56 };
57
58 AsyncSchedulingPolicy() = default;
59 constexpr AsyncSchedulingPolicy(Value value) : value_(value) {} // NOLINT(runtime/explicit)
60 explicit AsyncSchedulingPolicy(const std::string & data_type)
61 {
62 if (data_type == "synchronized") {
63 value_ = SYNCHRONIZED;
64 } else if (data_type == "detached") {
65 value_ = DETACHED;
66 } else {
67 value_ = UNKNOWN;
68 }
69 }
70
71 operator Value() const { return value_; }
72
73 explicit operator bool() const = delete;
74
75 constexpr bool operator==(AsyncSchedulingPolicy other) const { return value_ == other.value_; }
76 constexpr bool operator!=(AsyncSchedulingPolicy other) const { return value_ != other.value_; }
77
78 constexpr bool operator==(Value other) const { return value_ == other; }
79 constexpr bool operator!=(Value other) const { return value_ != other; }
80
81 std::string to_string() const
82 {
83 switch (value_) {
84 case SYNCHRONIZED:
85 return "synchronized";
86 case DETACHED:
87 return "detached";
88 default:
89 return "unknown";
90 }
91 }
92
93 AsyncSchedulingPolicy from_string(const std::string & data_type)
94 {
95 return AsyncSchedulingPolicy(data_type);
96 }
97
98private:
99 Value value_ = UNKNOWN;
100};
101
128{
134 bool validate() const
135 {
136 if (thread_priority < 0 || thread_priority > 99) {
137 RCLCPP_ERROR(
138 logger, "Invalid thread priority: %d. It should be between 0 and 99.", thread_priority);
139 return false;
140 }
141 if (scheduling_policy == AsyncSchedulingPolicy::DETACHED) {
142 if (!clock) {
143 RCLCPP_ERROR(logger, "Clock must be set when using DETACHED scheduling policy.");
144 return false;
145 }
146 if (exec_rate == 0u) {
147 RCLCPP_ERROR(logger, "Execution rate must be set when using DETACHED scheduling policy.");
148 return false;
149 }
150 }
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'.");
155 }
156 if (trigger_predicate == nullptr) {
157 RCLCPP_ERROR(logger, "The parsed trigger predicate is not valid!");
158 return false;
159 }
160 for (const int & core : cpu_affinity_cores) {
161 if (core < 0) {
162 RCLCPP_ERROR(logger, "Invalid CPU core id: %d. It should be a non-negative integer.", core);
163 return false;
164 }
165 }
166 return true;
167 }
168
186 template <typename NodeT>
187 void initialize(NodeT & node, const std::string & prefix)
188 {
189 if (node->has_parameter(prefix + "thread_priority")) {
190 thread_priority = static_cast<int>(node->get_parameter(prefix + "thread_priority").as_int());
191 }
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));
197 }
198 }
199 if (node->has_parameter(prefix + "scheduling_policy")) {
200 scheduling_policy =
201 AsyncSchedulingPolicy(node->get_parameter(prefix + "scheduling_policy").as_string());
202 }
203 if (
204 scheduling_policy == AsyncSchedulingPolicy::DETACHED &&
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.");
211 }
212 exec_rate = static_cast<unsigned int>(execution_rate);
213 }
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();
217 }
218 if (node->has_parameter(prefix + "print_warnings")) {
219 print_warnings = node->get_parameter(prefix + "print_warnings").as_bool();
220 }
221 if (node->has_parameter(prefix + "thread_name")) {
222 thread_name = node->get_parameter(prefix + "thread_name").as_string();
223 }
224 }
225
226 int thread_priority = 50;
227 std::vector<int> cpu_affinity_cores = {};
228 AsyncSchedulingPolicy scheduling_policy = AsyncSchedulingPolicy::SYNCHRONIZED;
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 = "";
236};
237
243template <typename T>
245{
246public:
247 AsyncFunctionHandler() = default;
248
250
252
258 void init(
259 std::function<T(const rclcpp::Time &, const rclcpp::Duration &)> callback,
260 int thread_priority = 50)
261 {
262 if (callback == nullptr) {
263 throw std::runtime_error(
264 "AsyncFunctionHandler: parsed function to call asynchronously is not valid!");
265 }
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!");
270 }
271 async_function_ = callback;
272 thread_priority_ = thread_priority;
273 }
274
276
289 void init(
290 std::function<T(const rclcpp::Time &, const rclcpp::Duration &)> callback,
291 std::function<bool()> trigger_predicate, int thread_priority = 50)
292 {
293 if (trigger_predicate == nullptr) {
294 throw std::runtime_error("AsyncFunctionHandler: parsed trigger predicate is not valid!");
295 }
296 init(callback, thread_priority);
297 trigger_predicate_ = trigger_predicate;
298 }
299
300 void init(
301 std::function<T(const rclcpp::Time &, const rclcpp::Duration &)> callback,
302 const AsyncFunctionHandlerParams & params)
303 {
304 params.validate();
305 init(callback, params.trigger_predicate, params.thread_priority);
306 params_ = params;
307 pause_thread_ = params.wait_until_initial_trigger;
308 }
309
311
330 std::pair<bool, T> trigger_async_callback(
331 const rclcpp::Time & time, const rclcpp::Duration & period)
332 {
333 if (!is_initialized()) {
334 throw std::runtime_error("AsyncFunctionHandler: need to be initialized first!");
335 }
336 if (async_exception_ptr_) {
337 RCLCPP_ERROR(
338 params_.logger, "AsyncFunctionHandler: Exception caught in the async callback thread!");
339 std::rethrow_exception(async_exception_ptr_);
340 }
341 if (params_.scheduling_policy == AsyncSchedulingPolicy::DETACHED) {
342 RCLCPP_WARN_ONCE(
343 params_.logger,
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)) {
347 {
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);
354 }
355 async_callback_condition_.notify_one();
356 }
357 return std::make_pair(true, async_callback_return_.load(std::memory_order_relaxed));
358 }
359 if (!is_running()) {
360 throw std::runtime_error(
361 "AsyncFunctionHandler: need to start the async callback thread first before triggering!");
362 }
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_()) {
366 {
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;
371 }
372 async_callback_condition_.notify_one();
373 trigger_status = true;
374 }
375 const T return_value = async_callback_return_;
376 return std::make_pair(trigger_status, return_value);
377 }
378
380
383 T get_last_return_value() const { return async_callback_return_; }
384
386
389 const rclcpp::Time & get_current_callback_time() const { return current_callback_time_; }
390
392
395 const rclcpp::Duration & get_current_callback_period() const { return current_callback_period_; }
396
398
406 {
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;
415 }
416
418
422 {
423 if (is_running()) {
424 std::unique_lock<std::mutex> lock(async_mtx_);
425 cycle_end_condition_.wait(lock, [this] { return !trigger_in_progress_; });
426 return true;
427 }
428 return false;
429 }
430
432
440 {
441 RCLCPP_INFO_EXPRESSION(
442 params_.logger, !pause_thread_, "AsyncFunctionHandler: Pausing the async callback thread.");
443 if (params_.scheduling_policy == AsyncSchedulingPolicy::SYNCHRONIZED) {
444 pause_thread_ = true;
446 } else {
447 if (is_running()) {
448 pause_thread_.store(true, std::memory_order_relaxed);
449 std::unique_lock<std::mutex> lock(async_mtx_);
450 return true;
451 }
452 }
453 return pause_thread_.load(std::memory_order_relaxed);
454 }
455
457
460 bool is_initialized() const { return async_function_ && trigger_predicate_; }
461
463
468 {
469 if (is_running()) {
470 thread_.join();
471 }
472 }
473
475
478 bool is_running() const { return thread_.joinable(); }
479
481
484 bool is_stopped() const { return stop_async_callback_.load(std::memory_order_relaxed); }
485
487
490 bool is_paused() const { return pause_thread_.load(std::memory_order_relaxed); }
491
493
496 std::thread & get_thread() { return thread_; }
497
499
502 const std::thread & get_thread() const { return thread_; }
503
505
508 const AsyncFunctionHandlerParams & get_params() const { return params_; }
509
511
514 bool is_trigger_cycle_in_progress() const { return trigger_in_progress_; }
515
517
522 {
523 if (is_running()) {
524 {
525 stop_async_callback_.store(true, std::memory_order_relaxed);
526 std::unique_lock<std::mutex> lock(async_mtx_);
527 }
528 async_callback_condition_.notify_one();
529 thread_.join();
530 }
531 }
532
534
537 std::chrono::nanoseconds get_last_execution_time() const
538 {
539 return last_execution_time_.load(std::memory_order_relaxed);
540 }
541
543
549 {
550 if (!is_initialized()) {
551 throw std::runtime_error("AsyncFunctionHandler: need to be initialized first!");
552 }
553 if (!thread_.joinable()) {
555 thread_ = std::thread([this]() -> void {
556 if (!realtime_tools::configure_sched_fifo(thread_priority_)) {
557 RCLCPP_WARN(
558 params_.logger,
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] "
562 "for details.");
563 }
564 if (!params_.cpu_affinity_cores.empty()) {
565 const auto affinity_result =
566 realtime_tools::set_current_thread_affinity(params_.cpu_affinity_cores);
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!");
574 }
575 if (!params_.thread_name.empty()) {
576 const auto rename_result = realtime_tools::set_current_thread_name(params_.thread_name);
577
578 if (!rename_result.first) {
579 RCLCPP_WARN(
580 params_.logger, "Could not set thread name for the async worker thread. Error: %s",
581 rename_result.second.c_str());
582 } else {
583 RCLCPP_INFO(params_.logger, "%s", rename_result.second.c_str());
584 }
585 }
586 if (params_.scheduling_policy == AsyncSchedulingPolicy::SYNCHRONIZED) {
587 execute_synchronized_callback();
588 } else {
589 execute_detached_callback();
590 }
591 });
592 }
593 }
594
595private:
596 void execute_synchronized_callback()
597 {
598 while (!stop_async_callback_.load(std::memory_order_relaxed)) {
599 {
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();
605 try {
606 async_callback_return_ =
607 async_function_(current_callback_time_, current_callback_period_);
608 } catch (...) {
609 async_exception_ptr_ = std::current_exception();
610 }
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);
614 }
615 trigger_in_progress_ = false;
616 }
617 cycle_end_condition_.notify_all();
618 }
619 }
620
621 void execute_detached_callback()
622 {
623 if (!params_.clock) {
624 throw std::runtime_error(
625 "AsyncFunctionHandler: Clock must be set when using DETACHED scheduling policy.");
626 }
627 if (params_.exec_rate == 0u) {
628 throw std::runtime_error(
629 "AsyncFunctionHandler: Execution rate must be set when using DETACHED scheduling policy.");
630 }
631
632 auto const period = std::chrono::nanoseconds(1'000'000'000 / params_.exec_rate);
633
634 if (pause_thread_) {
635 std::unique_lock<std::mutex> lock(async_mtx_);
636 async_callback_condition_.wait(
637 lock, [this] { return !pause_thread_ || stop_async_callback_; });
638 }
639 // for calculating the measured period of the loop
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)) {
644 {
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_) {
649 // calculate measured period
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;
655
656 const auto start_time = std::chrono::steady_clock::now();
657 try {
658 async_callback_return_ = async_function_(current_time, measured_period);
659 } catch (...) {
660 async_exception_ptr_ = std::current_exception();
661 }
662 last_execution_time_ = std::chrono::duration_cast<std::chrono::nanoseconds>(
663 std::chrono::steady_clock::now() - start_time);
664
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);
678 }
679 next_iteration_time += (overrun_count * period);
680 }
681 std::this_thread::sleep_until(next_iteration_time);
682 }
683 trigger_in_progress_ = false;
684 }
685 cycle_end_condition_.notify_all();
686 }
687 }
688
689 rclcpp::Time current_callback_time_ = rclcpp::Time(0, 0, RCL_CLOCK_UNINITIALIZED);
690 rclcpp::Duration current_callback_period_{0, 0};
691
692 std::function<T(const rclcpp::Time &, const rclcpp::Duration &)> async_function_;
693 std::function<bool()> trigger_predicate_ = []() { return true; };
694
695 // Async related variables
696 std::thread thread_;
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_;
710};
711} // namespace realtime_tools
712
713#endif // REALTIME_TOOLS__ASYNC_FUNCTION_HANDLER_HPP_
Class to handle asynchronous function calls. AsyncFunctionHandler is a class that allows the user to ...
Definition async_function_handler.hpp:245
bool wait_for_trigger_cycle_to_finish()
Waits until the current async callback method trigger cycle is finished.
Definition async_function_handler.hpp:421
std::pair< bool, T > trigger_async_callback(const rclcpp::Time &time, const rclcpp::Duration &period)
Triggers the async callback method cycle.
Definition async_function_handler.hpp:330
std::thread & get_thread()
Get the async worker thread.
Definition async_function_handler.hpp:496
bool is_trigger_cycle_in_progress() const
Check if the async callback method is in progress.
Definition async_function_handler.hpp:514
std::chrono::nanoseconds get_last_execution_time() const
Get the last execution time of the async callback method.
Definition async_function_handler.hpp:537
const rclcpp::Time & get_current_callback_time() const
Get the current callback time.
Definition async_function_handler.hpp:389
void start_thread()
Initializes and starts the callback thread.
Definition async_function_handler.hpp:548
void init(std::function< T(const rclcpp::Time &, const rclcpp::Duration &)> callback, std::function< bool()> trigger_predicate, int thread_priority=50)
Initialize the AsyncFunctionHandler with the callback, trigger_predicate and thread_priority.
Definition async_function_handler.hpp:289
const AsyncFunctionHandlerParams & get_params() const
Get the parameters used to configure the AsyncFunctionHandler.
Definition async_function_handler.hpp:508
void reset_variables()
Resets the internal variables of the AsyncFunctionHandler.
Definition async_function_handler.hpp:405
bool is_running() const
Check if the async worker thread is running.
Definition async_function_handler.hpp:478
bool is_paused() const
Check if the async callback thread is paused.
Definition async_function_handler.hpp:490
bool pause_execution()
Pauses the execution of the async callback thread.
Definition async_function_handler.hpp:439
const rclcpp::Duration & get_current_callback_period() const
Get the current callback period.
Definition async_function_handler.hpp:395
void init(std::function< T(const rclcpp::Time &, const rclcpp::Duration &)> callback, int thread_priority=50)
Initialize the AsyncFunctionHandler with the callback and thread_priority.
Definition async_function_handler.hpp:258
void stop_thread()
Stops the callback thread.
Definition async_function_handler.hpp:521
void join_async_callback_thread()
Join the async callback thread.
Definition async_function_handler.hpp:467
bool is_stopped() const
Check if the async callback is triggered to stop the cycle.
Definition async_function_handler.hpp:484
bool is_initialized() const
Check if the AsyncFunctionHandler is initialized.
Definition async_function_handler.hpp:460
const std::thread & get_thread() const
Get the const version of async worker thread.
Definition async_function_handler.hpp:502
T get_last_return_value() const
Get the last return value of the async callback method.
Definition async_function_handler.hpp:383
Enum class to define the scheduling policy for the async worker thread. SYNCHRONIZED: The async worke...
Definition async_function_handler.hpp:50
Value
Definition async_function_handler.hpp:52
@ DETACHED
Synchronized scheduling policy.
Definition async_function_handler.hpp:55
@ SYNCHRONIZED
Unknown scheduling policy.
Definition async_function_handler.hpp:54
A pthread mutex wrapper that provides a mutex with the priority inheritance protocol and a priority c...
Definition async_function_handler.hpp:40
std::pair< bool, std::string > set_current_thread_name(const std::string &name)
Definition realtime_helpers.cpp:256
std::pair< bool, std::string > set_current_thread_affinity(int core)
Definition realtime_helpers.cpp:238
bool configure_sched_fifo(int priority)
Definition realtime_helpers.cpp:74
The AsyncFunctionHandlerParams struct is used to configure the AsyncFunctionHandler....
Definition async_function_handler.hpp:128
void initialize(NodeT &node, const std::string &prefix)
Initialize the parameters from a node's parameters. The node should have the following parameters:
Definition async_function_handler.hpp:187
bool validate() const
Validate the parameters.
Definition async_function_handler.hpp:134