Skip to content

Commit 81678ab

Browse files
committed
Add Client methods for checking and waiting for service availability (#85)
* Re-enable tests related to services Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Add Client methods for checking and waiting for service availability These methods are very useful for allowing a client to wait for a service to be available before making requests. Signed-off-by: Jacob Perron <jacob@openrobotics.org> * Refactor ClientTest to avoid repeatedly sending requests Signed-off-by: Jacob Perron <jacob@openrobotics.org>
1 parent 603a8f4 commit 81678ab

File tree

6 files changed

+145
-12
lines changed

6 files changed

+145
-12
lines changed

rcljava/CMakeLists.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,10 @@ if(BUILD_TESTING)
222222
set(${PROJECT_NAME}_test_sources
223223
"src/test/java/org/ros2/rcljava/RCLJavaTest.java"
224224
"src/test/java/org/ros2/rcljava/TimeTest.java"
225-
# "src/test/java/org/ros2/rcljava/client/ClientTest.java"
225+
"src/test/java/org/ros2/rcljava/client/ClientTest.java"
226226
"src/test/java/org/ros2/rcljava/node/NodeTest.java"
227-
# "src/test/java/org/ros2/rcljava/parameters/AsyncParametersClientTest.java"
228-
# "src/test/java/org/ros2/rcljava/parameters/SyncParametersClientTest.java"
227+
"src/test/java/org/ros2/rcljava/parameters/AsyncParametersClientTest.java"
228+
"src/test/java/org/ros2/rcljava/parameters/SyncParametersClientTest.java"
229229
"src/test/java/org/ros2/rcljava/publisher/PublisherTest.java"
230230
"src/test/java/org/ros2/rcljava/subscription/SubscriptionTest.java"
231231
"src/test/java/org/ros2/rcljava/timer/TimerTest.java"
@@ -234,9 +234,9 @@ if(BUILD_TESTING)
234234
set(${PROJECT_NAME}_testsuites
235235
"org.ros2.rcljava.RCLJavaTest"
236236
"org.ros2.rcljava.TimeTest"
237-
# "org.ros2.rcljava.client.ClientTest"
237+
"org.ros2.rcljava.client.ClientTest"
238238
"org.ros2.rcljava.node.NodeTest"
239-
# "org.ros2.rcljava.parameters.SyncParametersClientTest"
239+
"org.ros2.rcljava.parameters.SyncParametersClientTest"
240240
"org.ros2.rcljava.publisher.PublisherTest"
241241
"org.ros2.rcljava.subscription.SubscriptionTest"
242242
"org.ros2.rcljava.timer.TimerTest"

rcljava/include/org_ros2_rcljava_client_ClientImpl.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ JNICALL Java_org_ros2_rcljava_client_ClientImpl_nativeSendClientRequest(
3737
JNIEXPORT void
3838
JNICALL Java_org_ros2_rcljava_client_ClientImpl_nativeDispose(JNIEnv *, jclass, jlong, jlong);
3939

40+
/*
41+
* Class: org_ros2_rcljava_client_ClientImpl
42+
* Method: nativeIsServiceAvailable
43+
* Signature: (JJ)Z
44+
*/
45+
JNIEXPORT jboolean
46+
JNICALL Java_org_ros2_rcljava_client_ClientImpl_nativeIsServiceAvailable(
47+
JNIEnv *, jclass, jlong, jlong);
48+
49+
4050
#ifdef __cplusplus
4151
}
4252
#endif

rcljava/src/main/cpp/org_ros2_rcljava_client_ClientImpl.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <string>
2020

2121
#include "rcl/error_handling.h"
22+
#include "rcl/graph.h"
2223
#include "rcl/node.h"
2324
#include "rcl/rcl.h"
2425
#include "rmw/rmw.h"
@@ -96,3 +97,29 @@ Java_org_ros2_rcljava_client_ClientImpl_nativeDispose(
9697
rcljava_throw_rclexception(env, ret, msg);
9798
}
9899
}
100+
101+
JNIEXPORT jboolean JNICALL
102+
Java_org_ros2_rcljava_client_ClientImpl_nativeIsServiceAvailable(
103+
JNIEnv * env, jclass, jlong node_handle, jlong client_handle)
104+
{
105+
rcl_node_t * node = reinterpret_cast<rcl_node_t *>(node_handle);
106+
assert(node != NULL);
107+
rcl_client_t * client = reinterpret_cast<rcl_client_t *>(client_handle);
108+
assert(client != NULL);
109+
110+
bool is_ready;
111+
rcl_ret_t ret = rcl_service_server_is_available(node, client, &is_ready);
112+
if (RCL_RET_NODE_INVALID == ret) {
113+
if (node && !rcl_context_is_valid(node->context)) {
114+
// context is shutdown, do a soft failure
115+
return false;
116+
}
117+
}
118+
if (ret != RCL_RET_OK) {
119+
std::string msg =
120+
"Failed to check if service is available: " + std::string(rcl_get_error_string().str);
121+
rcl_reset_error();
122+
rcljava_throw_rclexception(env, ret, msg);
123+
}
124+
return is_ready;
125+
}

rcljava/src/main/java/org/ros2/rcljava/client/Client.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
package org.ros2.rcljava.client;
1717

18+
import java.time.Duration;
1819
import java.util.concurrent.Future;
1920

2021
import org.ros2.rcljava.concurrent.RCLFuture;
@@ -37,5 +38,34 @@ <U extends MessageDefinition, V extends MessageDefinition> Future<V> asyncSendRe
3738
<U extends MessageDefinition, V extends MessageDefinition> Future<V> asyncSendRequest(
3839
final U request, final Consumer<Future<V>> callback);
3940

41+
/**
42+
* Check if the service server is available.
43+
*
44+
* @return true if the client can talk to the service, false otherwise.
45+
*/
46+
boolean isServiceAvailable();
47+
48+
/**
49+
* Wait for the service server to be available.
50+
*
51+
* Blocks until the service is available or the ROS context is invalidated.
52+
*
53+
* @return true if the service is available, false if the ROS context was shutdown.
54+
*/
55+
boolean waitForService();
56+
57+
/**
58+
* Wait for the service server to be available.
59+
*
60+
* Blocks until the service is available or a timeout occurs.
61+
* Also returns if the ROS context is invalidated.
62+
*
63+
* @param timeout Time to wait for the service to be available.
64+
* A zero value causes this method to check if the service is available and return immediately.
65+
* A negative value is treated as an infinite timeout.
66+
* @return true if the service is available, false otherwise.
67+
*/
68+
boolean waitForService(Duration timeout);
69+
4070
String getServiceName();
4171
}

rcljava/src/main/java/org/ros2/rcljava/client/ClientImpl.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@
1515

1616
package org.ros2.rcljava.client;
1717

18+
import java.time.Duration;
1819
import java.lang.ref.WeakReference;
20+
import java.lang.InterruptedException;
21+
import java.lang.Long;
1922
import java.util.AbstractMap;
2023
import java.util.HashMap;
2124
import java.util.Map;
2225
import java.util.concurrent.Future;
26+
import java.util.concurrent.TimeUnit;
2327

2428
import org.ros2.rcljava.RCLJava;
2529
import org.ros2.rcljava.common.JNIUtils;
@@ -139,6 +143,58 @@ public final long getHandle() {
139143
return this.handle;
140144
}
141145

146+
private static native boolean nativeIsServiceAvailable(long nodeHandle, long handle);
147+
148+
/**
149+
* {@inheritDoc}
150+
*/
151+
public boolean isServiceAvailable() {
152+
Node node = this.nodeReference.get();
153+
if (node == null) {
154+
return false;
155+
}
156+
return nativeIsServiceAvailable(node.getHandle(), this.handle);
157+
}
158+
159+
/**
160+
* {@inheritDoc}
161+
*/
162+
public final boolean waitForService() {
163+
return waitForService(Duration.ofNanos(-1));
164+
}
165+
166+
/**
167+
* {@inheritDoc}
168+
*/
169+
public final boolean waitForService(Duration timeout) {
170+
long timeoutNano = timeout.toNanos();
171+
if (0L == timeoutNano) {
172+
return isServiceAvailable();
173+
}
174+
long startTime = System.nanoTime();
175+
long timeToWait = (timeoutNano >= 0L) ? timeoutNano : Long.MAX_VALUE;
176+
while (RCLJava.ok() && timeToWait > 0L) {
177+
// TODO(jacobperron): Wake up whenever graph changes instead of sleeping for a fixed duration
178+
try {
179+
TimeUnit.MILLISECONDS.sleep(10);
180+
} catch (InterruptedException ex) {
181+
Thread.currentThread().interrupt();
182+
return false;
183+
}
184+
185+
if (isServiceAvailable()) {
186+
return true;
187+
}
188+
189+
// If timeout is negative, timeToWait will always be greater than zero
190+
if (timeoutNano > 0L) {
191+
timeToWait = timeoutNano - (System.nanoTime() - startTime);
192+
}
193+
}
194+
195+
return false;
196+
}
197+
142198
public String getServiceName() {
143199
return this.serviceName;
144200
}

rcljava/src/test/java/org/ros2/rcljava/client/ClientTest.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@
2626
import org.junit.Test;
2727

2828
import java.lang.ref.WeakReference;
29+
import java.time.Duration;
2930

3031
import java.util.Arrays;
3132
import java.util.List;
33+
import java.util.concurrent.Future;
34+
import java.util.concurrent.TimeUnit;
3235

3336
import org.ros2.rcljava.RCLJava;
3437
import org.ros2.rcljava.concurrent.RCLFuture;
@@ -81,10 +84,10 @@ public static void tearDownOnce() {
8184

8285
@Test
8386
public final void testAdd() throws Exception {
84-
RCLFuture<rcljava.srv.AddTwoInts_Response> future =
87+
RCLFuture<rcljava.srv.AddTwoInts_Response> consumerFuture =
8588
new RCLFuture<rcljava.srv.AddTwoInts_Response>(new WeakReference<Node>(node));
8689

87-
TestClientConsumer clientConsumer = new TestClientConsumer(future);
90+
TestClientConsumer clientConsumer = new TestClientConsumer(consumerFuture);
8891

8992
Service<rcljava.srv.AddTwoInts> service = node.<rcljava.srv.AddTwoInts>createService(
9093
rcljava.srv.AddTwoInts.class, "add_two_ints", clientConsumer);
@@ -96,12 +99,19 @@ public final void testAdd() throws Exception {
9699
Client<rcljava.srv.AddTwoInts> client =
97100
node.<rcljava.srv.AddTwoInts>createClient(rcljava.srv.AddTwoInts.class, "add_two_ints");
98101

99-
while (RCLJava.ok() && !future.isDone()) {
100-
client.asyncSendRequest(request);
101-
RCLJava.spinOnce(node);
102-
}
102+
assertTrue(client.waitForService(Duration.ofSeconds(10)));
103+
104+
Future<rcljava.srv.AddTwoInts_Response> responseFuture = client.asyncSendRequest(request);
105+
106+
rcljava.srv.AddTwoInts_Response response = responseFuture.get(10, TimeUnit.SECONDS);
107+
108+
// Check that the message was received by the service
109+
assertTrue(consumerFuture.isDone());
110+
111+
// Check the contents of the response
112+
assertEquals(5, response.getSum());
103113

104-
assertEquals(5, future.get().getSum());
114+
// Cleanup
105115
client.dispose();
106116
assertEquals(0, client.getHandle());
107117
service.dispose();

0 commit comments

Comments
 (0)