ROS 2 rclcpp + rcl - rolling  rolling-20536064
ROS 2 C++ Client Library with ROS Client Library
timer_manager.hpp
1 // Copyright 2024 Cellumation GmbH.
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 
15 #pragma once
16 
17 #include <rcl/timer.h>
18 
19 #include <chrono>
20 #include <functional>
21 #include <map>
22 #include <memory>
23 #include <utility>
24 #include <vector>
25 
26 #include <rclcpp/timer.hpp>
27 #include <rclcpp/utilities.hpp>
28 
29 namespace rclcpp::executors::cbg_executor
30 {
31 
40 {
41  std::mutex pred_mutex_;
42  bool shutdown_ = false;
43  rclcpp::Context::SharedPtr context_;
44  rclcpp::OnShutdownCallbackHandle shutdown_cb_handle_;
45  ClockWaiter::UniquePtr clock_;
46 
47 public:
48  explicit ClockConditionalVariable(rclcpp::Context::SharedPtr context)
49  :context_(std::move(context))
50  {
51  if (!context_ || !context_->is_valid()) {
52  throw std::runtime_error("context cannot be slept with because it's invalid");
53  }
54  // Wake this thread if the context is shutdown
55  shutdown_cb_handle_ = context_->add_on_shutdown_callback(
56  [this]() {
57  std::unique_lock lock(pred_mutex_);
58  shutdown_ = true;
59  if(clock_) {
60  clock_->notify_one();
61  }
62  });
63  }
64 
66  {
67  context_->remove_on_shutdown_callback(shutdown_cb_handle_);
68  }
69 
70  bool
71  wait_until(
72  std::unique_lock<std::mutex> & lock, const rclcpp::Clock::SharedPtr & clock,
73  const rclcpp::Time & until,
74  const std::function<bool ()> & pred)
75  {
76  if(lock.mutex() != &pred_mutex_) {
77  throw std::runtime_error(
78  "ClockConditionalVariable::wait_until: Internal error, given lock does not use"
79  " mutex returned by this->mutex()");
80  }
81 
82  if(shutdown_) {
83  return false;
84  }
85 
86  clock_ = std::make_unique<ClockWaiter>(clock);
87 
88  clock_->wait_until(lock, until, [this, &pred] () -> bool {
89  return shutdown_ || pred();
90  });
91 
92  clock_.reset();
93 
94  return true;
95  }
96 
97  void
98  notify_one()
99  {
100  std::unique_lock lock(pred_mutex_);
101 
102  if(clock_) {
103  clock_->notify_one();
104  }
105  }
106 
107  std::mutex &
108  mutex()
109  {
110  return pred_mutex_;
111  }
112 };
113 
122 {
123  struct TimerData
124  {
125  std::shared_ptr<const rcl_timer_t> rcl_ref;
126  std::weak_ptr<rclcpp::TimerBase> timer_ref;
127  rclcpp::Clock::SharedPtr clock;
128  bool in_running_list = false;
129  std::function<void(const std::function<void()> & executed_cb)> timer_ready_callback;
130  };
131 
132 public:
133  TimerQueue(rcl_clock_type_t timer_type, const rclcpp::Context::SharedPtr & context)
134  : timer_type(timer_type), clock_waiter(context)
135  {
136  // must be initialized here so that all class members
137  // are initialized
138  trigger_thread = std::thread([this]() {
139  timer_thread();
140  });
141  }
142 
143  ~TimerQueue()
144  {
145  stop();
146  }
147 
148  void stop()
149  {
150  running = false;
151  {
152  std::scoped_lock l(mutex);
153  wakeup_timer_thread();
154  }
155  if(trigger_thread.joinable()) {
156  trigger_thread.join();
157  }
158 
159  std::scoped_lock l(mutex);
160 
161  for (auto & tData : all_timers) {
162  if (auto shrPtr = tData->timer_ref.lock()) {
163  shrPtr->clear_on_reset_callback();
164  }
165  }
166  }
167 
177  void remove_timer(const rclcpp::TimerBase::SharedPtr & timer)
178  {
179  rcl_clock_t * clock_type_of_timer{};
180 
181  std::shared_ptr<const rcl_timer_t> handle = timer->get_timer_handle();
182 
183  if (rcl_timer_clock(
184  const_cast<rcl_timer_t *>(handle.get()),
185  &clock_type_of_timer) != RCL_RET_OK)
186  {
187  assert(false);
188  }
189 
190  if (clock_type_of_timer->type != timer_type) {
191  // timer is handled by another queue
192  return;
193  }
194 
195  std::scoped_lock l(mutex);
196 
197  // clear the timer under lock, as the underlying rcl function
198  // is not thread safe
199  timer->clear_on_reset_callback();
200 
201  auto it = std::find_if(
202  all_timers.begin(), all_timers.end(),
203  [rcl_ref = timer->get_timer_handle()](const std::unique_ptr<TimerData> & d)
204  {
205  return d->rcl_ref == rcl_ref;
206  });
207 
208  if (it != all_timers.end()) {
209  const TimerData * data_ptr = it->get();
210 
211  auto it2 = std::find_if(
212  running_timers.begin(), running_timers.end(), [data_ptr](const auto & e) {
213  return e.second == data_ptr;
214  });
215 
216  if(it2 != running_timers.end()) {
217  running_timers.erase(it2);
218  }
219  all_timers.erase(it);
220  }
221 
222  wakeup_timer_thread();
223  }
224 
234  void add_timer(
235  const rclcpp::TimerBase::SharedPtr & timer,
236  const std::function<void(const std::function<void()> executed_cb)> & timer_ready_callback)
237  {
238  rcl_clock_t * clock_type_of_timer{};
239 
240  std::shared_ptr<const rcl_timer_t> handle = timer->get_timer_handle();
241 
242  if (rcl_timer_clock(
243  const_cast<rcl_timer_t *>(handle.get()),
244  &clock_type_of_timer) != RCL_RET_OK)
245  {
246  assert(false);
247  }
248 
249  if (clock_type_of_timer->type != timer_type) {
250  // timer is handled by another queue
251  return;
252  }
253 
254  std::unique_ptr<TimerData> data = std::make_unique<TimerData>(TimerData{std::move(handle),
255  timer,
256  timer->get_clock(), false, timer_ready_callback});
257 
258  timer->set_on_reset_callback(
259  [data_ptr = data.get(), this](size_t) {
260  std::scoped_lock l(mutex);
261  if (!remove_if_dropped(data_ptr)) {
262  add_timer_to_running_map(data_ptr);
263  }
264  });
265 
266  {
267  std::scoped_lock l(mutex);
268  // this will wake up the timer thread if needed
269  add_timer_to_running_map(data.get());
270 
271  all_timers.emplace_back(std::move(data) );
272  }
273  }
274 
275 private:
280  void wakeup_timer_thread()
281  {
282  if(used_clock_for_timers) {
283  {
284  std::unique_lock<std::mutex> l(clock_waiter.mutex());
285  wake_up = true;
286  }
287  clock_waiter.notify_one();
288  } else {
289  thread_conditional.notify_all();
290  }
291  }
292 
299  bool remove_if_dropped(const TimerData * timer_data)
300  {
301  if (timer_data->rcl_ref.use_count() == 1) {
302  // clear on reset callback
303  if(rcl_timer_set_on_reset_callback(timer_data->rcl_ref.get(), nullptr,
304  nullptr) != RCL_RET_OK)
305  {
306  assert(false);
307  }
308 
309  // timer was deleted
310  auto it = std::find_if(
311  all_timers.begin(), all_timers.end(), [timer_data](const std::unique_ptr<TimerData> & e) {
312  return timer_data == e.get();
313  }
314  );
315 
316  if (it != all_timers.end()) {
317  all_timers.erase(it);
318  }
319  return true;
320  }
321  return false;
322  }
323 
331  void add_timer_to_running_map(TimerData * timer_data)
332  {
333  bool wasEmpty = running_timers.empty();
334  std::chrono::nanoseconds old_next_call_time(-1);
335  if(!wasEmpty) {
336  old_next_call_time = running_timers.begin()->first;
337  }
338 
339  // timer can already be in the running list, if
340  // e.g. reset was called on a running timer
341  if(timer_data->in_running_list) {
342  for(auto it = running_timers.begin() ; it != running_timers.end(); it++) {
343  if(it->second == timer_data) {
344  running_timers.erase(it);
345  break;
346  }
347  }
348  timer_data->in_running_list = false;
349  }
350 
351  int64_t next_call_time{};
352  rcl_ret_t ret = rcl_timer_get_next_call_time(timer_data->rcl_ref.get(), &next_call_time);
353 
354  if (ret != RCL_RET_OK) {
355  return;
356  }
357 
358  running_timers.emplace(next_call_time, timer_data);
359  timer_data->in_running_list = true;
360 
361  if(wasEmpty || running_timers.begin()->first < old_next_call_time) {
362  // the next wakeup is now earlier, wake up the timer thread so that it can pick up the timer
363  wakeup_timer_thread();
364  }
365  }
366 
367  std::vector<std::function<void()>> get_ready_timer_callbacks()
368  {
369  std::vector<std::function<void()>> ready_timer_callbacks;
370  ready_timer_callbacks.reserve(running_timers.size());
371  while (!running_timers.empty()) {
372  if(remove_if_dropped(running_timers.begin()->second)) {
373  running_timers.erase(running_timers.begin());
374  continue;
375  }
376 
377  int64_t time_until_call{};
378  TimerData *timer_data(running_timers.begin()->second);
379 
380  const rcl_timer_t * rcl_timer_ref = timer_data->rcl_ref.get();
381  auto ret = rcl_timer_get_time_until_next_call(rcl_timer_ref, &time_until_call);
382  if (ret == RCL_RET_TIMER_CANCELED) {
383  timer_data->in_running_list = false;
384  running_timers.erase(running_timers.begin());
385  continue;
386  }
387 
388  if (time_until_call <= 0) {
389  auto timer_done_callback = [timer_data = timer_data, this] ()
390  {
391  // Note, we have the guarantee, that the shared_ptr to this timer is
392  // valid in case this callback is executed, as the executor holds a
393  // reference to the timer during execution and at the time of this callback.
394  // Therefore timer_data is valid.
395  {
396  std::scoped_lock l(mutex);
397  add_timer_to_running_map(timer_data);
398  }
399  };
400 
401  ready_timer_callbacks.push_back([ready_callback =
402  timer_data->timer_ready_callback,
403  done_callback = std::move(timer_done_callback)] () {
404  ready_callback(done_callback);
405  });
406 
407  // remove timer from, running list, until it was executed
408  // the scheduler will readd the timer after execution
409  timer_data->in_running_list = false;
410  running_timers.erase(running_timers.begin());
411 
412  continue;
413  }
414  break;
415  }
416 
417  return ready_timer_callbacks;
418  }
419 
420  void timer_thread()
421  {
422  while (running && rclcpp::ok()) {
423  std::chrono::nanoseconds next_wakeup_time{};
424  std::vector<std::function<void()>> ready_timer_callbacks;
425  rclcpp::Clock::SharedPtr used_clock;
426  {
427  std::scoped_lock l(mutex);
428  ready_timer_callbacks = get_ready_timer_callbacks();
429 
430  if(running_timers.empty()) {
431  used_clock_for_timers.reset();
432  } else {
433  used_clock_for_timers = running_timers.begin()->second->clock;
434  next_wakeup_time = running_timers.begin()->first;
435  used_clock = used_clock_for_timers;
436  }
437  }
438 
439  for(const std::function<void()> & timer_ready_fun : ready_timer_callbacks) {
440  // inform the timer that it is ready. We need to do this out of the scope
441  // of the mutex, to avoid a deadlock, as the timer_ready function will need
442  // to acquire the callback group mutex
443  timer_ready_fun();
444  }
445 
446  if(used_clock) {
447  try {
448  used_clock->wait_until_started();
449 
450  std::unique_lock<std::mutex> l(clock_waiter.mutex());
451  clock_waiter.wait_until(l, used_clock,
452  rclcpp::Time(next_wakeup_time.count(), timer_type), [this] () -> bool {
453  return wake_up || !running || !rclcpp::ok();
454  });
455  wake_up = false;
456  } catch (const std::runtime_error &) {
457  // there is a race on shutdown, were the context may
458  // become invalid, while we call sleep_until
459  running = false;
460  }
461  } else {
462  std::unique_lock l(mutex);
463  thread_conditional.wait(l, [this]() {
464  return !running_timers.empty() || !running || !rclcpp::ok();
465  });
466  }
467  }
468  thread_terminated = true;
469  }
470 
471  rcl_clock_type_t timer_type;
472 
473  rclcpp::Clock::SharedPtr used_clock_for_timers;
474 
475  ClockConditionalVariable clock_waiter;
476  bool wake_up = false;
477 
478  std::mutex mutex;
479 
480  std::atomic_bool running = true;
481  std::atomic_bool thread_terminated = false;
482 
483  std::vector<std::unique_ptr<TimerData>> all_timers;
484 
485  using TimerMap = std::multimap<std::chrono::nanoseconds, TimerData *>;
486  TimerMap running_timers;
487 
488  std::thread trigger_thread;
489 
490  std::condition_variable thread_conditional;
491 };
492 
494 {
495  static constexpr size_t NUM_TYPES_OF_TIMERS = 3;
496  std::array<TimerQueue, NUM_TYPES_OF_TIMERS> timer_queues;
497 
498 public:
499  explicit TimerManager(const rclcpp::Context::SharedPtr & context)
500  : timer_queues{TimerQueue{RCL_ROS_TIME, context}, TimerQueue{RCL_SYSTEM_TIME, context},
501  TimerQueue{RCL_STEADY_TIME, context}}
502  {
503  }
504 
505  void remove_timer(const rclcpp::TimerBase::SharedPtr & timer)
506  {
507  for (TimerQueue & q : timer_queues) {
508  q.remove_timer(timer);
509  }
510  }
511 
512  void add_timer(
513  const rclcpp::TimerBase::SharedPtr & timer,
514  const std::function<void(const std::function<void()> executed_cb)> & timer_ready_callback)
515  {
516  for (TimerQueue & q : timer_queues) {
517  q.add_timer(timer, timer_ready_callback);
518  }
519  }
520 
521  void stop()
522  {
523  for (TimerQueue & q : timer_queues) {
524  q.stop();
525  }
526  }
527 };
528 } // namespace rclcpp::executors::cbg_executor
A class for managing a queue of timers.
void remove_timer(const rclcpp::TimerBase::SharedPtr &timer)
Removes a new timer from the queue. This function is thread safe.
void add_timer(const rclcpp::TimerBase::SharedPtr &timer, const std::function< void(const std::function< void()> executed_cb)> &timer_ready_callback)
Adds a new timer to the queue. This function is thread safe.
RCLCPP_PUBLIC bool ok(const rclcpp::Context::SharedPtr &context=rclcpp::contexts::get_global_default_context())
Check rclcpp's status.
Encapsulation of a time source.
Definition: time.h:138
Structure which encapsulates a ROS Timer.
Definition: timer.h:41
enum rcl_clock_type_e rcl_clock_type_t
Time source type, used to indicate the source of a time measurement.
@ RCL_ROS_TIME
Use ROS time.
Definition: time.h:66
@ RCL_SYSTEM_TIME
Use system time.
Definition: time.h:68
@ RCL_STEADY_TIME
Use a steady clock time.
Definition: time.h:70
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_timer_get_next_call_time(const rcl_timer_t *timer, int64_t *next_call_time)
Retrieve the time when the next call to rcl_timer_call() shall occur.
Definition: timer.c:321
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_timer_get_time_until_next_call(const rcl_timer_t *timer, int64_t *time_until_next_call)
Calculate and retrieve the time until the next call in nanoseconds.
Definition: timer.c:337
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_timer_set_on_reset_callback(const rcl_timer_t *timer, rcl_event_callback_t on_reset_callback, const void *user_data)
Set the on reset callback function for the timer.
Definition: timer.c:509
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_timer_clock(const rcl_timer_t *timer, rcl_clock_t **clock)
Retrieve the clock of the timer.
Definition: timer.c:228
#define RCL_RET_OK
Success return code.
Definition: types.h:27
#define RCL_RET_TIMER_CANCELED
Given timer was canceled return code.
Definition: types.h:95
rmw_ret_t rcl_ret_t
The type that holds an rcl return code.
Definition: types.h:24