diff --git a/src/main/java/com/google/devtools/build/lib/remote/ByteStreamUploader.java b/src/main/java/com/google/devtools/build/lib/remote/ByteStreamUploader.java index 8ddf3a2af4672c..68eb671bd0fb87 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/ByteStreamUploader.java +++ b/src/main/java/com/google/devtools/build/lib/remote/ByteStreamUploader.java @@ -30,6 +30,7 @@ import com.google.bytestream.ByteStreamProto.WriteResponse; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Ascii; +import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.util.concurrent.AsyncCallable; import com.google.common.util.concurrent.Futures; @@ -389,7 +390,24 @@ private ListenableFuture upload(long pos) { channel -> { SettableFuture uploadResult = SettableFuture.create(); bsAsyncStub(channel).write(new Writer(resourceName, chunker, pos, uploadResult)); - return uploadResult; + return Futures.catchingAsync( + uploadResult, + Throwable.class, + throwable -> { + Preconditions.checkNotNull(throwable); + + Status status = Status.fromThrowable(throwable); + switch (status.getCode()) { + case ALREADY_EXISTS: + // Server indicated the blob already exists, so we translate the error to a + // successful upload. + return Futures.immediateFuture(chunker.getSize()); + + default: + return Futures.immediateFailedFuture(throwable); + } + }, + MoreExecutors.directExecutor()); }); } } diff --git a/src/test/java/com/google/devtools/build/lib/remote/GrpcCacheClientTest.java b/src/test/java/com/google/devtools/build/lib/remote/GrpcCacheClientTest.java index c71f7166bff510..24212e84978627 100644 --- a/src/test/java/com/google/devtools/build/lib/remote/GrpcCacheClientTest.java +++ b/src/test/java/com/google/devtools/build/lib/remote/GrpcCacheClientTest.java @@ -750,9 +750,12 @@ public void testUploadCacheMissesWithRetries() throws Exception { fakeFileCache.createScratchInput(ActionInputHelper.fromPath("bar"), "x"); final Digest bazDigest = fakeFileCache.createScratchInput(ActionInputHelper.fromPath("baz"), "z"); + final Digest foobarDigest = + fakeFileCache.createScratchInput(ActionInputHelper.fromPath("foobar"), "foobar"); final Path fooFile = execRoot.getRelative("a/foo"); final Path barFile = execRoot.getRelative("bar"); final Path bazFile = execRoot.getRelative("baz"); + final Path foobarFile = execRoot.getRelative("foobar"); ActionKey actionKey = DIGEST_UTIL.asActionKey(fooDigest); // Could be any key. barFile.setExecutable(true); serviceRegistry.addService( @@ -770,6 +773,7 @@ public void findMissingBlobs( .addMissingBlobDigests(fooDigest) .addMissingBlobDigests(barDigest) .addMissingBlobDigests(bazDigest) + .addMissingBlobDigests(foobarDigest) .build()); responseObserver.onCompleted(); } else { @@ -783,6 +787,7 @@ public void findMissingBlobs( rb.addOutputFilesBuilder().setPath("a/foo").setDigest(fooDigest).setIsExecutable(true); rb.addOutputFilesBuilder().setPath("bar").setDigest(barDigest).setIsExecutable(true); rb.addOutputFilesBuilder().setPath("baz").setDigest(bazDigest).setIsExecutable(true); + rb.addOutputFilesBuilder().setPath("foobar").setDigest(foobarDigest).setIsExecutable(true); ActionResult result = rb.build(); serviceRegistry.addService( new ActionCacheImplBase() { @@ -838,6 +843,9 @@ public void onNext(WriteRequest request) { } else if (resourceName.contains(bazDigest.getHash())) { assertThat(dataStr).isEqualTo("z"); size = 1; + } else if (resourceName.contains(foobarDigest.getHash())) { + responseObserver.onError(Status.ALREADY_EXISTS.asRuntimeException()); + return; } else { fail("Unexpected resource name in upload: " + resourceName); } @@ -875,9 +883,9 @@ public void onError(Throwable t) { actionKey, Action.getDefaultInstance(), Command.getDefaultInstance(), - ImmutableList.of(fooFile, barFile, bazFile)); - // 4 times for the errors, 3 times for the successful uploads. - Mockito.verify(mockByteStreamImpl, Mockito.times(7)) + ImmutableList.of(fooFile, barFile, bazFile, foobarFile)); + // 4 times for the errors, 4 times for the successful uploads. + Mockito.verify(mockByteStreamImpl, Mockito.times(8)) .write(ArgumentMatchers.>any()); }