Consider the following non-blocking bidirectional streaming gRPC call:
this.channel = ManagedChannelBuilder.forAddress("NoSuchServer", 12345))
.usePlaintext()
.build();
this.kvStore = KVStoreServiceGrpc.newStub(channel);
this.executor = Executors.newSingleThreadExecutor();
this.executor.submit(() -> {
// This try-and-catch is my failed attempt at handling StatusRuntimeException
try {
this.toServer = kvStore.connect(new StreamObserver<KVMessage>() {
@Override
public void onNext(KVMessage resp) {
lock.lock();
response = resp;
responseReceived.signalAll();
lock.unlock();
}
/* Implementation for `onError` and `onCompletedd` redacted */
});
Thread.sleep(5000);
} catch (StatusRuntimeException e) {
// Never caught!
System.err.println("*** FAILED TO CONNECT TO SERVER!!! ***");
}
gate.countDown();
});
The above code will cause a StatusRuntimeException: UNAVAILABLE: Unable to resolve host NoSuchServer, as with any unresolvable host:
$ java -jar m1-client.jar
Command> connect NoSuchServer 12345
Mar 04, 2024 3:14:24 P.M. io.grpc.internal.ManagedChannelImpl$NameResolverListener handleErrorInSyncContext
WARNING: [Channel<1>: (NoSuchServer:12345)] Failed to resolve name. status=Status{code=UNAVAILABLE, description=Unable to resolve host NoSuchServer, cause=java.lang.RuntimeException: java.net.UnknownHostException: NoSuchServer: Name or service not known
at io.grpc.internal.DnsNameResolver.resolveAddresses(DnsNameResolver.java:223)
at io.grpc.internal.DnsNameResolver.doResolve(DnsNameResolver.java:282)
at io.grpc.internal.DnsNameResolver$Resolve.run(DnsNameResolver.java:318)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1589)
Caused by: java.net.UnknownHostException: NoSuchServer: Name or service not known
at java.base/java.net.Inet6AddressImpl.lookupAllHostAddr(Native Method)
at java.base/java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:52)
at java.base/java.net.InetAddress$PlatformResolver.lookupByName(InetAddress.java:1059)
at java.base/java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1673)
at java.base/java.net.InetAddress$NameServiceAddresses.get(InetAddress.java:1003)
at java.base/java.net.InetAddress.getAllByName0(InetAddress.java:1663)
at java.base/java.net.InetAddress.getAllByName(InetAddress.java:1528)
at io.grpc.internal.DnsNameResolver$JdkAddressResolver.resolveAddress(DnsNameResolver.java:632)
at io.grpc.internal.DnsNameResolver.resolveAddresses(DnsNameResolver.java:219)
... 5 more
}
io.grpc.StatusRuntimeException: UNAVAILABLE: Unable to resolve host NoSuchServer
at io.grpc.Status.asRuntimeException(Status.java:533)
at io.grpc.stub.ClientCalls$StreamObserverToCallListenerAdapter.onClose(ClientCalls.java:481)
at io.grpc.internal.DelayedClientCall$DelayedListener$3.run(DelayedClientCall.java:489)
at io.grpc.internal.DelayedClientCall$DelayedListener.drainPendingCallbacks(DelayedClientCall.java:528)
at io.grpc.internal.DelayedClientCall$1DrainListenerRunnable.runInContext(DelayedClientCall.java:317)
at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1589)
Caused by: java.lang.RuntimeException: java.net.UnknownHostException: NoSuchServer: Name or service not known
at io.grpc.internal.DnsNameResolver.resolveAddresses(DnsNameResolver.java:223)
at io.grpc.internal.DnsNameResolver.doResolve(DnsNameResolver.java:282)
at io.grpc.internal.DnsNameResolver$Resolve.run(DnsNameResolver.java:318)
... 3 more
Caused by: java.net.UnknownHostException: NoSuchServer: Name or service not known
at java.base/java.net.Inet6AddressImpl.lookupAllHostAddr(Native Method)
at java.base/java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:52)
at java.base/java.net.InetAddress$PlatformResolver.lookupByName(InetAddress.java:1059)
at java.base/java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1673)
at java.base/java.net.InetAddress$NameServiceAddresses.get(InetAddress.java:1003)
at java.base/java.net.InetAddress.getAllByName0(InetAddress.java:1663)
at java.base/java.net.InetAddress.getAllByName(InetAddress.java:1528)
at io.grpc.internal.DnsNameResolver$JdkAddressResolver.resolveAddress(DnsNameResolver.java:632)
at io.grpc.internal.DnsNameResolver.resolveAddresses(DnsNameResolver.java:219)
(Note: First thread dump is from the logger, which can be turned off; but that's a separate discussion)
It appears that the StatusRuntimeException is thrown from internal lib calls (perhaps as an async task at inner levels).
Unlike with blocking gRPC calls, which the client application can catch exceptions thrown from lower levels using a try-and-catch in the above manner, I cannot do the same for non-blocking gRPC calls.
How can I catch StatusRuntimeExceptions at the app level?
P.S. I've searched high and low for an answer and thought that there'd be a simple way to handle exceptions (in general) that are thrown from internal lib calls, but these "internal exceptions" don't seem to propagate their way up to the app level.
grpc-java's internals don't throw. Instead they report back a
Status. For blocking stubs, that status is converted into aStatusRuntimeExceptionand thrown. For async stubs, it is also converted into an exception, but is delivered toonError(Throwable)as an argument.For more information, see the
asyncCall()function of the error handling example.