Skip to content

Commit c99d764

Browse files
committed
cleanup timers manager
Signed-off-by: Soragna, Alberto <[email protected]>
1 parent 6a99496 commit c99d764

File tree

2 files changed

+94
-80
lines changed

2 files changed

+94
-80
lines changed

rclcpp/include/rclcpp/executors/timers_manager.hpp

Lines changed: 86 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,22 @@ namespace executors
4040
*
4141
* Timers management
4242
* This class provides APIs to add and remove timers.
43-
* It keeps a list of weak pointers from added timers, and owns them only when
44-
* have expired and need to be executed.
43+
* It keeps a list of weak pointers from added timers, and locks them only when
44+
* they need to be executed or modified.
4545
* Timers are kept ordered in a binary-heap priority queue.
4646
* Calls to add/remove APIs will temporarily block the execution of the timers and
47-
* will require to reorder the internal priority queue of timers.
47+
* will require to reorder the internal priority queue.
4848
* Because of this, they have a not-negligible impact on the performance.
4949
*
5050
* Timers execution
5151
* The most efficient implementation consists in letting a TimersManager object
5252
* to spawn a thread where timers are monitored and periodically executed.
5353
* Besides this, other APIs allow to either execute a single timer or all the
5454
* currently ready ones.
55-
* This class assumes that the execute_callback API of the stored timer is never
56-
* called by other entities, but can only be called from here.
57-
* If this assumption is not respected, the heap property will be invalidated,
58-
* so timers may be executed out of order.
55+
* This class assumes that the `execute_callback()` API of the stored timers is never
56+
* called by other entities, but it can only be called from here.
57+
* If this assumption is not respected, the heap property may be invalidated,
58+
* so timers may be executed out of order, without this object noticing it.
5959
*
6060
*/
6161
class TimersManager
@@ -65,70 +65,80 @@ class TimersManager
6565

6666
/**
6767
* @brief Construct a new TimersManager object
68+
*
69+
* @param context custom context to be used.
70+
* Shared ownership of the context is held until destruction.
6871
*/
6972
explicit TimersManager(std::shared_ptr<rclcpp::Context> context);
7073

7174
/**
72-
* @brief Destruct the object making sure to stop thread and release memory.
75+
* @brief Destruct the TimersManager object making sure to stop thread and release memory.
7376
*/
7477
~TimersManager();
7578

7679
/**
77-
* @brief Adds a new TimerBase::WeakPtr to the storage.
78-
* This object will store a weak pointer of the added timer
79-
* in a heap data structure.
80-
* @param timer the timer to be added
80+
* @brief Adds a new timer to the storage, maintaining weak ownership of it.
81+
* Function is thread safe and it can be called regardless of the state of the timers thread.
82+
*
83+
* @param timer the timer to add.
8184
*/
8285
void add_timer(rclcpp::TimerBase::SharedPtr timer);
8386

8487
/**
85-
* @brief Starts a thread that takes care of executing timers added to this object.
88+
* @brief Remove a single timer from the object storage.
89+
* Will do nothing if the timer was not being stored here.
90+
* Function is thread safe and it can be called regardless of the state of the timers thread.
91+
*
92+
* @param timer the timer to remove.
93+
*/
94+
void remove_timer(rclcpp::TimerBase::SharedPtr timer);
95+
96+
/**
97+
* @brief Remove all the timers stored in the object.
98+
* Function is thread safe and it can be called regardless of the state of the timers thread.
99+
*/
100+
void clear();
101+
102+
/**
103+
* @brief Starts a thread that takes care of executing the timers stored in this object.
104+
* Function will throw an error if the timers thread was already running.
86105
*/
87106
void start();
88107

89108
/**
90109
* @brief Stops the timers thread.
110+
* Will do nothing if the timer thread was not running.
91111
*/
92112
void stop();
93113

94114
/**
95-
* @brief Executes all the timers currently ready when the function is invoked
96-
* while keeping the heap correctly sorted.
97-
* @return std::chrono::nanoseconds for next timer to expire,
98-
* the returned value could be negative if the timer is already expired
99-
* or MAX_TIME if the heap is empty.
115+
* @brief Executes all the timers currently ready when the function was invoked.
116+
* This function will lock all the stored timers throughout its duration.
117+
* Function is thread safe, but it will throw an error if the timers thread is running.
100118
*/
101-
std::chrono::nanoseconds execute_ready_timers();
119+
void execute_ready_timers();
102120

103121
/**
104122
* @brief Executes head timer if ready at time point.
105-
* @param tp the timepoint to check for
106-
* @return true if head timer was ready at tp
123+
* Function is thread safe, but it will throw an error if the timers thread is running.
124+
*
125+
* @param tp the time point to check for, where `max()` denotes that no check will be performed.
126+
* @return true if head timer was ready at time point.
107127
*/
108128
bool execute_head_timer(
109129
std::chrono::time_point<std::chrono::steady_clock> tp =
110130
std::chrono::time_point<std::chrono::steady_clock>::max());
111131

112132
/**
113133
* @brief Get the amount of time before the next timer expires.
114-
*
134+
* Function is thread safe, but it will throw an error if the timers thread is running.
135+
*
115136
* @return std::chrono::nanoseconds to wait,
116137
* the returned value could be negative if the timer is already expired
117-
* or MAX_TIME if the heap is empty.
138+
* or MAX_TIME if there are no timers stored in the object.
118139
*/
119140
std::chrono::nanoseconds get_head_timeout();
120141

121-
/**
122-
* @brief Remove all the timers stored in the object.
123-
*/
124-
void clear();
125-
126-
/**
127-
* @brief Remove a single timer stored in the object, passed as a shared_ptr.
128-
* @param timer the timer to remove.
129-
*/
130-
void remove_timer(rclcpp::TimerBase::SharedPtr timer);
131-
132142
// This is what the TimersManager uses to denote a duration forever.
133143
// We don't use std::chrono::nanoseconds::max because it will overflow.
134144
// See https://en.cppreference.com/w/cpp/thread/condition_variable/wait_for
@@ -144,20 +154,22 @@ class TimersManager
144154
class TimersHeap;
145155

146156
/**
147-
* @brief This class allows to store weak pointers to timers in a heap data structure.
157+
* @brief This class allows to store weak pointers to timers in a heap-like data structure.
158+
* The root of the heap is the timer that expires first.
159+
* Since this class uses weak ownership, it is not guaranteed that it represents a valid heap
160+
* at any point in time as timers could go out of scope, thus invalidating it.
148161
* The "validate_and_lock" API allows to get ownership of the timers and also makes sure that
149162
* the heap property is respected.
150-
* The root of the heap is the timer that expires first.
151163
* This class is not thread safe and requires external mutexes to protect its usage.
152164
*/
153165
class WeakTimersHeap
154166
{
155167
public:
156168
/**
157-
* @brief Try to add a new timer to the heap.
158-
* After the addition, the heap property is preserved.
159-
* @param timer new timer to add
160-
* @return true if timer has been added, false if it was already there
169+
* @brief Add a new timer to the heap. After the addition, the heap property is enforced.
170+
*
171+
* @param timer new timer to add.
172+
* @return true if timer has been added, false if it was already there.
161173
*/
162174
bool add_timer(TimerPtr timer)
163175
{
@@ -173,10 +185,10 @@ class TimersManager
173185
}
174186

175187
/**
176-
* @brief Try to remove a timer from the heap.
177-
* After the removal, the heap property is preserved.
178-
* @param timer timer to remove
179-
* @return true if timer has been removed, false if it was not there
188+
* @brief Remove a timer from the heap. After the removal, the heap property is enforced.
189+
*
190+
* @param timer timer to remove.
191+
* @return true if timer has been removed, false if it was not there.
180192
*/
181193
bool remove_timer(TimerPtr timer)
182194
{
@@ -193,7 +205,9 @@ class TimersManager
193205

194206
/**
195207
* @brief This function restores the current object as a valid heap
196-
* and it also returns a locked version of it
208+
* and it returns a locked version of it.
209+
* It is the only public API to access the stored timers.
210+
*
197211
* @return TimersHeap owned timers corresponding to the current object
198212
*/
199213
TimersHeap validate_and_lock()
@@ -205,8 +219,9 @@ class TimersManager
205219

206220
while (it != weak_heap_.end()) {
207221
if (auto timer_shared_ptr = it->lock()) {
208-
// This timer is valid, add it to the vector
209-
locked_heap.push_back(std::move(timer_shared_ptr));
222+
// This timer is valid, add it to the locked heap
223+
// Note: we access private `owned_heap_` member field.
224+
locked_heap.owned_heap_.push_back(std::move(timer_shared_ptr));
210225
it++;
211226
} else {
212227
// This timer went out of scope, remove it
@@ -229,11 +244,15 @@ class TimersManager
229244
/**
230245
* @brief This function allows to recreate the heap of weak pointers
231246
* from an heap of owned pointers.
247+
* It is required to be called after a locked TimersHeap generated from this object
248+
* has been modified in any way (e.g. timers triggered, added, removed).
249+
*
232250
* @param heap timers heap to store as weak pointers
233251
*/
234252
void store(const TimersHeap & heap)
235253
{
236254
weak_heap_.clear();
255+
// Note: we access private `owned_heap_` member field.
237256
for (auto t : heap.owned_heap_) {
238257
weak_heap_.push_back(t);
239258
}
@@ -254,7 +273,8 @@ class TimersManager
254273
/**
255274
* @brief This class is the equivalent of WeakTimersHeap but with ownership of the timers.
256275
* It can be generated by locking the weak version.
257-
* It provides operations to manipulate the heap
276+
* It provides operations to manipulate the heap.
277+
* This class is not thread safe and requires external mutexes to protect its usage.
258278
*/
259279
class TimersHeap
260280
{
@@ -324,13 +344,15 @@ class TimersManager
324344
}
325345

326346
/**
327-
* @brief Restore a valid heap after the root value has been replaced.
347+
* @brief Restore a valid heap after the root value has been replaced (e.g. timer triggered).
328348
*/
329349
void heapify_root()
330350
{
331351
// The following code is a more efficient version of doing
332-
// - pop_heap; pop_back;
333-
// - push_back; push_heap;
352+
// pop_heap();
353+
// pop_back();
354+
// push_back();
355+
// push_heap();
334356
// as it removes the need for the last push_heap
335357

336358
// Push the modified element (i.e. the current root) at the bottom of the heap
@@ -350,26 +372,21 @@ class TimersManager
350372
}
351373

352374
/**
353-
* @brief Convenience function that allows to push an element at the bottom of the heap.
354-
* It will not perform any check on whether the heap remains valid or not.
355-
* Those checks are responsibility of the calling code.
356-
*
357-
* @param timer timer to push at the back of the data structure representing the heap
375+
* @brief Friend declaration to allow the `validate_and_lock()` function to access the
376+
* underlying heap container
358377
*/
359-
void push_back(TimerPtr timer)
360-
{
361-
owned_heap_.push_back(timer);
362-
}
378+
friend TimersHeap WeakTimersHeap::validate_and_lock();
363379

364380
/**
365-
* @brief Friend declaration to allow the store function to access the underlying
366-
* heap container
381+
* @brief Friend declaration to allow the `store()` function to access the
382+
* underlying heap container
367383
*/
368384
friend void WeakTimersHeap::store(const TimersHeap & heap);
369385

370386
private:
371387
/**
372388
* @brief Comparison function between timers.
389+
* Returns true if `a` expires after `b`.
373390
*/
374391
static bool timer_greater(TimerPtr a, TimerPtr b)
375392
{
@@ -392,6 +409,7 @@ class TimersManager
392409
* @return std::chrono::nanoseconds to wait,
393410
* the returned value could be negative if the timer is already expired
394411
* or MAX_TIME if the heap is empty.
412+
* This function is not thread safe, acquire the timers_mutex_ before calling it.
395413
*/
396414
std::chrono::nanoseconds get_head_timeout_unsafe(const TimersHeap & heap)
397415
{
@@ -404,15 +422,16 @@ class TimersManager
404422
/**
405423
* @brief Executes all the timers currently ready when the function is invoked
406424
* while keeping the heap correctly sorted.
407-
* This function is not thread safe, acquire a mutex before calling it.
425+
* This function is not thread safe, acquire the timers_mutex_ before calling it.
408426
*/
409-
void execute_ready_timers_unsafe(TimersHeap & heap);
427+
void execute_ready_timers_unsafe();
410428

411429
/**
412430
* @brief Helper function that checks whether a timer was already ready
413-
* at a specific timepoint
431+
* at a specific time point.
432+
414433
* @param timer a pointer to the timer to check for
415-
* @param tp the timepoint to check for
434+
* @param tp the time point to check for
416435
* @return true if timer was ready at tp
417436
*/
418437
bool timer_was_ready_at_tp(
@@ -424,7 +443,7 @@ class TimersManager
424443
return time_ready < tp;
425444
}
426445

427-
// Thread used to run the timers monitoring and execution task
446+
// Thread used to run the timers execution task
428447
std::thread timers_thread_;
429448
// Protects access to timers
430449
std::mutex timers_mutex_;

rclcpp/src/rclcpp/executors/timers_manager.cpp

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ void TimersManager::start()
6868
void TimersManager::stop()
6969
{
7070
// Nothing to do if the timers thread is not running
71-
// or if another thred already signaled to stop.
71+
// or if another thread already signaled to stop.
7272
if (!running_.exchange(false)) {
7373
return;
7474
}
@@ -99,7 +99,7 @@ std::chrono::nanoseconds TimersManager::get_head_timeout()
9999
return this->get_head_timeout_unsafe(timers_heap);
100100
}
101101

102-
std::chrono::nanoseconds TimersManager::execute_ready_timers()
102+
void TimersManager::execute_ready_timers()
103103
{
104104
// Do not allow to interfere with the thread running
105105
if (running_) {
@@ -112,12 +112,10 @@ std::chrono::nanoseconds TimersManager::execute_ready_timers()
112112
TimersHeap timers_heap = weak_timers_heap_.validate_and_lock();
113113
this->execute_ready_timers_unsafe(timers_heap);
114114
weak_timers_heap_.store(timers_heap);
115-
116-
return this->get_head_timeout_unsafe(timers_heap);
117115
}
118116

119117
bool TimersManager::execute_head_timer(
120-
std::chrono::time_point<std::chrono::steady_clock> timepoint)
118+
std::chrono::time_point<std::chrono::steady_clock> tp)
121119
{
122120
// Do not allow to interfere with the thread running
123121
if (running_) {
@@ -137,11 +135,8 @@ bool TimersManager::execute_head_timer(
137135
TimerPtr head = timers_heap.front();
138136

139137
bool timer_ready = false;
140-
141-
auto max_time = std::chrono::time_point<std::chrono::steady_clock>::max();
142-
143-
if (timepoint != max_time) {
144-
timer_ready = timer_was_ready_at_tp(head, timepoint);
138+
if (tp != std::chrono::time_point<std::chrono::steady_clock>::max()) {
139+
timer_ready = timer_was_ready_at_tp(head, tp);
145140
} else {
146141
timer_ready = head->is_ready();
147142
}
@@ -152,10 +147,10 @@ bool TimersManager::execute_head_timer(
152147
timers_heap.heapify_root();
153148
weak_timers_heap_.store(timers_heap);
154149
return true;
155-
} else {
156-
// Head timer was not ready yet
157-
return false;
158150
}
151+
152+
// Head timer was not ready yet
153+
return false;
159154
}
160155

161156
void TimersManager::execute_ready_timers_unsafe(TimersHeap & heap)

0 commit comments

Comments
 (0)