ROS 2 rclcpp + rcl - rolling  rolling-20536064
ROS 2 C++ Client Library with ROS Client Library
client.cpp
1 // Copyright 2015 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 #include "rclcpp/client.hpp"
16 
17 #include <algorithm>
18 #include <chrono>
19 #include <cstdio>
20 #include <functional>
21 #include <memory>
22 #include <mutex>
23 #include <stdexcept>
24 #include <string>
25 
26 #include "rcl/graph.h"
27 #include "rcl/node.h"
28 #include "rcl/wait.h"
29 
30 #include "rmw/impl/cpp/demangle.hpp"
31 
32 #include "rclcpp/detail/cpp_callback_trampoline.hpp"
33 #include "rclcpp/exceptions.hpp"
34 #include "rclcpp/node_interfaces/node_base_interface.hpp"
35 #include "rclcpp/node_interfaces/node_graph_interface.hpp"
36 #include "rclcpp/qos.hpp"
37 #include "rclcpp/utilities.hpp"
38 #include "rclcpp/logging.hpp"
39 
40 using rclcpp::ClientBase;
42 using rclcpp::exceptions::throw_from_rcl_error;
43 
44 ClientBase::ClientBase(
46  const rclcpp::node_interfaces::NodeGraphInterface::SharedPtr & node_graph)
47 : node_graph_(node_graph),
48  node_handle_(node_base->get_shared_rcl_node_handle()),
49  context_(node_base->get_context()),
50  node_logger_(rclcpp::get_node_logger(node_handle_.get()))
51 {
52  std::weak_ptr<rcl_node_t> weak_node_handle(node_handle_);
53  rcl_client_t * new_rcl_client = new rcl_client_t;
54  *new_rcl_client = rcl_get_zero_initialized_client();
55  client_handle_.reset(
56  new_rcl_client, [weak_node_handle](rcl_client_t * client)
57  {
58  auto handle = weak_node_handle.lock();
59  if (handle) {
60  if (rcl_client_fini(client, handle.get()) != RCL_RET_OK) {
61  RCLCPP_ERROR(
62  rclcpp::get_node_logger(handle.get()).get_child("rclcpp"),
63  "Error in destruction of rcl client handle: %s", rcl_get_error_string().str);
64  rcl_reset_error();
65  }
66  } else {
67  RCLCPP_ERROR(
68  rclcpp::get_logger("rclcpp"),
69  "Error in destruction of rcl client handle: "
70  "the Node Handle was destructed too early. You will leak memory");
71  }
72  delete client;
73  });
74 }
75 
76 bool
77 ClientBase::take_type_erased_response(void * response_out, rmw_request_id_t & request_header_out)
78 {
80  this->get_client_handle().get(),
81  &request_header_out,
82  response_out);
83  if (RCL_RET_CLIENT_TAKE_FAILED == ret) {
84  return false;
85  } else if (RCL_RET_OK != ret) {
86  rclcpp::exceptions::throw_from_rcl_error(ret);
87  }
88  return true;
89 }
90 
91 const char *
93 {
94  return rcl_client_get_service_name(this->get_client_handle().get());
95 }
96 
97 std::shared_ptr<rcl_client_t>
99 {
100  return client_handle_;
101 }
102 
103 std::shared_ptr<const rcl_client_t>
105 {
106  return client_handle_;
107 }
108 
109 bool
111 {
112  bool is_ready;
114  this->get_rcl_node_handle(),
115  this->get_client_handle().get(),
116  &is_ready);
117  if (RCL_RET_NODE_INVALID == ret) {
118  const rcl_node_t * node_handle = this->get_rcl_node_handle();
119  if (node_handle && !rcl_context_is_valid(node_handle->context)) {
120  // context is shutdown, do a soft failure
121  return false;
122  }
123  }
124  if (ret != RCL_RET_OK) {
125  throw_from_rcl_error(ret, "rcl_service_server_is_available failed");
126  }
127  return is_ready;
128 }
129 
130 bool
131 ClientBase::wait_for_service_nanoseconds(std::chrono::nanoseconds timeout)
132 {
133  auto start = std::chrono::steady_clock::now();
134  auto node_ptr = node_graph_.lock();
135  if (!node_ptr) {
136  throw InvalidNodeError();
137  }
138  // check to see if the server is ready immediately
139  if (this->service_is_ready()) {
140  return true;
141  }
142  if (timeout == std::chrono::nanoseconds(0)) {
143  // check was non-blocking, return immediately
144  return false;
145  }
146  // make an event to reuse, rather than create a new one each time
147  auto event = node_ptr->get_graph_event();
148  // update the time even on the first loop to account for time spent in the first call
149  // to this->server_is_ready()
150  std::chrono::nanoseconds time_to_wait =
151  timeout > std::chrono::nanoseconds(0) ?
152  timeout - (std::chrono::steady_clock::now() - start) :
153  std::chrono::nanoseconds::max();
154  if (time_to_wait < std::chrono::nanoseconds(0)) {
155  // Do not allow the time_to_wait to become negative when timeout was originally positive.
156  // Setting time_to_wait to 0 will allow one non-blocking wait because of the do-while.
157  time_to_wait = std::chrono::nanoseconds(0);
158  }
159  do {
160  if (!rclcpp::ok(this->context_)) {
161  return false;
162  }
163  // Limit each wait to 100ms to workaround an issue specific to the Connext RMW implementation.
164  // A race condition means that graph changes for services becoming available may trigger the
165  // wait set to wake up, but then not be reported as ready immediately after the wake up
166  // (see https://github.com/ros2/rmw_connext/issues/201)
167  // If no other graph events occur, the wait set will not be triggered again until the timeout
168  // has been reached, despite the service being available, so we artificially limit the wait
169  // time to limit the delay.
170  node_ptr->wait_for_graph_change(
171  event, std::min(time_to_wait, std::chrono::nanoseconds(RCL_MS_TO_NS(100))));
172  // Because of the aforementioned race condition, we check if the service is ready even if the
173  // graph event wasn't triggered.
174  event->check_and_clear();
175  if (this->service_is_ready()) {
176  return true;
177  }
178  // server is not ready, loop if there is time left
179  if (timeout > std::chrono::nanoseconds(0)) {
180  time_to_wait = timeout - (std::chrono::steady_clock::now() - start);
181  }
182  // if timeout is negative, time_to_wait will never reach zero
183  } while (time_to_wait > std::chrono::nanoseconds(0));
184  return false; // timeout exceeded while waiting for the server to be ready
185 }
186 
187 rcl_node_t *
188 ClientBase::get_rcl_node_handle()
189 {
190  return node_handle_.get();
191 }
192 
193 const rcl_node_t *
194 ClientBase::get_rcl_node_handle() const
195 {
196  return node_handle_.get();
197 }
198 
199 bool
201 {
202  return in_use_by_wait_set_.exchange(in_use_state);
203 }
204 
207 {
208  const rmw_qos_profile_t * qos =
209  rcl_client_request_publisher_get_actual_qos(client_handle_.get());
210  if (!qos) {
211  auto msg =
212  std::string("failed to get client's request publisher qos settings: ") +
213  rcl_get_error_string().str;
214  rcl_reset_error();
215  throw std::runtime_error(msg);
216  }
217 
218  rclcpp::QoS request_publisher_qos =
220 
221  return request_publisher_qos;
222 }
223 
226 {
227  const rmw_qos_profile_t * qos =
229  if (!qos) {
230  auto msg =
231  std::string("failed to get client's response subscription qos settings: ") +
232  rcl_get_error_string().str;
233  rcl_reset_error();
234  throw std::runtime_error(msg);
235  }
236 
237  rclcpp::QoS response_subscription_qos =
239 
240  return response_subscription_qos;
241 }
242 
243 void
244 ClientBase::set_on_new_response_callback(rcl_event_callback_t callback, const void * user_data)
245 {
247  client_handle_.get(),
248  callback,
249  user_data);
250 
251  if (RCL_RET_OK != ret) {
252  throw_from_rcl_error(ret, "failed to set the on new response callback for client");
253  }
254 }
255 
256 void
257 ClientBase::set_on_new_response_callback(const std::function<void(size_t)> & callback)
258 {
259  if (!callback) {
260  throw std::invalid_argument(
261  "The callback passed to set_on_new_response_callback "
262  "is not callable.");
263  }
264 
265  auto new_callback =
266  [callback, this](size_t number_of_responses) {
267  try {
268  callback(number_of_responses);
269  } catch (const std::exception & exception) {
270  RCLCPP_ERROR_STREAM(
271  node_logger_,
272  "rclcpp::ClientBase@" << this <<
273  " caught " << rmw::impl::cpp::demangle(exception) <<
274  " exception in user-provided callback for the 'on new response' callback: " <<
275  exception.what());
276  } catch (...) {
277  RCLCPP_ERROR_STREAM(
278  node_logger_,
279  "rclcpp::ClientBase@" << this <<
280  " caught unhandled exception in user-provided callback " <<
281  "for the 'on new response' callback");
282  }
283  };
284 
285  std::lock_guard<std::recursive_mutex> lock(callback_mutex_);
286 
287  // Set it temporarily to the new callback, while we replace the old one.
288  // This two-step setting, prevents a gap where the old std::function has
289  // been replaced but the middleware hasn't been told about the new one yet.
291  rclcpp::detail::cpp_callback_trampoline<decltype(new_callback), const void *, size_t>,
292  static_cast<const void *>(&new_callback));
293 
294  // Store the std::function to keep it in scope, also overwrites the existing one.
295  on_new_response_callback_ = new_callback;
296 
297  // Set it again, now using the permanent storage.
299  rclcpp::detail::cpp_callback_trampoline<
300  decltype(on_new_response_callback_), const void *, size_t>,
301  static_cast<const void *>(&on_new_response_callback_));
302 }
303 
304 void
306 {
307  std::lock_guard<std::recursive_mutex> lock(callback_mutex_);
308  if (on_new_response_callback_) {
309  set_on_new_response_callback(nullptr, nullptr);
310  on_new_response_callback_ = nullptr;
311  }
312 }
RCLCPP_PUBLIC bool exchange_in_use_by_wait_set_state(bool in_use_state)
Exchange the "in use by wait set" state for this client.
Definition: client.cpp:200
RCLCPP_PUBLIC rclcpp::QoS get_request_publisher_actual_qos() const
Get the actual request publsher QoS settings, after the defaults have been determined.
Definition: client.cpp:206
RCLCPP_PUBLIC std::shared_ptr< rcl_client_t > get_client_handle()
Return the rcl_client_t client handle in a std::shared_ptr.
Definition: client.cpp:98
RCLCPP_PUBLIC void set_on_new_response_callback(const std::function< void(size_t)> &callback)
Set a callback to be called when each new response is received.
Definition: client.cpp:257
RCLCPP_PUBLIC void clear_on_new_response_callback()
Unset the callback registered for new responses, if any.
Definition: client.cpp:305
RCLCPP_PUBLIC const char * get_service_name() const
Return the name of the service.
Definition: client.cpp:92
RCLCPP_PUBLIC rclcpp::QoS get_response_subscription_actual_qos() const
Get the actual response subscription QoS settings, after the defaults have been determined.
Definition: client.cpp:225
RCLCPP_PUBLIC bool service_is_ready() const
Return if the service is ready.
Definition: client.cpp:110
Encapsulation of Quality of Service settings.
Definition: qos.hpp:114
Thrown when a method is trying to use a node, but it is invalid.
Definition: exceptions.hpp:35
Pure virtual interface class for the NodeBase part of the Node API.
RCL_PUBLIC RCL_WARN_UNUSED const rmw_qos_profile_t * rcl_client_request_publisher_get_actual_qos(const rcl_client_t *client)
Get the actual qos settings of the client's request publisher.
Definition: client.c:421
RCL_PUBLIC RCL_WARN_UNUSED const char * rcl_client_get_service_name(const rcl_client_t *client)
Get the name of the service that this client will request a response from.
Definition: client.c:281
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_client_fini(rcl_client_t *client, rcl_node_t *node)
Finalize a rcl_client_t.
Definition: client.c:219
RCL_PUBLIC RCL_WARN_UNUSED rcl_client_t rcl_get_zero_initialized_client(void)
Return a rcl_client_t struct with members set to NULL.
Definition: client.c:45
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_take_response(const rcl_client_t *client, rmw_request_id_t *request_header, void *ros_response)
backwards compatibility function that takes a rmw_request_id_t only
Definition: client.c:397
struct rcl_client_s rcl_client_t
Structure which encapsulates a ROS Client.
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_client_set_on_new_response_callback(const rcl_client_t *client, rcl_event_callback_t callback, const void *user_data)
Set the on new response callback function for the client.
Definition: client.c:439
RCL_PUBLIC RCL_WARN_UNUSED const rmw_qos_profile_t * rcl_client_response_subscription_get_actual_qos(const rcl_client_t *client)
Get the actual qos settings of the client's response subscription.
Definition: client.c:430
RCL_PUBLIC RCL_WARN_UNUSED bool rcl_context_is_valid(const rcl_context_t *context)
Return true if the given context is currently valid, otherwise false.
Definition: context.c:94
RCL_PUBLIC RCL_WARN_UNUSED rcl_ret_t rcl_service_server_is_available(const rcl_node_t *node, const rcl_client_t *client, bool *is_available)
Check if a service server is available for the given service client.
Definition: graph.c:883
Versions of rosidl_typesupport_cpp::get_message_type_support_handle that handle adapted types.
RCLCPP_PUBLIC bool ok(const rclcpp::Context::SharedPtr &context=rclcpp::contexts::get_global_default_context())
Check rclcpp's status.
RCLCPP_PUBLIC Logger get_node_logger(const rcl_node_t *node)
Return a named logger using an rcl_node_t.
Definition: logger.cpp:43
RCLCPP_PUBLIC Logger get_logger(const std::string &name)
Return a named logger.
Definition: logger.cpp:32
Structure which encapsulates a ROS Client.
Definition: client.h:43
Structure which encapsulates a ROS Node.
Definition: node.h:45
rcl_context_t * context
Context associated with this node.
Definition: node.h:47
static QoSInitialization from_rmw(const rmw_qos_profile_t &rmw_qos)
Create a QoSInitialization from an existing rmw_qos_profile_t, using its history and depth.
Definition: qos.cpp:70
#define RCL_MS_TO_NS
Convenience macro to convert milliseconds to nanoseconds.
Definition: time.h:34
#define RCL_RET_CLIENT_TAKE_FAILED
Failed to take a response from the client return code.
Definition: types.h:81
#define RCL_RET_OK
Success return code.
Definition: types.h:27
#define RCL_RET_NODE_INVALID
Invalid rcl_node_t given return code.
Definition: types.h:59
rmw_ret_t rcl_ret_t
The type that holds an rcl return code.
Definition: types.h:24