ROS 2 rclcpp + rcl - rolling  rolling-6c35dd21
ROS 2 C++ Client Library with ROS Client Library
timers_manager.cpp
1 // Copyright 2023 iRobot Corporation.
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 #include "rclcpp/experimental/timers_manager.hpp"
16 
17 #include <inttypes.h>
18 
19 #include <ctime>
20 #include <iostream>
21 #include <memory>
22 #include <stdexcept>
23 
24 #include "rclcpp/utilities.hpp"
25 #include "rcpputils/scope_exit.hpp"
26 
28 
29 TimersManager::TimersManager(
30  std::shared_ptr<rclcpp::Context> context,
31  std::function<void(const rclcpp::TimerBase *, const std::shared_ptr<void> &)> on_ready_callback)
32 : on_ready_callback_(on_ready_callback),
33  context_(std::move(context))
34 {
35 }
36 
38 {
39  // Remove all timers
40  this->clear();
41 
42  // Make sure timers thread is stopped before destroying this object
43  this->stop();
44 }
45 
46 void TimersManager::add_timer(const rclcpp::TimerBase::SharedPtr & timer)
47 {
48  if (!timer) {
49  throw std::invalid_argument("TimersManager::add_timer() trying to add nullptr timer");
50  }
51 
52  bool added = false;
53  {
54  std::unique_lock<std::mutex> lock(timers_mutex_);
55  added = weak_timers_heap_.add_timer(timer);
56  timers_updated_ = timers_updated_ || added;
57  }
58 
59  timer->set_on_reset_callback(
60  [this](size_t arg) {
61  {
62  (void)arg;
63  std::unique_lock<std::mutex> lock(timers_mutex_);
64  timers_updated_ = true;
65  }
66  timers_cv_.notify_one();
67  });
68 
69  if (added) {
70  // Notify that a timer has been added
71  timers_cv_.notify_one();
72  }
73 }
74 
76 {
77  // Make sure that the thread is not already running
78  if (running_.exchange(true)) {
79  throw std::runtime_error("TimersManager::start() can't start timers thread as already running");
80  }
81 
82  timers_thread_ = std::thread(&TimersManager::run_timers, this);
83 }
84 
86 {
87  // Lock stop() function to prevent race condition in destructor
88  std::unique_lock<std::mutex> lock(stop_mutex_);
89  running_ = false;
90 
91  // Notify the timers manager thread to wake up
92  {
93  std::unique_lock<std::mutex> lock(timers_mutex_);
94  timers_updated_ = true;
95  }
96  timers_cv_.notify_one();
97 
98  // Join timers thread if it's running
99  if (timers_thread_.joinable()) {
100  timers_thread_.join();
101  }
102 }
103 
104 std::optional<std::chrono::nanoseconds> TimersManager::get_head_timeout()
105 {
106  // Do not allow to interfere with the thread running
107  if (running_) {
108  throw std::runtime_error(
109  "get_head_timeout() can't be used while timers thread is running");
110  }
111 
112  std::unique_lock<std::mutex> lock(timers_mutex_);
113  return this->get_head_timeout_unsafe();
114 }
115 
117 {
118  // Do not allow to interfere with the thread running
119  if (running_) {
120  throw std::runtime_error(
121  "get_number_ready_timers() can't be used while timers thread is running");
122  }
123 
124  std::unique_lock<std::mutex> lock(timers_mutex_);
125  TimersHeap locked_heap = weak_timers_heap_.validate_and_lock();
126  return locked_heap.get_number_ready_timers();
127 }
128 
130 {
131  // Do not allow to interfere with the thread running
132  if (running_) {
133  throw std::runtime_error(
134  "execute_head_timer() can't be used while timers thread is running");
135  }
136 
137  std::unique_lock<std::mutex> lock(timers_mutex_);
138 
139  TimersHeap timers_heap = weak_timers_heap_.validate_and_lock();
140 
141  // Nothing to do if we don't have any timer
142  if (timers_heap.empty()) {
143  return false;
144  }
145 
146  TimerPtr head_timer = timers_heap.front();
147 
148  const bool timer_ready = head_timer->is_ready();
149  if (timer_ready) {
150  // NOTE: here we always execute the timer, regardless of whether the
151  // on_ready_callback is set or not.
152  auto data = head_timer->call();
153  if (!data) {
154  // someone canceled the timer between is_ready and call
155  return false;
156  }
157  head_timer->execute_callback(data);
158  timers_heap.heapify_root();
159  weak_timers_heap_.store(timers_heap);
160  }
161 
162  return timer_ready;
163 }
164 
166  const rclcpp::TimerBase * timer_id,
167  const std::shared_ptr<void> & data)
168 {
169  TimerPtr ready_timer;
170  {
171  std::unique_lock<std::mutex> lock(timers_mutex_);
172  ready_timer = weak_timers_heap_.get_timer(timer_id);
173  }
174  if (ready_timer) {
175  ready_timer->execute_callback(data);
176  }
177 }
178 
179 std::optional<std::chrono::nanoseconds> TimersManager::get_head_timeout_unsafe()
180 {
181  // If we don't have any weak pointer, then we just return maximum timeout
182  if (weak_timers_heap_.empty()) {
183  return std::chrono::nanoseconds::max();
184  }
185  // Weak heap is not empty, so try to lock the first element.
186  // If it is still a valid pointer, it is guaranteed to be the correct head
187  TimerPtr head_timer = weak_timers_heap_.front().lock();
188 
189  if (!head_timer) {
190  // The first element has expired, we can't make other assumptions on the heap
191  // and we need to entirely validate it.
192  TimersHeap locked_heap = weak_timers_heap_.validate_and_lock();
193  // NOTE: the following operations will not modify any element in the heap, so we
194  // don't have to call `weak_timers_heap_.store(locked_heap)` at the end.
195 
196  if (locked_heap.empty()) {
197  return std::chrono::nanoseconds::max();
198  }
199  head_timer = locked_heap.front();
200  }
201  if (head_timer->is_canceled()) {
202  return std::nullopt;
203  }
204  return head_timer->time_until_trigger();
205 }
206 
207 void TimersManager::execute_ready_timers_unsafe()
208 {
209  // We start by locking the timers
210  TimersHeap locked_heap = weak_timers_heap_.validate_and_lock();
211 
212  // Nothing to do if we don't have any timer
213  if (locked_heap.empty()) {
214  return;
215  }
216 
217  // Keep executing timers until they are ready and they were already ready when we started.
218  // The two checks prevent this function from blocking indefinitely if the
219  // time required for executing the timers is longer than their period.
220 
221  TimerPtr head_timer = locked_heap.front();
222  const size_t number_ready_timers = locked_heap.get_number_ready_timers();
223  size_t executed_timers = 0;
224  while (executed_timers < number_ready_timers && head_timer->is_ready()) {
225  auto data = head_timer->call();
226  if (data) {
227  if (on_ready_callback_) {
228  on_ready_callback_(head_timer.get(), data);
229  } else {
230  head_timer->execute_callback(data);
231  }
232  } else {
233  // someone canceled the timer between is_ready and call
234  // we don't do anything, as the timer is now 'processed'
235  }
236 
237  executed_timers++;
238  // Executing a timer will result in updating its time_until_trigger, so re-heapify
239  locked_heap.heapify_root();
240  // Get new head timer
241  head_timer = locked_heap.front();
242  }
243 
244  // After having performed work on the locked heap we reflect the changes to weak one.
245  // Timers will be already sorted the next time we need them if none went out of scope.
246  weak_timers_heap_.store(locked_heap);
247 }
248 
249 void TimersManager::run_timers()
250 {
251  // Make sure the running flag is set to false when we exit from this function
252  // to allow restarting the timers thread.
253  RCPPUTILS_SCOPE_EXIT(this->running_.store(false); );
254 
255  while (rclcpp::ok(context_) && running_) {
256  // Lock mutex
257  std::unique_lock<std::mutex> lock(timers_mutex_);
258 
259  std::optional<std::chrono::nanoseconds> time_to_sleep = get_head_timeout_unsafe();
260 
261  // If head timer was cancelled, try to reheap and get a new head.
262  // This avoids an edge condition where head timer is cancelled, but other
263  // valid timers remain in the heap.
264  if (!time_to_sleep.has_value()) {
265  // Re-heap to (possibly) move cancelled timer from head of heap. If
266  // entire heap is cancelled, this will still result in a nullopt.
267  TimersHeap locked_heap = weak_timers_heap_.validate_and_lock();
268  locked_heap.heapify();
269  weak_timers_heap_.store(locked_heap);
270  time_to_sleep = get_head_timeout_unsafe();
271  }
272 
273  // If no timers, or all timers cancelled, wait for an update.
274  if (!time_to_sleep.has_value() || (time_to_sleep.value() == std::chrono::nanoseconds::max()) ) {
275  // Wait until notification that timers have been updated
276  timers_cv_.wait(lock, [this]() {return timers_updated_;});
277 
278  // Re-heap in case ordering changed due to a cancelled timer
279  // re-activating.
280  TimersHeap locked_heap = weak_timers_heap_.validate_and_lock();
281  locked_heap.heapify();
282  weak_timers_heap_.store(locked_heap);
283  } else if (time_to_sleep.value() != std::chrono::nanoseconds::zero()) {
284  // If time_to_sleep is zero, we immediately execute. Otherwise, wait
285  // until timeout or notification that timers have been updated
286  timers_cv_.wait_for(lock, time_to_sleep.value(), [this]() {return timers_updated_;});
287  }
288 
289  // Reset timers updated flag
290  timers_updated_ = false;
291 
292  // Execute timers
293  this->execute_ready_timers_unsafe();
294  }
295 }
296 
298 {
299  {
300  // Lock mutex and then clear all data structures
301  std::unique_lock<std::mutex> lock(timers_mutex_);
302 
303  TimersHeap locked_heap = weak_timers_heap_.validate_and_lock();
304  locked_heap.clear_timers_on_reset_callbacks();
305 
306  weak_timers_heap_.clear();
307 
308  timers_updated_ = true;
309  }
310 
311  // Notify timers thread such that it can re-compute its timeout
312  timers_cv_.notify_one();
313 }
314 
315 void TimersManager::remove_timer(const TimerPtr & timer)
316 {
317  bool removed = false;
318  {
319  std::unique_lock<std::mutex> lock(timers_mutex_);
320  removed = weak_timers_heap_.remove_timer(timer);
321 
322  timers_updated_ = timers_updated_ || removed;
323  }
324 
325  if (removed) {
326  // Notify timers thread such that it can re-compute its timeout
327  timers_cv_.notify_one();
328  timer->clear_on_reset_callback();
329  }
330 }
This class provides a way for storing and executing timer objects. It provides APIs to suit the needs...
RCLCPP_PUBLIC void start()
Starts a thread that takes care of executing the timers stored in this object. Function will throw an...
RCLCPP_PUBLIC ~TimersManager()
Destruct the TimersManager object making sure to stop thread and release memory.
RCLCPP_PUBLIC std::optional< std::chrono::nanoseconds > get_head_timeout()
Get the amount of time before the next timer triggers. This function is thread safe.
RCLCPP_PUBLIC void add_timer(const rclcpp::TimerBase::SharedPtr &timer)
Adds a new timer to the storage, maintaining weak ownership of it. Function is thread safe and it can...
RCLCPP_PUBLIC bool execute_head_timer()
Executes head timer if ready. This function is thread safe. This function will try to execute the tim...
RCLCPP_PUBLIC void clear()
Remove all the timers stored in the object. Function is thread safe and it can be called regardless o...
RCLCPP_PUBLIC size_t get_number_ready_timers()
Get the number of timers that are currently ready. This function is thread safe.
RCLCPP_PUBLIC void remove_timer(const rclcpp::TimerBase::SharedPtr &timer)
Remove a single timer from the object storage. Will do nothing if the timer was not being stored here...
RCLCPP_PUBLIC void stop()
Stops the timers thread. Will do nothing if the timer thread was not running.
RCLCPP_PUBLIC void execute_ready_timer(const rclcpp::TimerBase *timer_id, const std::shared_ptr< void > &data)
Executes timer identified by its ID. This function is thread safe. This function will try to execute ...
RCLCPP_PUBLIC bool ok(const rclcpp::Context::SharedPtr &context=rclcpp::contexts::get_global_default_context())
Check rclcpp's status.