Skip to content

Commit 7e7d709

Browse files
Janosch MachowinskiJanosch Machowinski
authored andcommitted
fix: Make passing of timer data thread safe
Signed-off-by: Janosch Machowinski <[email protected]>
1 parent e9639af commit 7e7d709

File tree

12 files changed

+173
-46
lines changed

12 files changed

+173
-46
lines changed

rclcpp/include/rclcpp/executor.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ class Executor
500500
*/
501501
RCLCPP_PUBLIC
502502
static void
503-
execute_timer(rclcpp::TimerBase::SharedPtr timer);
503+
execute_timer(rclcpp::TimerBase::SharedPtr timer, const std::shared_ptr<void> & dataPtr);
504504

505505
/// Run service server executable.
506506
/**

rclcpp/include/rclcpp/experimental/executors/events_executor/events_executor_event_types.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#ifndef RCLCPP__EXPERIMENTAL__EXECUTORS__EVENTS_EXECUTOR__EVENTS_EXECUTOR_EVENT_TYPES_HPP_
1616
#define RCLCPP__EXPERIMENTAL__EXECUTORS__EVENTS_EXECUTOR__EVENTS_EXECUTOR_EVENT_TYPES_HPP_
1717

18+
#include <memory>
19+
1820
namespace rclcpp
1921
{
2022
namespace experimental
@@ -34,6 +36,7 @@ enum ExecutorEventType
3436
struct ExecutorEvent
3537
{
3638
const void * entity_key;
39+
std::shared_ptr<void> data;
3740
int waitable_data;
3841
ExecutorEventType type;
3942
size_t num_events;

rclcpp/include/rclcpp/experimental/timers_manager.hpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ class TimersManager
8686
RCLCPP_PUBLIC
8787
TimersManager(
8888
std::shared_ptr<rclcpp::Context> context,
89-
std::function<void(const rclcpp::TimerBase *)> on_ready_callback = nullptr);
89+
std::function<void(const rclcpp::TimerBase *,
90+
const std::shared_ptr<void> &)> on_ready_callback = nullptr);
9091

9192
/**
9293
* @brief Destruct the TimersManager object making sure to stop thread and release memory.
@@ -164,9 +165,10 @@ class TimersManager
164165
* the TimersManager on_ready_callback was passed during construction.
165166
*
166167
* @param timer_id the timer ID of the timer to execute
168+
* @param data internal data of the timer
167169
*/
168170
RCLCPP_PUBLIC
169-
void execute_ready_timer(const rclcpp::TimerBase * timer_id);
171+
void execute_ready_timer(const rclcpp::TimerBase * timer_id, const std::shared_ptr<void> & data);
170172

171173
/**
172174
* @brief Get the amount of time before the next timer triggers.
@@ -529,7 +531,8 @@ class TimersManager
529531
void execute_ready_timers_unsafe();
530532

531533
// Callback to be called when timer is ready
532-
std::function<void(const rclcpp::TimerBase *)> on_ready_callback_;
534+
std::function<void(const rclcpp::TimerBase *,
535+
const std::shared_ptr<void> &)> on_ready_callback_ = nullptr;
533536

534537
// Thread used to run the timers execution task
535538
std::thread timers_thread_;

rclcpp/include/rclcpp/strategies/allocator_memory_strategy.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,8 @@ class AllocatorMemoryStrategy : public memory_strategy::MemoryStrategy
368368
++it;
369369
continue;
370370
}
371-
if (!timer->call()) {
371+
auto data = timer->call();
372+
if (!data) {
372373
// timer was cancelled, skip it.
373374
++it;
374375
continue;
@@ -377,6 +378,7 @@ class AllocatorMemoryStrategy : public memory_strategy::MemoryStrategy
377378
any_exec.timer = timer;
378379
any_exec.callback_group = group;
379380
any_exec.node_base = get_node_by_group(group, weak_groups_to_nodes);
381+
any_exec.data = *data;
380382
timer_handles_.erase(it);
381383
return;
382384
}

rclcpp/include/rclcpp/timer.hpp

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <atomic>
1919
#include <chrono>
2020
#include <functional>
21+
#include <optional>
2122
#include <memory>
2223
#include <sstream>
2324
#include <thread>
@@ -43,6 +44,12 @@
4344
namespace rclcpp
4445
{
4546

47+
struct TimerInfo
48+
{
49+
Time expected_call_time;
50+
Time actual_call_time;
51+
};
52+
4653
class TimerBase
4754
{
4855
public:
@@ -101,16 +108,19 @@ class TimerBase
101108
* The multithreaded executor takes advantage of this to avoid scheduling
102109
* the callback multiple times.
103110
*
104-
* \return `true` if the callback should be executed, `false` if the timer was canceled.
111+
* \return a shared_ptr if the callback should be executed, std::nullopt if the timer was canceled.
105112
*/
106113
RCLCPP_PUBLIC
107-
virtual bool
114+
virtual std::optional<std::shared_ptr<void>>
108115
call() = 0;
109116

110117
/// Call the callback function when the timer signal is emitted.
118+
/**
119+
* \param[in] data the pointer returned by the call function
120+
*/
111121
RCLCPP_PUBLIC
112122
virtual void
113-
execute_callback() = 0;
123+
execute_callback(const std::shared_ptr<void> & data) = 0;
114124

115125
RCLCPP_PUBLIC
116126
std::shared_ptr<const rcl_timer_t>
@@ -198,12 +208,6 @@ class TimerBase
198208
set_on_reset_callback(rcl_event_callback_t callback, const void * user_data);
199209
};
200210

201-
struct TimerInfo
202-
{
203-
Time expected_call_time;
204-
Time actual_call_time;
205-
};
206-
207211
using VoidCallbackType = std::function<void ()>;
208212
using TimerCallbackType = std::function<void (TimerBase &)>;
209213
using TimerInfoCallbackType = std::function<void (const TimerInfo &)>;
@@ -263,27 +267,28 @@ class GenericTimer : public TimerBase
263267
* \sa rclcpp::TimerBase::call
264268
* \throws std::runtime_error if it failed to notify timer that callback will occurr
265269
*/
266-
bool
270+
std::optional<std::shared_ptr<void>>
267271
call() override
268272
{
273+
rcl_timer_call_info_t timer_call_info_;
269274
rcl_ret_t ret = rcl_timer_call_with_info(timer_handle_.get(), &timer_call_info_);
270275
if (ret == RCL_RET_TIMER_CANCELED) {
271-
return false;
276+
return std::nullopt;
272277
}
273278
if (ret != RCL_RET_OK) {
274279
throw std::runtime_error("Failed to notify timer that callback occurred");
275280
}
276-
return true;
281+
return std::make_shared<rcl_timer_call_info_t>(timer_call_info_);
277282
}
278283

279284
/**
280285
* \sa rclcpp::TimerBase::execute_callback
281286
*/
282287
void
283-
execute_callback() override
288+
execute_callback(const std::shared_ptr<void> & data) override
284289
{
285290
TRACETOOLS_TRACEPOINT(callback_start, reinterpret_cast<const void *>(&callback_), false);
286-
execute_callback_delegate<>();
291+
execute_callback_delegate<>(*static_cast<rcl_timer_call_info_t *>(data.get()));
287292
TRACETOOLS_TRACEPOINT(callback_end, reinterpret_cast<const void *>(&callback_));
288293
}
289294

@@ -295,7 +300,7 @@ class GenericTimer : public TimerBase
295300
>::type * = nullptr
296301
>
297302
void
298-
execute_callback_delegate()
303+
execute_callback_delegate(const rcl_timer_call_info_t &)
299304
{
300305
callback_();
301306
}
@@ -307,7 +312,7 @@ class GenericTimer : public TimerBase
307312
>::type * = nullptr
308313
>
309314
void
310-
execute_callback_delegate()
315+
execute_callback_delegate(const rcl_timer_call_info_t &)
311316
{
312317
callback_(*this);
313318
}
@@ -320,7 +325,7 @@ class GenericTimer : public TimerBase
320325
>::type * = nullptr
321326
>
322327
void
323-
execute_callback_delegate()
328+
execute_callback_delegate(const rcl_timer_call_info_t & timer_call_info_)
324329
{
325330
const TimerInfo info{Time{timer_call_info_.expected_call_time, clock_->get_clock_type()},
326331
Time{timer_call_info_.actual_call_time, clock_->get_clock_type()}};
@@ -339,14 +344,14 @@ class GenericTimer : public TimerBase
339344
RCLCPP_DISABLE_COPY(GenericTimer)
340345

341346
FunctorT callback_;
342-
rcl_timer_call_info_t timer_call_info_;
343347
};
344348

345349
template<
346350
typename FunctorT,
347351
typename std::enable_if<
348352
rclcpp::function_traits::same_arguments<FunctorT, VoidCallbackType>::value ||
349-
rclcpp::function_traits::same_arguments<FunctorT, TimerCallbackType>::value
353+
rclcpp::function_traits::same_arguments<FunctorT, TimerCallbackType>::value ||
354+
rclcpp::function_traits::same_arguments<FunctorT, TimerInfoCallbackType>::value
350355
>::type * = nullptr
351356
>
352357
class WallTimer : public GenericTimer<FunctorT>

rclcpp/src/rclcpp/executor.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ Executor::execute_any_executable(AnyExecutable & any_exec)
545545
TRACETOOLS_TRACEPOINT(
546546
rclcpp_executor_execute,
547547
static_cast<const void *>(any_exec.timer->get_timer_handle().get()));
548-
execute_timer(any_exec.timer);
548+
execute_timer(any_exec.timer, any_exec.data);
549549
}
550550
if (any_exec.subscription) {
551551
TRACETOOLS_TRACEPOINT(
@@ -715,9 +715,9 @@ Executor::execute_subscription(rclcpp::SubscriptionBase::SharedPtr subscription)
715715
}
716716

717717
void
718-
Executor::execute_timer(rclcpp::TimerBase::SharedPtr timer)
718+
Executor::execute_timer(rclcpp::TimerBase::SharedPtr timer, const std::shared_ptr<void> & dataPtr)
719719
{
720-
timer->execute_callback();
720+
timer->execute_callback(dataPtr);
721721
}
722722

723723
void

rclcpp/src/rclcpp/executors/static_single_threaded_executor.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,13 @@ StaticSingleThreadedExecutor::execute_ready_executables(bool spin_once)
231231
if (i < entities_collector_->get_number_of_timers()) {
232232
if (wait_set_.timers[i] && entities_collector_->get_timer(i)->is_ready()) {
233233
auto timer = entities_collector_->get_timer(i);
234-
timer->call();
235-
execute_timer(std::move(timer));
234+
auto data = timer->call();
235+
if (!data) {
236+
throw std::runtime_error(
237+
"StaticSingleThreadedExecutor::execute_ready_executables() Error,"
238+
"timer is ready, but call returned no data");
239+
}
240+
execute_timer(std::move(timer), *data);
236241
if (spin_once) {
237242
return true;
238243
}

rclcpp/src/rclcpp/experimental/executors/events_executor/events_executor.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,12 @@ EventsExecutor::EventsExecutor(
4040
// The timers manager can be used either to only track timers (in this case an expired
4141
// timer will generate an executor event and then it will be executed by the executor thread)
4242
// or it can also take care of executing expired timers in its dedicated thread.
43-
std::function<void(const rclcpp::TimerBase *)> timer_on_ready_cb = nullptr;
43+
std::function<void(const rclcpp::TimerBase *,
44+
const std::shared_ptr<void> &)> timer_on_ready_cb = nullptr;
4445
if (!execute_timers_separate_thread) {
45-
timer_on_ready_cb = [this](const rclcpp::TimerBase * timer_id) {
46-
ExecutorEvent event = {timer_id, -1, ExecutorEventType::TIMER_EVENT, 1};
46+
timer_on_ready_cb =
47+
[this](const rclcpp::TimerBase * timer_id, const std::shared_ptr<void> & data) {
48+
ExecutorEvent event = {timer_id, data, -1, ExecutorEventType::TIMER_EVENT, 1};
4749
this->events_queue_->enqueue(event);
4850
};
4951
}
@@ -88,7 +90,7 @@ EventsExecutor::EventsExecutor(
8890
}
8991

9092
ExecutorEvent event =
91-
{notify_waitable_entity_id, waitable_data, ExecutorEventType::WAITABLE_EVENT, 1};
93+
{notify_waitable_entity_id, nullptr, waitable_data, ExecutorEventType::WAITABLE_EVENT, 1};
9294
this->events_queue_->enqueue(event);
9395
});
9496

@@ -325,7 +327,7 @@ EventsExecutor::execute_event(const ExecutorEvent & event)
325327
case ExecutorEventType::TIMER_EVENT:
326328
{
327329
timers_manager_->execute_ready_timer(
328-
static_cast<const rclcpp::TimerBase *>(event.entity_key));
330+
static_cast<const rclcpp::TimerBase *>(event.entity_key), event.data);
329331
break;
330332
}
331333
case ExecutorEventType::WAITABLE_EVENT:
@@ -485,7 +487,7 @@ EventsExecutor::create_entity_callback(
485487
{
486488
std::function<void(size_t)>
487489
callback = [this, entity_key, event_type](size_t num_events) {
488-
ExecutorEvent event = {entity_key, -1, event_type, num_events};
490+
ExecutorEvent event = {entity_key, nullptr, -1, event_type, num_events};
489491
this->events_queue_->enqueue(event);
490492
};
491493
return callback;
@@ -497,7 +499,7 @@ EventsExecutor::create_waitable_callback(const rclcpp::Waitable * entity_key)
497499
std::function<void(size_t, int)>
498500
callback = [this, entity_key](size_t num_events, int waitable_data) {
499501
ExecutorEvent event =
500-
{entity_key, waitable_data, ExecutorEventType::WAITABLE_EVENT, num_events};
502+
{entity_key, nullptr, waitable_data, ExecutorEventType::WAITABLE_EVENT, num_events};
501503
this->events_queue_->enqueue(event);
502504
};
503505
return callback;

rclcpp/src/rclcpp/experimental/timers_manager.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ using rclcpp::experimental::TimersManager;
2727

2828
TimersManager::TimersManager(
2929
std::shared_ptr<rclcpp::Context> context,
30-
std::function<void(const rclcpp::TimerBase *)> on_ready_callback)
30+
std::function<void(const rclcpp::TimerBase *, const std::shared_ptr<void> &)> on_ready_callback)
3131
: on_ready_callback_(on_ready_callback),
3232
context_(context)
3333
{
@@ -148,24 +148,29 @@ bool TimersManager::execute_head_timer()
148148
if (timer_ready) {
149149
// NOTE: here we always execute the timer, regardless of whether the
150150
// on_ready_callback is set or not.
151-
head_timer->call();
152-
head_timer->execute_callback();
151+
auto data = head_timer->call();
152+
if (!data) {
153+
throw std::runtime_error("execute_head_timer() timer repoted ready, but call returned false");
154+
}
155+
head_timer->execute_callback(*data);
153156
timers_heap.heapify_root();
154157
weak_timers_heap_.store(timers_heap);
155158
}
156159

157160
return timer_ready;
158161
}
159162

160-
void TimersManager::execute_ready_timer(const rclcpp::TimerBase * timer_id)
163+
void TimersManager::execute_ready_timer(
164+
const rclcpp::TimerBase * timer_id,
165+
const std::shared_ptr<void> & data)
161166
{
162167
TimerPtr ready_timer;
163168
{
164169
std::unique_lock<std::mutex> lock(timers_mutex_);
165170
ready_timer = weak_timers_heap_.get_timer(timer_id);
166171
}
167172
if (ready_timer) {
168-
ready_timer->execute_callback();
173+
ready_timer->execute_callback(data);
169174
}
170175
}
171176

@@ -215,11 +220,16 @@ void TimersManager::execute_ready_timers_unsafe()
215220
const size_t number_ready_timers = locked_heap.get_number_ready_timers();
216221
size_t executed_timers = 0;
217222
while (executed_timers < number_ready_timers && head_timer->is_ready()) {
218-
head_timer->call();
223+
auto data = head_timer->call();
224+
if (!data) {
225+
throw std::runtime_error(
226+
"TimersManager::execute_ready_timers_unsafe(): Error, timer was"
227+
"ready but call returned false");
228+
}
219229
if (on_ready_callback_) {
220-
on_ready_callback_(head_timer.get());
230+
on_ready_callback_(head_timer.get(), *data);
221231
} else {
222-
head_timer->execute_callback();
232+
head_timer->execute_callback(*data);
223233
}
224234

225235
executed_timers++;

rclcpp/test/rclcpp/executors/test_events_queue.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ TEST(TestEventsQueue, SimpleQueueTest)
6868
// Lets push an event into the queue and get it back
6969
rclcpp::experimental::executors::ExecutorEvent push_event = {
7070
simple_queue.get(),
71+
nullptr,
7172
99,
7273
rclcpp::experimental::executors::ExecutorEventType::SUBSCRIPTION_EVENT,
7374
1};

0 commit comments

Comments
 (0)