Skip to content

Commit 8bffd25

Browse files
wjwwoodsloretz
authored andcommitted
add wait_for_action_server() for action clients (#598)
* add wait_for_action_server() for action clients Signed-off-by: William Woodall <[email protected]> * Handle negative timeouts in wait_for_service() and wait_for_action_server() methods. * Fix uncrustify errors. * Ignore take failure on services for connext
1 parent ef2014a commit 8bffd25

File tree

7 files changed

+155
-13
lines changed

7 files changed

+155
-13
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.DS_Store

Diff for: rclcpp/src/rclcpp/client.cpp

+10-5
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,15 @@ ClientBase::wait_for_service_nanoseconds(std::chrono::nanoseconds timeout)
129129
}
130130
// update the time even on the first loop to account for time spent in the first call
131131
// to this->server_is_ready()
132-
std::chrono::nanoseconds time_to_wait = timeout - (std::chrono::steady_clock::now() - start);
133-
if (timeout > std::chrono::nanoseconds(0) && time_to_wait < std::chrono::nanoseconds(0)) {
132+
std::chrono::nanoseconds time_to_wait =
133+
timeout > std::chrono::nanoseconds(0) ?
134+
timeout - (std::chrono::steady_clock::now() - start) :
135+
std::chrono::nanoseconds::max();
136+
if (time_to_wait < std::chrono::nanoseconds(0)) {
134137
// Do not allow the time_to_wait to become negative when timeout was originally positive.
135138
// Setting time_to_wait to 0 will allow one non-blocking wait because of the do-while.
136139
time_to_wait = std::chrono::nanoseconds(0);
137140
}
138-
// continue forever if timeout is negative, otherwise continue until out of time_to_wait
139141
do {
140142
if (!rclcpp::ok(this->context_)) {
141143
return false;
@@ -156,8 +158,11 @@ ClientBase::wait_for_service_nanoseconds(std::chrono::nanoseconds timeout)
156158
return true;
157159
}
158160
// server is not ready, loop if there is time left
159-
time_to_wait = timeout - (std::chrono::steady_clock::now() - start);
160-
} while (time_to_wait > std::chrono::nanoseconds(0) || timeout < std::chrono::nanoseconds(0));
161+
if (timeout > std::chrono::nanoseconds(0)) {
162+
time_to_wait = timeout - (std::chrono::steady_clock::now() - start);
163+
}
164+
// if timeout is negative, time_to_wait will never reach zero
165+
} while (time_to_wait > std::chrono::nanoseconds(0));
161166
return false; // timeout exceeded while waiting for the server to be ready
162167
}
163168

Diff for: rclcpp_action/include/rclcpp_action/client.hpp

+27-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
#include <rclcpp/macros.hpp>
1919
#include <rclcpp/node_interfaces/node_base_interface.hpp>
20+
#include <rclcpp/node_interfaces/node_logging_interface.hpp>
21+
#include <rclcpp/node_interfaces/node_graph_interface.hpp>
2022
#include <rclcpp/logger.hpp>
2123
#include <rclcpp/time.hpp>
2224
#include <rclcpp/waitable.hpp>
@@ -25,6 +27,7 @@
2527
#include <rosidl_typesupport_cpp/action_type_support.hpp>
2628

2729
#include <algorithm>
30+
#include <chrono>
2831
#include <functional>
2932
#include <future>
3033
#include <map>
@@ -57,6 +60,22 @@ class ClientBase : public rclcpp::Waitable
5760
RCLCPP_ACTION_PUBLIC
5861
virtual ~ClientBase();
5962

63+
/// Return true if there is an action server that is ready to take goal requests.
64+
RCLCPP_ACTION_PUBLIC
65+
bool
66+
action_server_is_ready() const;
67+
68+
/// Wait for action_server_is_ready() to become true, or until the given timeout is reached.
69+
template<typename RatioT = std::milli>
70+
bool
71+
wait_for_action_server(
72+
std::chrono::duration<int64_t, RatioT> timeout = std::chrono::duration<int64_t, RatioT>(-1))
73+
{
74+
return wait_for_action_server_nanoseconds(
75+
std::chrono::duration_cast<std::chrono::nanoseconds>(timeout)
76+
);
77+
}
78+
6079
// -------------
6180
// Waitables API
6281

@@ -107,11 +126,17 @@ class ClientBase : public rclcpp::Waitable
107126
RCLCPP_ACTION_PUBLIC
108127
ClientBase(
109128
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
129+
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
110130
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging,
111131
const std::string & action_name,
112132
const rosidl_action_type_support_t * type_support,
113133
const rcl_action_client_options_t & options);
114134

135+
/// Wait for action_server_is_ready() to become true, or until the given timeout is reached.
136+
RCLCPP_ACTION_PUBLIC
137+
bool
138+
wait_for_action_server_nanoseconds(std::chrono::nanoseconds timeout);
139+
115140
// -----------------------------------------------------
116141
// API for communication between ClientBase and Client<>
117142
using ResponseCallback = std::function<void (std::shared_ptr<void> response)>;
@@ -242,12 +267,13 @@ class Client : public ClientBase
242267

243268
Client(
244269
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
270+
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
245271
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging,
246272
const std::string & action_name,
247273
const rcl_action_client_options_t client_options = rcl_action_client_get_default_options()
248274
)
249275
: ClientBase(
250-
node_base, node_logging, action_name,
276+
node_base, node_graph, node_logging, action_name,
251277
rosidl_typesupport_cpp::get_action_type_support_handle<ActionT>(),
252278
client_options)
253279
{

Diff for: rclcpp_action/include/rclcpp_action/client_goal_handle_impl.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
#include <memory>
2121

22+
#include "rclcpp/logging.hpp"
23+
#include "rclcpp_action/client_goal_handle.hpp"
2224
#include "rclcpp_action/exceptions.hpp"
2325

2426
namespace rclcpp_action

Diff for: rclcpp_action/include/rclcpp_action/create_client.hpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ create_client(
6363
};
6464

6565
std::shared_ptr<Client<ActionT>> action_client(
66-
new Client<ActionT>(node->get_node_base_interface(), node->get_node_logging_interface(), name),
66+
new Client<ActionT>(
67+
node->get_node_base_interface(),
68+
node->get_node_graph_interface(),
69+
node->get_node_logging_interface(),
70+
name),
6771
deleter);
6872

6973
node->get_node_waitables_interface()->add_waitable(action_client, group);

Diff for: rclcpp_action/src/client.cpp

+86-2
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ class ClientBaseImpl
3434
public:
3535
ClientBaseImpl(
3636
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
37+
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
3738
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging,
3839
const std::string & action_name,
3940
const rosidl_action_type_support_t * type_support,
4041
const rcl_action_client_options_t & client_options)
41-
: node_handle(node_base->get_shared_rcl_node_handle()),
42+
: node_graph_(node_graph),
43+
node_handle(node_base->get_shared_rcl_node_handle()),
4244
logger(node_logging->get_logger().get_child("rclcpp_acton")),
4345
random_bytes_generator(std::random_device{} ())
4446
{
@@ -96,6 +98,8 @@ class ClientBaseImpl
9698
bool is_cancel_response_ready{false};
9799
bool is_result_response_ready{false};
98100

101+
rclcpp::Context::SharedPtr context_;
102+
rclcpp::node_interfaces::NodeGraphInterface::WeakPtr node_graph_;
99103
std::shared_ptr<rcl_action_client_t> client_handle{nullptr};
100104
std::shared_ptr<rcl_node_t> node_handle{nullptr};
101105
rclcpp::Logger logger;
@@ -117,18 +121,98 @@ class ClientBaseImpl
117121

118122
ClientBase::ClientBase(
119123
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
124+
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
120125
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging,
121126
const std::string & action_name,
122127
const rosidl_action_type_support_t * type_support,
123128
const rcl_action_client_options_t & client_options)
124-
: pimpl_(new ClientBaseImpl(node_base, node_logging, action_name, type_support, client_options))
129+
: pimpl_(new ClientBaseImpl(
130+
node_base, node_graph, node_logging, action_name, type_support, client_options))
125131
{
126132
}
127133

128134
ClientBase::~ClientBase()
129135
{
130136
}
131137

138+
bool
139+
ClientBase::action_server_is_ready() const
140+
{
141+
bool is_ready;
142+
rcl_ret_t ret = rcl_action_server_is_available(
143+
this->pimpl_->node_handle.get(),
144+
this->pimpl_->client_handle.get(),
145+
&is_ready);
146+
if (RCL_RET_NODE_INVALID == ret) {
147+
const rcl_node_t * node_handle = this->pimpl_->node_handle.get();
148+
if (node_handle && !rcl_context_is_valid(node_handle->context)) {
149+
// context is shutdown, do a soft failure
150+
return false;
151+
}
152+
}
153+
if (ret != RCL_RET_OK) {
154+
rclcpp::exceptions::throw_from_rcl_error(ret, "rcl_action_server_is_available failed");
155+
}
156+
return is_ready;
157+
}
158+
159+
bool
160+
ClientBase::wait_for_action_server_nanoseconds(std::chrono::nanoseconds timeout)
161+
{
162+
auto start = std::chrono::steady_clock::now();
163+
// make an event to reuse, rather than create a new one each time
164+
auto node_ptr = pimpl_->node_graph_.lock();
165+
if (!node_ptr) {
166+
throw rclcpp::exceptions::InvalidNodeError();
167+
}
168+
auto event = node_ptr->get_graph_event();
169+
// check to see if the server is ready immediately
170+
if (this->action_server_is_ready()) {
171+
return true;
172+
}
173+
if (timeout == std::chrono::nanoseconds(0)) {
174+
// check was non-blocking, return immediately
175+
return false;
176+
}
177+
// update the time even on the first loop to account for time spent in the first call
178+
// to this->server_is_ready()
179+
std::chrono::nanoseconds time_to_wait =
180+
timeout > std::chrono::nanoseconds(0) ?
181+
timeout - (std::chrono::steady_clock::now() - start) :
182+
std::chrono::nanoseconds::max();
183+
if (time_to_wait < std::chrono::nanoseconds(0)) {
184+
// Do not allow the time_to_wait to become negative when timeout was originally positive.
185+
// Setting time_to_wait to 0 will allow one non-blocking wait because of the do-while.
186+
time_to_wait = std::chrono::nanoseconds(0);
187+
}
188+
do {
189+
if (!rclcpp::ok(this->pimpl_->context_)) {
190+
return false;
191+
}
192+
// Limit each wait to 100ms to workaround an issue specific to the Connext RMW implementation.
193+
// A race condition means that graph changes for services becoming available may trigger the
194+
// wait set to wake up, but then not be reported as ready immediately after the wake up
195+
// (see https://github.com/ros2/rmw_connext/issues/201)
196+
// If no other graph events occur, the wait set will not be triggered again until the timeout
197+
// has been reached, despite the service being available, so we artificially limit the wait
198+
// time to limit the delay.
199+
node_ptr->wait_for_graph_change(
200+
event, std::min(time_to_wait, std::chrono::nanoseconds(RCL_MS_TO_NS(100))));
201+
// Because of the aforementioned race condition, we check if the service is ready even if the
202+
// graph event wasn't triggered.
203+
event->check_and_clear();
204+
if (this->action_server_is_ready()) {
205+
return true;
206+
}
207+
// server is not ready, loop if there is time left
208+
if (timeout > std::chrono::nanoseconds(0)) {
209+
time_to_wait = timeout - (std::chrono::steady_clock::now() - start);
210+
}
211+
// if timeout is negative, time_to_wait will never reach zero
212+
} while (time_to_wait > std::chrono::nanoseconds(0));
213+
return false; // timeout exceeded while waiting for the server to be ready
214+
}
215+
132216
rclcpp::Logger
133217
ClientBase::get_logger()
134218
{

Diff for: rclcpp_action/src/server.cpp

+24-4
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,17 @@ ServerBase::execute_goal_request_received()
217217
&request_header,
218218
message.get());
219219

220-
if (RCL_RET_OK != ret) {
220+
pimpl_->goal_request_ready_ = false;
221+
222+
if (RCL_RET_ACTION_SERVER_TAKE_FAILED == ret) {
223+
// Ignore take failure because connext fails if it receives a sample without valid data.
224+
// This happens when a client shuts down and connext receives a sample saying the client is
225+
// no longer alive.
226+
return;
227+
} else if (RCL_RET_OK != ret) {
221228
rclcpp::exceptions::throw_from_rcl_error(ret);
222229
}
223230

224-
pimpl_->goal_request_ready_ = false;
225231
GoalID uuid = get_goal_id_from_goal_request(message.get());
226232
convert(uuid, &goal_info);
227233

@@ -295,7 +301,14 @@ ServerBase::execute_cancel_request_received()
295301
&request_header,
296302
request.get());
297303

298-
if (RCL_RET_OK != ret) {
304+
pimpl_->cancel_request_ready_ = false;
305+
306+
if (RCL_RET_ACTION_SERVER_TAKE_FAILED == ret) {
307+
// Ignore take failure because connext fails if it receives a sample without valid data.
308+
// This happens when a client shuts down and connext receives a sample saying the client is
309+
// no longer alive.
310+
return;
311+
} else if (RCL_RET_OK != ret) {
299312
rclcpp::exceptions::throw_from_rcl_error(ret);
300313
}
301314

@@ -361,7 +374,14 @@ ServerBase::execute_result_request_received()
361374
ret = rcl_action_take_result_request(
362375
pimpl_->action_server_.get(), &request_header, result_request.get());
363376

364-
if (RCL_RET_OK != ret) {
377+
pimpl_->result_request_ready_ = false;
378+
379+
if (RCL_RET_ACTION_SERVER_TAKE_FAILED == ret) {
380+
// Ignore take failure because connext fails if it receives a sample without valid data.
381+
// This happens when a client shuts down and connext receives a sample saying the client is
382+
// no longer alive.
383+
return;
384+
} else if (RCL_RET_OK != ret) {
365385
rclcpp::exceptions::throw_from_rcl_error(ret);
366386
}
367387

0 commit comments

Comments
 (0)