ROS 2 rclcpp + rcl - rolling  rolling-a919a6e5
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 <memory>
21 #include <string>
22 
23 #include "rcl/graph.h"
24 #include "rcl/node.h"
25 #include "rcl/wait.h"
26 
27 #include "rclcpp/exceptions.hpp"
28 #include "rclcpp/node_interfaces/node_base_interface.hpp"
29 #include "rclcpp/node_interfaces/node_graph_interface.hpp"
30 #include "rclcpp/qos.hpp"
31 #include "rclcpp/utilities.hpp"
32 #include "rclcpp/logging.hpp"
33 
34 using rclcpp::ClientBase;
36 using rclcpp::exceptions::throw_from_rcl_error;
37 
38 ClientBase::ClientBase(
40  rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph)
41 : node_graph_(node_graph),
42  node_handle_(node_base->get_shared_rcl_node_handle()),
43  context_(node_base->get_context()),
44  node_logger_(rclcpp::get_node_logger(node_handle_.get()))
45 {
46  std::weak_ptr<rcl_node_t> weak_node_handle(node_handle_);
47  rcl_client_t * new_rcl_client = new rcl_client_t;
48  *new_rcl_client = rcl_get_zero_initialized_client();
49  client_handle_.reset(
50  new_rcl_client, [weak_node_handle](rcl_client_t * client)
51  {
52  auto handle = weak_node_handle.lock();
53  if (handle) {
54  if (rcl_client_fini(client, handle.get()) != RCL_RET_OK) {
55  RCLCPP_ERROR(
56  rclcpp::get_node_logger(handle.get()).get_child("rclcpp"),
57  "Error in destruction of rcl client handle: %s", rcl_get_error_string().str);
58  rcl_reset_error();
59  }
60  } else {
61  RCLCPP_ERROR(
62  rclcpp::get_logger("rclcpp"),
63  "Error in destruction of rcl client handle: "
64  "the Node Handle was destructed too early. You will leak memory");
65  }
66  delete client;
67  });
68 }
69 
70 bool
71 ClientBase::take_type_erased_response(void * response_out, rmw_request_id_t & request_header_out)
72 {
74  this->get_client_handle().get(),
75  &request_header_out,
76  response_out);
77  if (RCL_RET_CLIENT_TAKE_FAILED == ret) {
78  return false;
79  } else if (RCL_RET_OK != ret) {
80  rclcpp::exceptions::throw_from_rcl_error(ret);
81  }
82  return true;
83 }
84 
85 const char *
87 {
88  return rcl_client_get_service_name(this->get_client_handle().get());
89 }
90 
91 std::shared_ptr<rcl_client_t>
93 {
94  return client_handle_;
95 }
96 
97 std::shared_ptr<const rcl_client_t>
99 {
100  return client_handle_;
101 }
102 
103 bool
105 {
106  bool is_ready;
108  this->get_rcl_node_handle(),
109  this->get_client_handle().get(),
110  &is_ready);
111  if (RCL_RET_NODE_INVALID == ret) {
112  const rcl_node_t * node_handle = this->get_rcl_node_handle();
113  if (node_handle && !rcl_context_is_valid(node_handle->context)) {
114  // context is shutdown, do a soft failure
115  return false;
116  }
117  }
118  if (ret != RCL_RET_OK) {
119  throw_from_rcl_error(ret, "rcl_service_server_is_available failed");
120  }
121  return is_ready;
122 }
123 
124 bool
125 ClientBase::wait_for_service_nanoseconds(std::chrono::nanoseconds timeout)
126 {
127  auto start = std::chrono::steady_clock::now();
128  auto node_ptr = node_graph_.lock();
129  if (!node_ptr) {
130  throw InvalidNodeError();
131  }
132  // check to see if the server is ready immediately
133  if (this->service_is_ready()) {
134  return true;
135  }
136  if (timeout == std::chrono::nanoseconds(0)) {
137  // check was non-blocking, return immediately
138  return false;
139  }
140  // make an event to reuse, rather than create a new one each time
141  auto event = node_ptr->get_graph_event();
142  // update the time even on the first loop to account for time spent in the first call
143  // to this->server_is_ready()
144  std::chrono::nanoseconds time_to_wait =
145  timeout > std::chrono::nanoseconds(0) ?
146  timeout - (std::chrono::steady_clock::now() - start) :
147  std::chrono::nanoseconds::max();
148  if (time_to_wait < std::chrono::nanoseconds(0)) {
149  // Do not allow the time_to_wait to become negative when timeout was originally positive.
150  // Setting time_to_wait to 0 will allow one non-blocking wait because of the do-while.
151  time_to_wait = std::chrono::nanoseconds(0);
152  }
153  do {
154  if (!rclcpp::ok(this->context_)) {
155  return false;
156  }
157  // Limit each wait to 100ms to workaround an issue specific to the Connext RMW implementation.
158  // A race condition means that graph changes for services becoming available may trigger the
159  // wait set to wake up, but then not be reported as ready immediately after the wake up
160  // (see https://github.com/ros2/rmw_connext/issues/201)
161  // If no other graph events occur, the wait set will not be triggered again until the timeout
162  // has been reached, despite the service being available, so we artificially limit the wait
163  // time to limit the delay.
164  node_ptr->wait_for_graph_change(
165  event, std::min(time_to_wait, std::chrono::nanoseconds(RCL_MS_TO_NS(100))));
166  // Because of the aforementioned race condition, we check if the service is ready even if the
167  // graph event wasn't triggered.
168  event->check_and_clear();
169  if (this->service_is_ready()) {
170  return true;
171  }
172  // server is not ready, loop if there is time left
173  if (timeout > std::chrono::nanoseconds(0)) {
174  time_to_wait = timeout - (std::chrono::steady_clock::now() - start);
175  }
176  // if timeout is negative, time_to_wait will never reach zero
177  } while (time_to_wait > std::chrono::nanoseconds(0));
178  return false; // timeout exceeded while waiting for the server to be ready
179 }
180 
181 rcl_node_t *
182 ClientBase::get_rcl_node_handle()
183 {
184  return node_handle_.get();
185 }
186 
187 const rcl_node_t *
188 ClientBase::get_rcl_node_handle() const
189 {
190  return node_handle_.get();
191 }
192 
193 bool
195 {
196  return in_use_by_wait_set_.exchange(in_use_state);
197 }
198 
201 {
202  const rmw_qos_profile_t * qos =
203  rcl_client_request_publisher_get_actual_qos(client_handle_.get());
204  if (!qos) {
205  auto msg =
206  std::string("failed to get client's request publisher qos settings: ") +
207  rcl_get_error_string().str;
208  rcl_reset_error();
209  throw std::runtime_error(msg);
210  }
211 
212  rclcpp::QoS request_publisher_qos =
214 
215  return request_publisher_qos;
216 }
217 
220 {
221  const rmw_qos_profile_t * qos =
223  if (!qos) {
224  auto msg =
225  std::string("failed to get client's response subscription qos settings: ") +
226  rcl_get_error_string().str;
227  rcl_reset_error();
228  throw std::runtime_error(msg);
229  }
230 
231  rclcpp::QoS response_subscription_qos =
233 
234  return response_subscription_qos;
235 }
236 
237 void
238 ClientBase::set_on_new_response_callback(rcl_event_callback_t callback, const void * user_data)
239 {
241  client_handle_.get(),
242  callback,
243  user_data);
244 
245  if (RCL_RET_OK != ret) {
246  throw_from_rcl_error(ret, "failed to set the on new response callback for client");
247  }
248 }
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:194
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:200
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:92
void set_on_new_response_callback(std::function< void(size_t)> callback)
Set a callback to be called when each new response is received.
Definition: client.hpp:299
RCLCPP_PUBLIC const char * get_service_name() const
Return the name of the service.
Definition: client.cpp:86
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:219
RCLCPP_PUBLIC bool service_is_ready() const
Return if the service is ready.
Definition: client.cpp:104
Encapsulation of Quality of Service settings.
Definition: qos.hpp:116
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:431
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:291
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:229
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:56
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:407
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:449
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:440
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:755
Versions of rosidl_typesupport_cpp::get_message_type_support_handle that handle adapted types.
RCLCPP_PUBLIC Logger get_node_logger(const rcl_node_t *node)
Return a named logger using an rcl_node_t.
Definition: logger.cpp:45
RCLCPP_PUBLIC bool ok(rclcpp::Context::SharedPtr context=nullptr)
Check rclcpp's status.
RCLCPP_PUBLIC Logger get_logger(const std::string &name)
Return a named logger.
Definition: logger.cpp:34
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:63
#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