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