@@ -31,10 +31,13 @@ class ClientBaseImpl
3131public:
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
116121ClientBase::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+
131210rclcpp::Logger
132211ClientBase::get_logger ()
133212{
0 commit comments