Skip to content

Commit 56405fc

Browse files
wjwwoodMauro Passerino
authored andcommitted
significant refactor to make callbacks type safe
Signed-off-by: William Woodall <[email protected]>
1 parent 560e0cd commit 56405fc

File tree

13 files changed

+396
-82
lines changed

13 files changed

+396
-82
lines changed

rclcpp/include/rclcpp/client.hpp

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,19 @@
2828
#include "rcl/error_handling.h"
2929
#include "rcl/wait.h"
3030

31+
#include "rclcpp/detail/cpp_callback_trampoline.hpp"
3132
#include "rclcpp/exceptions.hpp"
33+
#include "rclcpp/expand_topic_or_service_name.hpp"
3234
#include "rclcpp/function_traits.hpp"
35+
#include "rclcpp/logging.hpp"
3336
#include "rclcpp/macros.hpp"
3437
#include "rclcpp/node_interfaces/node_graph_interface.hpp"
3538
#include "rclcpp/type_support_decl.hpp"
3639
#include "rclcpp/utilities.hpp"
37-
#include "rclcpp/expand_topic_or_service_name.hpp"
3840
#include "rclcpp/visibility_control.hpp"
3941

40-
#include "rcutils/logging_macros.h"
41-
4242
#include "rmw/error_handling.h"
43+
#include "rmw/impl/cpp/demangle.hpp"
4344
#include "rmw/rmw.h"
4445

4546
namespace rclcpp
@@ -150,11 +151,68 @@ class ClientBase
150151
bool
151152
exchange_in_use_by_wait_set_state(bool in_use_state);
152153

153-
RCLCPP_PUBLIC
154+
/// Set a callback to be called when each new response is received.
155+
/**
156+
* The callback receives a size_t which is the number of responses received
157+
* since the last time this callback was called.
158+
* Normally this is 1, but can be > 1 if responses were received before any
159+
* callback was set.
160+
*
161+
* Since this callback is called from the middleware, you should aim to make
162+
* it fast and not blocking.
163+
* If you need to do a lot of work or wait for some other event, you should
164+
* spin it off to another thread, otherwise you risk blocking the middleware.
165+
*
166+
* Calling it again will clear any previously set callback.
167+
*
168+
* This function is thread-safe.
169+
*
170+
* If you want more information available in the callback, like the client
171+
* or other information, you may use a lambda with captures or std::bind.
172+
*
173+
* \sa rmw_client_set_on_new_response_callback
174+
* \sa rcl_client_set_on_new_response_callback
175+
*
176+
* \param[in] callback functor to be called when a new response is received
177+
*/
154178
void
155-
set_listener_callback(
156-
rmw_listener_callback_t callback,
157-
const void * user_data) const;
179+
set_on_new_response_callback(std::function<void(size_t)> callback)
180+
{
181+
auto new_callback =
182+
[callback, this](size_t number_of_responses) {
183+
try {
184+
callback(number_of_responses);
185+
} catch (const std::exception & exception) {
186+
RCLCPP_ERROR_STREAM(
187+
node_logger_,
188+
"rclcpp::ClientBase@" << this <<
189+
" caught " << rmw::impl::cpp::demangle(exception) <<
190+
" exception in user-provided callback for the 'on new response' callback: " <<
191+
exception.what());
192+
} catch (...) {
193+
RCLCPP_ERROR_STREAM(
194+
node_logger_,
195+
"rclcpp::ClientBase@" << this <<
196+
" caught unhandled exception in user-provided callback " <<
197+
"for the 'on new response' callback");
198+
}
199+
};
200+
201+
// Set it temporarily to the new callback, while we replace the old one.
202+
// This two-step setting, prevents a gap where the old std::function has
203+
// been replaced but the middleware hasn't been told about the new one yet.
204+
set_on_new_response_callback(
205+
rclcpp::detail::cpp_callback_trampoline<const void *, size_t>,
206+
static_cast<const void *>(&new_callback));
207+
208+
// Store the std::function to keep it in scope, also overwrites the existing one.
209+
on_new_response_callback_ = new_callback;
210+
211+
// Set it again, now using the permanent storage.
212+
set_on_new_response_callback(
213+
rclcpp::detail::cpp_callback_trampoline<const void *, size_t>,
214+
static_cast<const void *>(&on_new_response_callback_));
215+
}
158216

159217
protected:
160218
RCLCPP_DISABLE_COPY(ClientBase)
@@ -171,13 +229,20 @@ class ClientBase
171229
const rcl_node_t *
172230
get_rcl_node_handle() const;
173231

232+
RCLCPP_PUBLIC
233+
void
234+
set_on_new_response_callback(rcl_event_callback_t callback, const void * user_data);
235+
174236
rclcpp::node_interfaces::NodeGraphInterface::WeakPtr node_graph_;
175237
std::shared_ptr<rcl_node_t> node_handle_;
176238
std::shared_ptr<rclcpp::Context> context_;
239+
rclcpp::Logger node_logger_;
177240

178241
std::shared_ptr<rcl_client_t> client_handle_;
179242

180243
std::atomic<bool> in_use_by_wait_set_{false};
244+
245+
std::function<void(size_t)> on_new_response_callback_;
181246
};
182247

183248
template<typename ServiceT>
@@ -303,8 +368,8 @@ class Client : public ClientBase
303368
int64_t sequence_number = request_header->sequence_number;
304369
// TODO(esteve) this should throw instead since it is not expected to happen in the first place
305370
if (this->pending_requests_.count(sequence_number) == 0) {
306-
RCUTILS_LOG_ERROR_NAMED(
307-
"rclcpp",
371+
RCLCPP_ERROR(
372+
node_logger_,
308373
"Received invalid sequence number. Ignoring...");
309374
return;
310375
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2021 Open Source Robotics Foundation, Inc.
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+
#ifndef RCLCPP__DETAIL__CPP_CALLBACK_TRAMPOLINE_HPP_
16+
#define RCLCPP__DETAIL__CPP_CALLBACK_TRAMPOLINE_HPP_
17+
18+
#include <functional>
19+
20+
namespace rclcpp
21+
{
22+
23+
namespace detail
24+
{
25+
26+
/// Trampoline pattern for wrapping std::function into C-style callbacks.
27+
/**
28+
* A common pattern in C is for a function to take a function pointer and a
29+
* void pointer for "user data" which is passed to the function pointer when it
30+
* is called from within C.
31+
*
32+
* It works by using the user data pointer to store a pointer to a
33+
* std::function instance.
34+
* So when called from C, this function will cast the user data to the right
35+
* std::function type and call it.
36+
*
37+
* This should allow you to use free functions, lambdas with and without
38+
* captures, and various kinds of std::bind instances.
39+
*
40+
* The interior of this function is likely to be executed within a C runtime,
41+
* so no exceptins should be thrown at this point, and doing so results in
42+
* undefined behavior.
43+
*
44+
* \tparam UserDataT Deduced type based on what is passed for user data,
45+
* usually this type is either `void *` or `const void *`.
46+
* \tparam Args the arguments being passed to the callback
47+
* \tparam ReturnT the return type of this function and the callback, default void
48+
* \param user_data the function pointer, possibly type erased
49+
* \returns whatever the callback returns, if anything
50+
*/
51+
template<
52+
typename UserDataT,
53+
typename ... Args,
54+
typename ReturnT = void
55+
>
56+
ReturnT
57+
cpp_callback_trampoline(UserDataT user_data, Args ... args) noexcept
58+
{
59+
auto & actual_callback = *reinterpret_cast<const std::function<ReturnT(Args...)> *>(user_data);
60+
return actual_callback(args ...);
61+
}
62+
63+
} // namespace detail
64+
65+
} // namespace rclcpp
66+
67+
#endif // RCLCPP__DETAIL__CPP_CALLBACK_TRAMPOLINE_HPP_

rclcpp/include/rclcpp/experimental/subscription_intra_process_base.hpp

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,23 +50,23 @@ class SubscriptionIntraProcessBase : public rclcpp::Waitable
5050

5151
RCLCPP_PUBLIC
5252
size_t
53-
get_number_of_ready_guard_conditions() {return 1;}
53+
get_number_of_ready_guard_conditions() override {return 1;}
5454

5555
RCLCPP_PUBLIC
5656
bool
57-
add_to_wait_set(rcl_wait_set_t * wait_set);
57+
add_to_wait_set(rcl_wait_set_t * wait_set) override;
5858

59-
virtual bool
60-
is_ready(rcl_wait_set_t * wait_set) = 0;
59+
bool
60+
is_ready(rcl_wait_set_t * wait_set) override = 0;
6161

62-
virtual
6362
std::shared_ptr<void>
64-
take_data() = 0;
63+
take_data() override = 0;
6564

66-
virtual void
67-
execute(std::shared_ptr<void> & data) = 0;
65+
void
66+
execute(std::shared_ptr<void> & data) override = 0;
6867

69-
virtual bool
68+
virtual
69+
bool
7070
use_take_shared_method() const = 0;
7171

7272
RCLCPP_PUBLIC
@@ -77,12 +77,6 @@ class SubscriptionIntraProcessBase : public rclcpp::Waitable
7777
rmw_qos_profile_t
7878
get_actual_qos() const;
7979

80-
RCLCPP_PUBLIC
81-
void
82-
set_listener_callback(
83-
rmw_listener_callback_t callback,
84-
const void * user_data) const override;
85-
8680
protected:
8781
std::recursive_mutex reentrant_mutex_;
8882
rclcpp::GuardCondition gc_;

rclcpp/include/rclcpp/qos_event.hpp

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@
2121
#include <string>
2222

2323
#include "rcl/error_handling.h"
24+
#include "rmw/impl/cpp/demangle.hpp"
2425
#include "rmw/incompatible_qos_events_statuses.h"
2526

2627
#include "rcutils/logging_macros.h"
2728

29+
#include "rclcpp/detail/cpp_callback_trampoline.hpp"
2830
#include "rclcpp/exceptions.hpp"
2931
#include "rclcpp/function_traits.hpp"
32+
#include "rclcpp/logging.hpp"
3033
#include "rclcpp/waitable.hpp"
3134

3235
namespace rclcpp
@@ -102,15 +105,78 @@ class QOSEventHandlerBase : public Waitable
102105
bool
103106
is_ready(rcl_wait_set_t * wait_set) override;
104107

105-
RCLCPP_PUBLIC
108+
/// Set a callback to be called when each new event instance occurs.
109+
/**
110+
* The callback receives a size_t which is the number of events that occurred
111+
* since the last time this callback was called.
112+
* Normally this is 1, but can be > 1 if events occurred before any
113+
* callback was set.
114+
*
115+
* Since this callback is called from the middleware, you should aim to make
116+
* it fast and not blocking.
117+
* If you need to do a lot of work or wait for some other event, you should
118+
* spin it off to another thread, otherwise you risk blocking the middleware.
119+
*
120+
* Calling it again will clear any previously set callback.
121+
*
122+
* This function is thread-safe.
123+
*
124+
* If you want more information available in the callback, like the qos event
125+
* or other information, you may use a lambda with captures or std::bind.
126+
*
127+
* \sa rmw_event_set_callback
128+
* \sa rcl_event_set_callback
129+
*
130+
* \param[in] callback functor to be called when a new event occurs
131+
*/
106132
void
107-
set_listener_callback(
108-
rmw_listener_callback_t callback,
109-
const void * user_data) const override;
133+
set_on_new_event_callback(std::function<void(size_t)> callback)
134+
{
135+
auto new_callback =
136+
[callback, this](size_t number_of_events) {
137+
try {
138+
callback(number_of_events);
139+
} catch (const std::exception & exception) {
140+
RCLCPP_ERROR_STREAM(
141+
// TODO(wjwwood): get this class access to the node logger it is associated with
142+
rclcpp::get_logger("rclcpp"),
143+
"rclcpp::QOSEventHandlerBase@" << this <<
144+
" caught " << rmw::impl::cpp::demangle(exception) <<
145+
" exception in user-provided callback for the 'on new event' callback: " <<
146+
exception.what());
147+
} catch (...) {
148+
RCLCPP_ERROR_STREAM(
149+
rclcpp::get_logger("rclcpp"),
150+
"rclcpp::QOSEventHandlerBase@" << this <<
151+
" caught unhandled exception in user-provided callback " <<
152+
"for the 'on new event' callback");
153+
}
154+
};
155+
156+
// Set it temporarily to the new callback, while we replace the old one.
157+
// This two-step setting, prevents a gap where the old std::function has
158+
// been replaced but the middleware hasn't been told about the new one yet.
159+
set_on_new_event_callback(
160+
rclcpp::detail::cpp_callback_trampoline<const void *, size_t>,
161+
static_cast<const void *>(&new_callback));
162+
163+
// Store the std::function to keep it in scope, also overwrites the existing one.
164+
on_new_event_callback_ = new_callback;
165+
166+
// Set it again, now using the permanent storage.
167+
set_on_new_event_callback(
168+
rclcpp::detail::cpp_callback_trampoline<const void *, size_t>,
169+
static_cast<const void *>(&on_new_event_callback_));
170+
}
110171

111172
protected:
173+
RCLCPP_PUBLIC
174+
void
175+
set_on_new_event_callback(rcl_event_callback_t callback, const void * user_data);
176+
112177
rcl_event_t event_handle_;
113178
size_t wait_set_event_index_;
179+
std::function<void(size_t)> on_new_event_callback_;
114180
};
115181

116182
template<typename EventCallbackT, typename ParentHandleT>
@@ -123,9 +189,8 @@ class QOSEventHandler : public QOSEventHandlerBase
123189
InitFuncT init_func,
124190
ParentHandleT parent_handle,
125191
EventTypeEnum event_type)
126-
: event_callback_(callback)
192+
: parent_handle_(parent_handle), event_callback_(callback)
127193
{
128-
parent_handle_ = parent_handle;
129194
event_handle_ = rcl_get_zero_initialized_event();
130195
rcl_ret_t ret = init_func(&event_handle_, parent_handle.get(), event_type);
131196
if (ret != RCL_RET_OK) {

0 commit comments

Comments
 (0)