How to handle (catch) io.grpc.StatusRuntimeException: UNAVAILABLE: Unable to resolve host

116 Views Asked by At

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.

1

There are 1 best solutions below

5
Eric Anderson On

grpc-java's internals don't throw. Instead they report back a Status. For blocking stubs, that status is converted into a StatusRuntimeException and thrown. For async stubs, it is also converted into an exception, but is delivered to onError(Throwable) as an argument.

this.toServer = kvStore.connect(new StreamObserver<KVMessage>() {
     @Override
     public void onNext(KVMessage resp) {
         lock.lock();
         response = resp;
         responseReceived.signalAll();
         lock.unlock();
     }

     @Override
     public void onError(Throwable t) {
         Status status = Status.fromThrowable(t);
         // This is the equivalent of the catch block for blocking stubs.
         // Do custom error handling here.
         System.err.println("*** FAILED TO CONNECT TO SERVER!!! ***");
     }
     /* Implementation for `onCompleted` redacted */
});

For more information, see the asyncCall() function of the error handling example.