Skip to content

Commit c7c1738

Browse files
committed
add wait_for_action_server() for action clients
Signed-off-by: William Woodall <william@osrfoundation.org>
1 parent fa62fbe commit c7c1738

2 files changed

Lines changed: 108 additions & 4 deletions

File tree

rclcpp_action/include/rclcpp_action/client.hpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <rosidl_typesupport_cpp/action_type_support.hpp>
2626

2727
#include <algorithm>
28+
#include <chrono>
2829
#include <functional>
2930
#include <future>
3031
#include <map>
@@ -52,13 +53,30 @@ class ClientBase : public rclcpp::Waitable
5253
RCLCPP_ACTION_PUBLIC
5354
ClientBase(
5455
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
56+
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
5557
const std::string & action_name,
5658
const rosidl_action_type_support_t * type_support,
5759
const rcl_action_client_options_t & options);
5860

5961
RCLCPP_ACTION_PUBLIC
6062
virtual ~ClientBase();
6163

64+
/// Return true if there is an action server that is ready to take goal requests.
65+
RCLCPP_ACTION_PUBLIC
66+
bool
67+
action_server_is_ready() const;
68+
69+
/// Wait for action_server_is_ready() to become true, or until the given timeout is reached.
70+
template<typename RatioT = std::milli>
71+
bool
72+
wait_for_action_server(
73+
std::chrono::duration<int64_t, RatioT> timeout = std::chrono::duration<int64_t, RatioT>(-1))
74+
{
75+
return wait_for_action_server_nanoseconds(
76+
std::chrono::duration_cast<std::chrono::nanoseconds>(timeout)
77+
);
78+
}
79+
6280
RCLCPP_ACTION_PUBLIC
6381
size_t
6482
get_number_of_ready_subscriptions() override;
@@ -92,6 +110,11 @@ class ClientBase : public rclcpp::Waitable
92110
execute() override;
93111

94112
protected:
113+
/// Wait for action_server_is_ready() to become true, or until the given timeout is reached.
114+
RCLCPP_ACTION_PUBLIC
115+
bool
116+
wait_for_action_server_nanoseconds(std::chrono::nanoseconds timeout);
117+
95118
using ResponseCallback =
96119
std::function<void(std::shared_ptr<void> response)>;
97120

@@ -167,18 +190,20 @@ class Client : public ClientBase
167190

168191
Client(
169192
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
193+
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
170194
const std::string & action_name
171195
)
172-
: Client(node_base, action_name, rcl_action_client_get_default_options())
196+
: Client(node_base, node_graph, action_name, rcl_action_client_get_default_options())
173197
{
174198
}
175199

176200
Client(
177201
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
202+
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
178203
const std::string & action_name, const rcl_action_client_options_t & client_options
179204
)
180205
: ClientBase(
181-
node_base, action_name,
206+
node_base, node_graph, action_name,
182207
rosidl_typesupport_cpp::get_action_type_support_handle<ACTION>(),
183208
client_options)
184209
{

rclcpp_action/src/client.cpp

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,13 @@ class ClientBaseImpl
3131
public:
3232
ClientBaseImpl(
3333
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
34+
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
3435
const std::string & action_name,
3536
const rosidl_action_type_support_t * type_support,
3637
const rcl_action_client_options_t & client_options)
37-
: node_handle(node_base->get_shared_rcl_node_handle()),
38+
: context_(node_base->get_context()),
39+
node_graph_(node_graph),
40+
node_handle(node_base->get_shared_rcl_node_handle()),
3841
logger(rclcpp::get_logger(rcl_node_get_logger_name(
3942
node_handle.get())).get_child("rclcpp")),
4043
random_bytes_generator(std::random_device{}())
@@ -93,6 +96,8 @@ class ClientBaseImpl
9396
bool is_cancel_response_ready{false};
9497
bool is_result_response_ready{false};
9598

99+
rclcpp::Context::SharedPtr context_;
100+
rclcpp::node_interfaces::NodeGraphInterface::WeakPtr node_graph_;
96101
std::shared_ptr<rcl_action_client_t> client_handle{nullptr};
97102
std::shared_ptr<rcl_node_t> node_handle{nullptr};
98103
rclcpp::Logger logger;
@@ -115,11 +120,12 @@ class ClientBaseImpl
115120

116121
ClientBase::ClientBase(
117122
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
123+
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
118124
const std::string & action_name,
119125
const rosidl_action_type_support_t * type_support,
120126
const rcl_action_client_options_t & client_options)
121127
: pimpl_(new ClientBaseImpl(
122-
node_base, action_name,
128+
node_base, node_graph, action_name,
123129
type_support, client_options))
124130
{
125131
}
@@ -128,6 +134,79 @@ ClientBase::~ClientBase()
128134
{
129135
}
130136

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

0 commit comments

Comments
 (0)