Skip to content

Commit b72d124

Browse files
committed
new implementation of get_head_timeout_unsafe
Signed-off-by: Soragna, Alberto <[email protected]>
1 parent c99d764 commit b72d124

File tree

2 files changed

+66
-38
lines changed

2 files changed

+66
-38
lines changed

rclcpp/include/rclcpp/executors/timers_manager.hpp

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class TimersManager
6565

6666
/**
6767
* @brief Construct a new TimersManager object
68-
*
68+
*
6969
* @param context custom context to be used.
7070
* Shared ownership of the context is held until destruction.
7171
*/
@@ -79,7 +79,7 @@ class TimersManager
7979
/**
8080
* @brief Adds a new timer to the storage, maintaining weak ownership of it.
8181
* Function is thread safe and it can be called regardless of the state of the timers thread.
82-
*
82+
*
8383
* @param timer the timer to add.
8484
*/
8585
void add_timer(rclcpp::TimerBase::SharedPtr timer);
@@ -88,7 +88,7 @@ class TimersManager
8888
* @brief Remove a single timer from the object storage.
8989
* Will do nothing if the timer was not being stored here.
9090
* Function is thread safe and it can be called regardless of the state of the timers thread.
91-
*
91+
*
9292
* @param timer the timer to remove.
9393
*/
9494
void remove_timer(rclcpp::TimerBase::SharedPtr timer);
@@ -121,7 +121,7 @@ class TimersManager
121121
/**
122122
* @brief Executes head timer if ready at time point.
123123
* Function is thread safe, but it will throw an error if the timers thread is running.
124-
*
124+
*
125125
* @param tp the time point to check for, where `max()` denotes that no check will be performed.
126126
* @return true if head timer was ready at time point.
127127
*/
@@ -132,7 +132,7 @@ class TimersManager
132132
/**
133133
* @brief Get the amount of time before the next timer expires.
134134
* Function is thread safe, but it will throw an error if the timers thread is running.
135-
*
135+
*
136136
* @return std::chrono::nanoseconds to wait,
137137
* the returned value could be negative if the timer is already expired
138138
* or MAX_TIME if there are no timers stored in the object.
@@ -203,10 +203,26 @@ class TimersManager
203203
return removed;
204204
}
205205

206+
/**
207+
* @brief Returns a const reference to the front element
208+
*/
209+
const WeakTimerPtr & front() const
210+
{
211+
return weak_heap_.front();
212+
}
213+
214+
/**
215+
* @brief Returns whether the heap is empty or not
216+
*/
217+
bool empty() const
218+
{
219+
return weak_heap_.empty();
220+
}
221+
206222
/**
207223
* @brief This function restores the current object as a valid heap
208224
* and it returns a locked version of it.
209-
* It is the only public API to access the stored timers.
225+
* It is the only public API to access and manipulate the stored timers.
210226
*
211227
* @return TimersHeap owned timers corresponding to the current object
212228
*/
@@ -411,13 +427,7 @@ class TimersManager
411427
* or MAX_TIME if the heap is empty.
412428
* This function is not thread safe, acquire the timers_mutex_ before calling it.
413429
*/
414-
std::chrono::nanoseconds get_head_timeout_unsafe(const TimersHeap & heap)
415-
{
416-
if (heap.empty()) {
417-
return MAX_TIME;
418-
}
419-
return (heap.front())->time_until_trigger();
420-
}
430+
std::chrono::nanoseconds get_head_timeout_unsafe();
421431

422432
/**
423433
* @brief Executes all the timers currently ready when the function is invoked

rclcpp/src/rclcpp/executors/timers_manager.cpp

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,7 @@ std::chrono::nanoseconds TimersManager::get_head_timeout()
9595
}
9696

9797
std::unique_lock<std::mutex> lock(timers_mutex_);
98-
TimersHeap timers_heap = weak_timers_heap_.validate_and_lock();
99-
return this->get_head_timeout_unsafe(timers_heap);
98+
return this->get_head_timeout_unsafe();
10099
}
101100

102101
void TimersManager::execute_ready_timers()
@@ -108,10 +107,7 @@ void TimersManager::execute_ready_timers()
108107
}
109108

110109
std::unique_lock<std::mutex> lock(timers_mutex_);
111-
112-
TimersHeap timers_heap = weak_timers_heap_.validate_and_lock();
113-
this->execute_ready_timers_unsafe(timers_heap);
114-
weak_timers_heap_.store(timers_heap);
110+
this->execute_ready_timers_unsafe();
115111
}
116112

117113
bool TimersManager::execute_head_timer(
@@ -153,55 +149,77 @@ bool TimersManager::execute_head_timer(
153149
return false;
154150
}
155151

156-
void TimersManager::execute_ready_timers_unsafe(TimersHeap & heap)
152+
std::chrono::nanoseconds TimersManager::get_head_timeout_unsafe()
157153
{
154+
// If we don't have any weak pointer, then we just return maximum timeout
155+
if (weak_timers_heap_.empty()) {
156+
return MAX_TIME;
157+
}
158+
159+
// Weak heap is not empty, so try to lock the first element
160+
TimerPtr head_timer = weak_timers_heap_.front().lock();
161+
// If it is still a valid pointer, it is guaranteed to be the correct head
162+
if (head_timer != nullptr) {
163+
return head_timer->time_until_trigger();
164+
}
165+
166+
// If the first elements has expired, we can't make other assumptions on the heap
167+
// and we need to entirely validate it.
168+
TimersHeap locked_heap = weak_timers_heap_.validate_and_lock();
169+
170+
// NOTE: the following operations will not modify any element in the heap, so we
171+
// don't have to call `weak_timers_heap_.store(locked_heap)` at the end.
172+
173+
if (locked_heap.empty()) {
174+
return MAX_TIME;
175+
}
176+
return locked_heap.front()->time_until_trigger();
177+
}
178+
179+
void TimersManager::execute_ready_timers_unsafe()
180+
{
181+
// We start by locking the timers
182+
TimersHeap locked_heap = weak_timers_heap_.validate_and_lock();
183+
158184
// Nothing to do if we don't have any timer
159-
if (heap.empty()) {
185+
if (locked_heap.empty()) {
160186
return;
161187
}
162188

163189
// Keep executing timers until they are ready and they were already ready when we started.
164190
// The second check prevents this function from blocking indefinitely if the
165191
// time required for executing the timers is longer than their period.
166192

167-
TimerPtr head = heap.front();
193+
TimerPtr head = locked_heap.front();
168194
auto start_time = std::chrono::steady_clock::now();
169195
while (head->is_ready() && this->timer_was_ready_at_tp(head, start_time)) {
170196
// Execute head timer
171197
head->execute_callback();
172198
// Executing a timer will result in updating its time_until_trigger, so re-heapify
173-
heap.heapify_root();
199+
locked_heap.heapify_root();
174200
// Get new head timer
175-
head = heap.front();
201+
head = locked_heap.front();
176202
}
203+
204+
// After having performed work on the locked heap we reflect the changes to weak one.
205+
// Timers will be already sorted the next time we need them if none went out of scope.
206+
weak_timers_heap_.store(locked_heap);
177207
}
178208

179209
void TimersManager::run_timers()
180210
{
181-
std::chrono::nanoseconds time_to_sleep;
182-
{
183-
std::unique_lock<std::mutex> lock(timers_mutex_);
184-
TimersHeap timers_heap = weak_timers_heap_.validate_and_lock();
185-
time_to_sleep = this->get_head_timeout_unsafe(timers_heap);
186-
}
187-
188211
while (rclcpp::ok(context_) && running_) {
189212
// Lock mutex
190213
std::unique_lock<std::mutex> timers_lock(timers_mutex_);
191214

215+
std::chrono::nanoseconds time_to_sleep = get_head_timeout_unsafe();
192216
// Wait until timeout or notification that timers have been updated
193217
timers_cv_.wait_for(timers_lock, time_to_sleep, [this]() {return timers_updated_;});
194218
// Reset timers updated flag
195219
timers_updated_ = false;
196220

197-
// Get ownership of timers
198-
TimersHeap timers_heap = weak_timers_heap_.validate_and_lock();
199221
// Execute timers
200-
this->execute_ready_timers_unsafe(timers_heap);
201-
// Store updated order of elements to efficiently re-use it next iteration
202-
weak_timers_heap_.store(timers_heap);
203-
// Get next timeout
204-
time_to_sleep = this->get_head_timeout_unsafe(timers_heap);
222+
this->execute_ready_timers_unsafe();
205223
}
206224

207225
// Make sure the running flag is set to false when we exit from this function

0 commit comments

Comments
 (0)