diff --git a/src/main/java/com/google/devtools/build/lib/remote/GrpcRemoteCache.java b/src/main/java/com/google/devtools/build/lib/remote/GrpcRemoteCache.java index d865d0c40b5d41..f0bc22b7b810d6 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/GrpcRemoteCache.java +++ b/src/main/java/com/google/devtools/build/lib/remote/GrpcRemoteCache.java @@ -178,11 +178,6 @@ public void ensureInputsPresent( * allow throwing such an exception. Any caller must make sure to catch the * {@link StatusRuntimeException}. Note that the retrier implicitly catches it, so if this is used * in the context of {@link RemoteRetrier#execute}, that's perfectly safe. - * - *

This method also converts any NOT_FOUND code returned from the server into a - * {@link CacheNotFoundException}. TODO(olaola): this is not enough. NOT_FOUND can also be raised - * by execute, in which case the server should return the missing digest in the Status.details - * field. This should be part of the API. */ private void readBlob(Digest digest, OutputStream stream) throws IOException, StatusRuntimeException { @@ -191,29 +186,29 @@ private void readBlob(Digest digest, OutputStream stream) resourceName += options.remoteInstanceName + "/"; } resourceName += "blobs/" + digest.getHash() + "/" + digest.getSizeBytes(); - try { - Iterator replies = bsBlockingStub() - .read(ReadRequest.newBuilder().setResourceName(resourceName).build()); - while (replies.hasNext()) { - replies.next().getData().writeTo(stream); - } - } catch (StatusRuntimeException e) { - if (e.getStatus().getCode() == Status.Code.NOT_FOUND) { - throw new CacheNotFoundException(digest); - } - throw e; + Iterator replies = bsBlockingStub() + .read(ReadRequest.newBuilder().setResourceName(resourceName).build()); + while (replies.hasNext()) { + replies.next().getData().writeTo(stream); } } @Override protected void downloadBlob(Digest digest, Path dest) throws IOException, InterruptedException { - retrier.execute( - () -> { - try (OutputStream stream = dest.getOutputStream()) { - readBlob(digest, stream); - } - return null; - }); + try { + retrier.execute( + () -> { + try (OutputStream stream = dest.getOutputStream()) { + readBlob(digest, stream); + } + return null; + }); + } catch (RetryException e) { + if (RemoteRetrierUtils.causedByStatus(e, Status.Code.NOT_FOUND)) { + throw new CacheNotFoundException(digest); + } + throw e; + } } @Override @@ -221,12 +216,19 @@ protected byte[] downloadBlob(Digest digest) throws IOException, InterruptedExce if (digest.getSizeBytes() == 0) { return new byte[0]; } - return retrier.execute( - () -> { - ByteArrayOutputStream stream = new ByteArrayOutputStream((int) digest.getSizeBytes()); - readBlob(digest, stream); - return stream.toByteArray(); - }); + try { + return retrier.execute( + () -> { + ByteArrayOutputStream stream = new ByteArrayOutputStream((int) digest.getSizeBytes()); + readBlob(digest, stream); + return stream.toByteArray(); + }); + } catch (RetryException e) { + if (RemoteRetrierUtils.causedByStatus(e, Status.Code.NOT_FOUND)) { + throw new CacheNotFoundException(digest); + } + throw e; + } } @Override diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java index e0ba1fb4f406c7..f8ad0879025f92 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java +++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java @@ -245,7 +245,8 @@ private SpawnResult handleError(IOException exception, FileOutErr outErr) throws if (exception instanceof RetryException && RemoteRetrierUtils.causedByStatus((RetryException) exception, Code.UNAVAILABLE)) { status = Status.EXECUTION_FAILED_CATASTROPHICALLY; - } else if (cause instanceof CacheNotFoundException) { + } else if (exception instanceof CacheNotFoundException + || cause instanceof CacheNotFoundException) { status = Status.REMOTE_CACHE_FAILED; } else { status = Status.EXECUTION_FAILED; diff --git a/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java b/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java index c278581c2cbe8d..7167f2f48a8615 100644 --- a/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java +++ b/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java @@ -259,6 +259,7 @@ public PathFragment getExecPath() { @After public void tearDown() throws Exception { fakeServer.shutdownNow(); + fakeServer.awaitTermination(); } @Test @@ -791,6 +792,66 @@ public void read(ReadRequest request, StreamObserver responseObser } } + @Test + public void passRepeatedOrphanedCacheMissErrorWithStackTrace() throws Exception { + final Digest stdOutDigest = DIGEST_UTIL.computeAsUtf8("bloo"); + final ActionResult actionResult = + ActionResult.newBuilder().setStdoutDigest(stdOutDigest).build(); + serviceRegistry.addService( + new ActionCacheImplBase() { + @Override + public void getActionResult( + GetActionResultRequest request, StreamObserver responseObserver) { + responseObserver.onNext(actionResult); + responseObserver.onCompleted(); + } + }); + serviceRegistry.addService( + new ExecutionImplBase() { + @Override + public void execute(ExecuteRequest request, StreamObserver responseObserver) { + responseObserver.onNext( + Operation.newBuilder() + .setDone(true) + .setResponse( + Any.pack(ExecuteResponse.newBuilder().setResult(actionResult).build())) + .build()); + responseObserver.onCompleted(); + } + }); + serviceRegistry.addService( + new ContentAddressableStorageImplBase() { + @Override + public void findMissingBlobs( + FindMissingBlobsRequest request, + StreamObserver responseObserver) { + responseObserver.onNext(FindMissingBlobsResponse.getDefaultInstance()); + responseObserver.onCompleted(); + } + }); + serviceRegistry.addService( + new ByteStreamImplBase() { + @Override + public void read(ReadRequest request, StreamObserver responseObserver) { + assertThat(request.getResourceName().contains(stdOutDigest.getHash())).isTrue(); + responseObserver.onError(Status.NOT_FOUND.asRuntimeException()); + } + }); + + try { + client.exec(simpleSpawn, simplePolicy); + fail("Expected an exception"); + } catch (SpawnExecException expected) { + assertThat(expected.getSpawnResult().status()) + .isEqualTo(SpawnResult.Status.REMOTE_CACHE_FAILED); + assertThat(expected).hasMessageThat().contains(stdOutDigest.getHash()); + // Ensure we also got back the stack trace. + assertThat(expected) + .hasMessageThat() + .contains("passRepeatedOrphanedCacheMissErrorWithStackTrace"); + } + } + @Test public void remotelyReExecuteOrphanedCachedActions() throws Exception { final Digest stdOutDigest = DIGEST_UTIL.computeAsUtf8("stdout"); @@ -807,10 +868,19 @@ public void getActionResult( }); serviceRegistry.addService( new ByteStreamImplBase() { + private boolean first = true; + @Override public void read(ReadRequest request, StreamObserver responseObserver) { - // All reads are a cache miss. - responseObserver.onError(Status.NOT_FOUND.asRuntimeException()); + // First read is a cache miss, next read succeeds. + if (first) { + first = false; + responseObserver.onError(Status.NOT_FOUND.asRuntimeException()); + } else { + responseObserver.onNext( + ReadResponse.newBuilder().setData(ByteString.copyFromUtf8("stdout")).build()); + responseObserver.onCompleted(); + } } @Override @@ -858,14 +928,9 @@ public void findMissingBlobs( } }); - try { - client.exec(simpleSpawn, simplePolicy); - fail("Expected an exception"); - } catch (ExecException expected) { - assertThat(expected).hasMessageThat().contains("Missing digest"); - assertThat(expected) - .hasMessageThat() - .contains(DIGEST_UTIL.computeAsUtf8("stdout").toString()); - } + SpawnResult result = client.exec(simpleSpawn, simplePolicy); + assertThat(result.setupSuccess()).isTrue(); + assertThat(result.exitCode()).isEqualTo(0); + assertThat(outErr.outAsLatin1()).isEqualTo("stdout"); } }