Wrap FindMissingBlobs exceptions in IOException

Consistent with #7860, present an IOException with context to ensure
fallback if configured, rather than an unhandled RuntimeException and
bazel crash, when issuing FindMissingBlobs requests.

Closes #10663.

PiperOrigin-RevId: 295957563
diff --git a/src/main/java/com/google/devtools/build/lib/remote/GrpcCacheClient.java b/src/main/java/com/google/devtools/build/lib/remote/GrpcCacheClient.java
index 1c07c87..1750526 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/GrpcCacheClient.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/GrpcCacheClient.java
@@ -191,16 +191,30 @@
       getMissingDigestCalls.add(getMissingDigests(requestBuilder.build()));
     }
 
-    return Futures.whenAllSucceed(getMissingDigestCalls)
-        .call(
-            () -> {
-              ImmutableSet.Builder<Digest> result = ImmutableSet.builder();
-              for (ListenableFuture<FindMissingBlobsResponse> callFuture : getMissingDigestCalls) {
-                result.addAll(callFuture.get().getMissingBlobDigestsList());
-              }
-              return result.build();
-            },
-            MoreExecutors.directExecutor());
+    ListenableFuture<ImmutableSet<Digest>> success =
+        Futures.whenAllSucceed(getMissingDigestCalls)
+            .call(
+                () -> {
+                  ImmutableSet.Builder<Digest> result = ImmutableSet.builder();
+                  for (ListenableFuture<FindMissingBlobsResponse> callFuture :
+                      getMissingDigestCalls) {
+                    result.addAll(callFuture.get().getMissingBlobDigestsList());
+                  }
+                  return result.build();
+                },
+                MoreExecutors.directExecutor());
+    return Futures.catchingAsync(
+        success,
+        RuntimeException.class,
+        (e) ->
+            Futures.immediateFailedFuture(
+                new IOException(
+                    String.format(
+                        "findMissingBlobs(%d) for %s",
+                        requestBuilder.getBlobDigestsCount(),
+                        TracingMetadataUtils.fromCurrentContext().getActionId()),
+                    e)),
+        MoreExecutors.directExecutor());
   }
 
   private ListenableFuture<FindMissingBlobsResponse> getMissingDigests(