Skip to content

Commit ce5a261

Browse files
mauropassealsora
andauthored
Add support for timers on reset callback (#1979)
Signed-off-by: Mauro Passerino <[email protected]> Co-authored-by: Alberto Soragna <[email protected]>
1 parent beda096 commit ce5a261

File tree

2 files changed

+119
-3
lines changed

2 files changed

+119
-3
lines changed

rclcpp/include/rclcpp/timer.hpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,48 @@ class TimerBase
149149
bool
150150
exchange_in_use_by_wait_set_state(bool in_use_state);
151151

152+
/// Set a callback to be called when the timer is reset
153+
/**
154+
* You should aim to make this callback fast and not blocking.
155+
* If you need to do a lot of work or wait for some other event, you should
156+
* spin it off to another thread.
157+
*
158+
* Calling it again will override any previously set callback.
159+
* An exception will be thrown if the callback is not callable.
160+
*
161+
* This function is thread-safe.
162+
*
163+
* If you want more information available in the callback,
164+
* you may use a lambda with captures or std::bind.
165+
*
166+
* \param[in] callback functor to be called whenever timer is reset
167+
*/
168+
RCLCPP_PUBLIC
169+
void
170+
set_on_reset_callback(std::function<void(size_t)> callback);
171+
172+
/// Unset the callback registered for reset timer
173+
RCLCPP_PUBLIC
174+
void
175+
clear_on_reset_callback();
176+
152177
protected:
178+
std::recursive_mutex callback_mutex_;
179+
// Declare callback before timer_handle_, so on destruction
180+
// the callback is destroyed last. Otherwise, the rcl timer
181+
// callback would point briefly to a destroyed function.
182+
// Clearing the callback on timer destructor also makes sure
183+
// the rcl callback is cleared before on_reset_callback_.
184+
std::function<void(size_t)> on_reset_callback_{nullptr};
185+
153186
Clock::SharedPtr clock_;
154187
std::shared_ptr<rcl_timer_t> timer_handle_;
155188

156189
std::atomic<bool> in_use_by_wait_set_{false};
190+
191+
RCLCPP_PUBLIC
192+
void
193+
set_on_reset_callback(rcl_event_callback_t callback, const void * user_data);
157194
};
158195

159196

rclcpp/src/rclcpp/timer.cpp

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
#include <memory>
2020
#include <thread>
2121

22+
#include "rmw/impl/cpp/demangle.hpp"
23+
2224
#include "rclcpp/contexts/default_context.hpp"
25+
#include "rclcpp/detail/cpp_callback_trampoline.hpp"
2326
#include "rclcpp/exceptions.hpp"
24-
27+
#include "rclcpp/logging.hpp"
2528
#include "rcutils/logging_macros.h"
2629

2730
using rclcpp::TimerBase;
@@ -71,7 +74,9 @@ TimerBase::TimerBase(
7174
}
7275

7376
TimerBase::~TimerBase()
74-
{}
77+
{
78+
clear_on_reset_callback();
79+
}
7580

7681
void
7782
TimerBase::cancel()
@@ -96,7 +101,11 @@ TimerBase::is_canceled()
96101
void
97102
TimerBase::reset()
98103
{
99-
rcl_ret_t ret = rcl_timer_reset(timer_handle_.get());
104+
rcl_ret_t ret = RCL_RET_OK;
105+
{
106+
std::lock_guard<std::recursive_mutex> lock(callback_mutex_);
107+
ret = rcl_timer_reset(timer_handle_.get());
108+
}
100109
if (ret != RCL_RET_OK) {
101110
rclcpp::exceptions::throw_from_rcl_error(ret, "Couldn't reset timer");
102111
}
@@ -138,3 +147,73 @@ TimerBase::exchange_in_use_by_wait_set_state(bool in_use_state)
138147
{
139148
return in_use_by_wait_set_.exchange(in_use_state);
140149
}
150+
151+
void
152+
TimerBase::set_on_reset_callback(std::function<void(size_t)> callback)
153+
{
154+
if (!callback) {
155+
throw std::invalid_argument(
156+
"The callback passed to set_on_reset_callback "
157+
"is not callable.");
158+
}
159+
160+
auto new_callback =
161+
[callback, this](size_t reset_calls) {
162+
try {
163+
callback(reset_calls);
164+
} catch (const std::exception & exception) {
165+
RCLCPP_ERROR_STREAM(
166+
rclcpp::get_logger("rclcpp"),
167+
"rclcpp::TimerBase@" << this <<
168+
" caught " << rmw::impl::cpp::demangle(exception) <<
169+
" exception in user-provided callback for the 'on reset' callback: " <<
170+
exception.what());
171+
} catch (...) {
172+
RCLCPP_ERROR_STREAM(
173+
rclcpp::get_logger("rclcpp"),
174+
"rclcpp::TimerBase@" << this <<
175+
" caught unhandled exception in user-provided callback " <<
176+
"for the 'on reset' callback");
177+
}
178+
};
179+
180+
std::lock_guard<std::recursive_mutex> lock(callback_mutex_);
181+
182+
// Set it temporarily to the new callback, while we replace the old one.
183+
// This two-step setting, prevents a gap where the old std::function has
184+
// been replaced but rcl hasn't been told about the new one yet.
185+
set_on_reset_callback(
186+
rclcpp::detail::cpp_callback_trampoline<
187+
decltype(new_callback), const void *, size_t>,
188+
static_cast<const void *>(&new_callback));
189+
190+
// Store the std::function to keep it in scope, also overwrites the existing one.
191+
on_reset_callback_ = new_callback;
192+
193+
// Set it again, now using the permanent storage.
194+
set_on_reset_callback(
195+
rclcpp::detail::cpp_callback_trampoline<
196+
decltype(on_reset_callback_), const void *, size_t>,
197+
static_cast<const void *>(&on_reset_callback_));
198+
}
199+
200+
void
201+
TimerBase::clear_on_reset_callback()
202+
{
203+
std::lock_guard<std::recursive_mutex> lock(callback_mutex_);
204+
205+
if (on_reset_callback_) {
206+
set_on_reset_callback(nullptr, nullptr);
207+
on_reset_callback_ = nullptr;
208+
}
209+
}
210+
211+
void
212+
TimerBase::set_on_reset_callback(rcl_event_callback_t callback, const void * user_data)
213+
{
214+
rcl_ret_t ret = rcl_timer_set_on_reset_callback(timer_handle_.get(), callback, user_data);
215+
216+
if (ret != RCL_RET_OK) {
217+
rclcpp::exceptions::throw_from_rcl_error(ret, "Failed to set timer on reset callback");
218+
}
219+
}

0 commit comments

Comments
 (0)