aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/google/devtools/build/lib/remote/GrpcRemoteCache.java77
-rw-r--r--src/main/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutor.java7
-rw-r--r--src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java141
-rw-r--r--src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnStrategy.java70
-rw-r--r--src/main/java/com/google/devtools/build/lib/remote/Retrier.java10
-rw-r--r--src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutionClientTest.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/remote/RetrierTest.java30
7 files changed, 212 insertions, 125 deletions
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 036fa93f4f..55ee38ab6e 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
@@ -22,6 +22,7 @@ import com.google.bytestream.ByteStreamProto.ReadResponse;
import com.google.bytestream.ByteStreamProto.WriteRequest;
import com.google.bytestream.ByteStreamProto.WriteResponse;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.actions.ActionInput;
@@ -209,16 +210,21 @@ public class GrpcRemoteCache implements RemoteActionCache {
file.getContent().writeTo(stream);
}
} else {
- retrier.execute(
- () -> {
- try (OutputStream stream = path.getOutputStream()) {
- Iterator<ReadResponse> replies = readBlob(digest);
- while (replies.hasNext()) {
- replies.next().getData().writeTo(stream);
+ try {
+ retrier.execute(
+ () -> {
+ try (OutputStream stream = path.getOutputStream()) {
+ Iterator<ReadResponse> replies = readBlob(digest);
+ while (replies.hasNext()) {
+ replies.next().getData().writeTo(stream);
+ }
+ return null;
}
- return null;
- }
- });
+ });
+ } catch (RetryException e) {
+ Throwables.throwIfInstanceOf(e.getCause(), CacheNotFoundException.class);
+ throw e;
+ }
}
}
path.setExecutable(file.getIsExecutable());
@@ -250,7 +256,19 @@ public class GrpcRemoteCache implements RemoteActionCache {
}
}
- private Iterator<ReadResponse> readBlob(Digest digest) throws CacheNotFoundException {
+ /**
+ * This method can throw {@link StatusRuntimeException}, but the RemoteCache interface does not
+ * 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 Retrier#execute}, that's perfectly safe.
+ *
+ * <p>On the other hand, this method can also throw {@link CacheNotFoundException}, but the
+ * retrier also implicitly catches that and wraps it in a {@link RetryException}, so any caller
+ * that wants to propagate the {@link CacheNotFoundException} needs to catch
+ * {@link RetryException} and rethrow the cause if it is a {@link CacheNotFoundException}.
+ */
+ private Iterator<ReadResponse> readBlob(Digest digest)
+ throws CacheNotFoundException, StatusRuntimeException {
String resourceName = "";
if (!options.remoteInstanceName.isEmpty()) {
resourceName += options.remoteInstanceName + "/";
@@ -282,10 +300,12 @@ public class GrpcRemoteCache implements RemoteActionCache {
.setActionDigest(actionKey.getDigest())
.setActionResult(result)
.build()));
- } catch (StatusRuntimeException e) {
- if (e.getStatus().getCode() != Status.Code.UNIMPLEMENTED) {
- throw e;
+ } catch (RetryException e) {
+ if (e.causedByStatusCode(Status.Code.UNIMPLEMENTED)) {
+ // Silently return without upload.
+ return;
}
+ throw e;
}
}
@@ -469,24 +489,28 @@ public class GrpcRemoteCache implements RemoteActionCache {
return new byte[0];
}
byte[] result = new byte[(int) digest.getSizeBytes()];
- retrier.execute(
- () -> {
- Iterator<ReadResponse> replies = readBlob(digest);
- int offset = 0;
- while (replies.hasNext()) {
- ByteString data = replies.next().getData();
- data.copyTo(result, offset);
- offset += data.size();
- }
- Preconditions.checkState(digest.getSizeBytes() == offset);
- return null;
- });
+ try {
+ retrier.execute(
+ () -> {
+ Iterator<ReadResponse> replies = readBlob(digest);
+ int offset = 0;
+ while (replies.hasNext()) {
+ ByteString data = replies.next().getData();
+ data.copyTo(result, offset);
+ offset += data.size();
+ }
+ Preconditions.checkState(digest.getSizeBytes() == offset);
+ return null;
+ });
+ } catch (RetryException e) {
+ Throwables.throwIfInstanceOf(e.getCause(), CacheNotFoundException.class);
+ throw e;
+ }
return result;
}
// Execution Cache API
- /** Returns a cached result for a given Action digest, or null if not found in cache. */
@Override
public ActionResult getCachedActionResult(ActionKey actionKey)
throws IOException, InterruptedException {
@@ -501,6 +525,7 @@ public class GrpcRemoteCache implements RemoteActionCache {
.build()));
} catch (RetryException e) {
if (e.causedByStatusCode(Status.Code.NOT_FOUND)) {
+ // Return null to indicate that it was a cache miss.
return null;
}
throw e;
diff --git a/src/main/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutor.java b/src/main/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutor.java
index e460c3ea7c..173f10ddb0 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/GrpcRemoteExecutor.java
@@ -14,7 +14,6 @@
package com.google.devtools.build.lib.remote;
-import com.google.devtools.build.lib.actions.UserExecException;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.remoteexecution.v1test.ExecuteRequest;
@@ -69,14 +68,14 @@ public class GrpcRemoteExecutor {
}
private @Nullable ExecuteResponse getOperationResponse(Operation op)
- throws IOException, UserExecException {
+ throws IOException {
if (op.getResultCase() == Operation.ResultCase.ERROR) {
StatusRuntimeException e = StatusProto.toStatusRuntimeException(op.getError());
if (e.getStatus().getCode() == Code.DEADLINE_EXCEEDED) {
// This was caused by the command itself exceeding the timeout,
// therefore it is not retriable.
// TODO(olaola): this should propagate a timeout SpawnResult instead of raising.
- throw new UserExecException("Remote execution time out", true);
+ throw new IOException("Remote execution time out");
}
throw e;
}
@@ -92,7 +91,7 @@ public class GrpcRemoteExecutor {
}
public ExecuteResponse executeRemotely(ExecuteRequest request)
- throws InterruptedException, IOException, UserExecException {
+ throws IOException, InterruptedException {
Operation op = retrier.execute(() -> execBlockingStub().execute(request));
ExecuteResponse resp = getOperationResponse(op);
if (resp != null) {
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 0c1a6cf63f..7b6971623c 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
@@ -15,8 +15,8 @@
package com.google.devtools.build.lib.remote;
import com.google.common.collect.ImmutableMap;
-import com.google.devtools.build.lib.actions.ActionExecutionMetadata;
import com.google.devtools.build.lib.actions.ActionInput;
+import com.google.devtools.build.lib.actions.ActionInputFileCache;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.Spawn;
import com.google.devtools.build.lib.actions.Spawns;
@@ -33,10 +33,11 @@ import com.google.devtools.remoteexecution.v1test.ActionResult;
import com.google.devtools.remoteexecution.v1test.Command;
import com.google.devtools.remoteexecution.v1test.Digest;
import com.google.devtools.remoteexecution.v1test.ExecuteRequest;
+import com.google.devtools.remoteexecution.v1test.ExecuteResponse;
import com.google.devtools.remoteexecution.v1test.Platform;
import com.google.protobuf.Duration;
-import io.grpc.StatusRuntimeException;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.SortedMap;
@@ -49,68 +50,102 @@ final class RemoteSpawnRunner implements SpawnRunner {
private final RemoteOptions options;
// TODO(olaola): This will be set on a per-action basis instead.
private final Platform platform;
+ private final SpawnRunner fallbackRunner;
- private final GrpcRemoteExecutor executor;
- private final GrpcRemoteCache remoteCache;
+ private final RemoteActionCache remoteCache;
+ private final GrpcRemoteExecutor remoteExecutor;
RemoteSpawnRunner(
Path execRoot,
RemoteOptions options,
- GrpcRemoteExecutor executor,
- GrpcRemoteCache remoteCache) {
+ SpawnRunner fallbackRunner,
+ RemoteActionCache remoteCache,
+ GrpcRemoteExecutor remoteExecutor) {
this.execRoot = execRoot;
this.options = options;
this.platform = options.parseRemotePlatformOverride();
- this.executor = executor;
+ this.fallbackRunner = fallbackRunner;
this.remoteCache = remoteCache;
+ this.remoteExecutor = remoteExecutor;
}
@Override
public SpawnResult exec(Spawn spawn, SpawnExecutionPolicy policy)
throws ExecException, InterruptedException, IOException {
- ActionExecutionMetadata owner = spawn.getResourceOwner();
- if (owner.getOwner() != null) {
- policy.report(ProgressStatus.EXECUTING);
+ if (!spawn.isRemotable() || remoteCache == null) {
+ return fallbackRunner.exec(spawn, policy);
}
- try {
- // Temporary hack: the TreeNodeRepository should be created and maintained upstream!
- TreeNodeRepository repository =
- new TreeNodeRepository(execRoot, policy.getActionInputFileCache());
- SortedMap<PathFragment, ActionInput> inputMap = policy.getInputMapping();
- TreeNode inputRoot = repository.buildFromActionInputs(inputMap);
- repository.computeMerkleDigests(inputRoot);
- Command command = buildCommand(spawn.getArguments(), spawn.getEnvironment());
- Action action =
- buildAction(
- spawn.getOutputFiles(),
- Digests.computeDigest(command),
- repository.getMerkleDigest(inputRoot),
- Spawns.getTimeoutSeconds(spawn));
+ policy.report(ProgressStatus.EXECUTING);
+ // Temporary hack: the TreeNodeRepository should be created and maintained upstream!
+ ActionInputFileCache inputFileCache = policy.getActionInputFileCache();
+ TreeNodeRepository repository = new TreeNodeRepository(execRoot, inputFileCache);
+ SortedMap<PathFragment, ActionInput> inputMap = policy.getInputMapping();
+ TreeNode inputRoot = repository.buildFromActionInputs(inputMap);
+ repository.computeMerkleDigests(inputRoot);
+ Command command = buildCommand(spawn.getArguments(), spawn.getEnvironment());
+ Action action =
+ buildAction(
+ spawn.getOutputFiles(),
+ Digests.computeDigest(command),
+ repository.getMerkleDigest(inputRoot),
+ Spawns.getTimeoutSeconds(spawn));
- ActionKey actionKey = Digests.computeActionKey(action);
+ // Look up action cache, and reuse the action output if it is found.
+ ActionKey actionKey = Digests.computeActionKey(action);
+ try {
+ boolean acceptCachedResult = options.remoteAcceptCached;
ActionResult result =
- options.remoteAcceptCached ? remoteCache.getCachedActionResult(actionKey) : null;
- if (result == null) {
- // Cache miss or we don't accept cache hits.
- // Upload the command and all the inputs into the remote cache.
- remoteCache.ensureInputsPresent(repository, execRoot, inputRoot, command);
- // TODO(olaola): set BuildInfo and input total bytes as well.
- ExecuteRequest.Builder request =
- ExecuteRequest.newBuilder()
- .setInstanceName(options.remoteInstanceName)
- .setAction(action)
- .setTotalInputFileCount(inputMap.size())
- .setSkipCacheLookup(!options.remoteAcceptCached);
- result = executor.executeRemotely(request.build()).getResult();
+ acceptCachedResult
+ ? remoteCache.getCachedActionResult(actionKey)
+ : null;
+ if (result != null) {
+ // We don't cache failed actions, so we know the outputs exist.
+ // For now, download all outputs locally; in the future, we can reuse the digests to
+ // just update the TreeNodeRepository and continue the build.
+ try {
+ remoteCache.download(result, execRoot, policy.getFileOutErr());
+ return new SpawnResult.Builder()
+ .setStatus(Status.SUCCESS) // Even if the action failed with non-zero exit code.
+ .setExitCode(result.getExitCode())
+ .build();
+ } catch (CacheNotFoundException e) {
+ acceptCachedResult = false; // Retry the action remotely and invalidate the results.
+ }
+ }
+
+ if (remoteExecutor == null) {
+ return execLocally(spawn, policy, remoteCache, actionKey);
}
+ // Upload the command and all the inputs into the remote cache.
+ remoteCache.ensureInputsPresent(repository, execRoot, inputRoot, command);
+ // TODO(olaola): set BuildInfo and input total bytes as well.
+ ExecuteRequest.Builder request =
+ ExecuteRequest.newBuilder()
+ .setInstanceName(options.remoteInstanceName)
+ .setAction(action)
+ .setTotalInputFileCount(inputMap.size())
+ .setSkipCacheLookup(!acceptCachedResult);
+ ExecuteResponse reply = remoteExecutor.executeRemotely(request.build());
+ result = reply.getResult();
+ if (options.remoteLocalFallback && result.getExitCode() != 0) {
+ return execLocally(spawn, policy, remoteCache, actionKey);
+ }
remoteCache.download(result, execRoot, policy.getFileOutErr());
return new SpawnResult.Builder()
.setStatus(Status.SUCCESS) // Even if the action failed with non-zero exit code.
.setExitCode(result.getExitCode())
.build();
- } catch (StatusRuntimeException | CacheNotFoundException e) {
+ } catch (IOException e) {
+ if (options.remoteLocalFallback) {
+ return execLocally(spawn, policy, remoteCache, actionKey);
+ }
+ throw e;
+ } catch (CacheNotFoundException e) {
+ if (options.remoteLocalFallback) {
+ return execLocally(spawn, policy, remoteCache, actionKey);
+ }
throw new IOException(e);
}
}
@@ -147,4 +182,32 @@ final class RemoteSpawnRunner implements SpawnRunner {
}
return command.build();
}
+
+ /**
+ * Fallback: execute the spawn locally. If an ActionKey is provided, try to upload results to
+ * remote action cache.
+ */
+ private SpawnResult execLocally(
+ Spawn spawn,
+ SpawnExecutionPolicy policy,
+ RemoteActionCache remoteCache,
+ ActionKey actionKey)
+ throws ExecException, IOException, InterruptedException {
+ SpawnResult result = fallbackRunner.exec(spawn, policy);
+ if (options.remoteUploadLocalResults && remoteCache != null && actionKey != null) {
+ ArrayList<Path> outputFiles = new ArrayList<>();
+ for (ActionInput output : spawn.getOutputFiles()) {
+ Path outputFile = execRoot.getRelative(output.getExecPathString());
+ // Ignore non-existent files.
+ // TODO(ulfjack): This is not ideal - in general, all spawn strategies should stat the
+ // output files and return a list of existing files. We shouldn't re-stat the files here.
+ if (!outputFile.exists()) {
+ continue;
+ }
+ outputFiles.add(outputFile);
+ }
+ remoteCache.upload(actionKey, execRoot, outputFiles, policy.getFileOutErr());
+ }
+ return result;
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnStrategy.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnStrategy.java
index 346d43e1bb..15545bdc44 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnStrategy.java
@@ -14,7 +14,6 @@
package com.google.devtools.build.lib.remote;
-import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ActionInput;
@@ -27,7 +26,6 @@ import com.google.devtools.build.lib.actions.SpawnActionContext;
import com.google.devtools.build.lib.actions.Spawns;
import com.google.devtools.build.lib.actions.UserExecException;
import com.google.devtools.build.lib.events.Event;
-import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.exec.SpawnInputExpander;
import com.google.devtools.build.lib.remote.Digests.ActionKey;
import com.google.devtools.build.lib.remote.TreeNodeRepository.TreeNode;
@@ -43,7 +41,6 @@ import com.google.devtools.remoteexecution.v1test.ExecuteRequest;
import com.google.devtools.remoteexecution.v1test.ExecuteResponse;
import com.google.devtools.remoteexecution.v1test.Platform;
import com.google.protobuf.Duration;
-import io.grpc.StatusRuntimeException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -136,7 +133,7 @@ final class RemoteSpawnStrategy implements SpawnActionContext {
ActionExecutionContext actionExecutionContext,
RemoteActionCache remoteCache,
ActionKey actionKey)
- throws ExecException, InterruptedException {
+ throws ExecException, IOException, InterruptedException {
fallbackStrategy.exec(spawn, actionExecutionContext);
if (remoteOptions.remoteUploadLocalResults && remoteCache != null && actionKey != null) {
ArrayList<Path> outputFiles = new ArrayList<>();
@@ -150,22 +147,8 @@ final class RemoteSpawnStrategy implements SpawnActionContext {
}
outputFiles.add(outputFile);
}
- try {
- remoteCache.upload(
- actionKey, execRoot, outputFiles, actionExecutionContext.getFileOutErr());
- } catch (IOException e) {
- throw new UserExecException("Unexpected IO error.", e);
- } catch (UnsupportedOperationException e) {
- actionExecutionContext
- .getEventHandler()
- .handle(
- Event.warn(
- spawn.getMnemonic() + " unsupported operation for action cache (" + e + ")"));
- } catch (StatusRuntimeException e) {
- actionExecutionContext
- .getEventHandler()
- .handle(Event.warn(spawn.getMnemonic() + " failed uploading results (" + e + ")"));
- }
+ remoteCache.upload(
+ actionKey, execRoot, outputFiles, actionExecutionContext.getFileOutErr());
}
}
@@ -178,9 +161,7 @@ final class RemoteSpawnStrategy implements SpawnActionContext {
@Override
public void exec(Spawn spawn, ActionExecutionContext actionExecutionContext)
throws ExecException, InterruptedException {
- ActionKey actionKey = null;
String mnemonic = spawn.getMnemonic();
- EventHandler eventHandler = actionExecutionContext.getEventHandler();
if (!spawn.isRemotable() || remoteCache == null) {
fallbackStrategy.exec(spawn, actionExecutionContext);
@@ -193,6 +174,7 @@ final class RemoteSpawnStrategy implements SpawnActionContext {
.getEventBus()
.post(ActionStatusMessage.runningStrategy(spawn.getResourceOwner(), "remote"));
+ ActionKey actionKey = null;
try {
// Temporary hack: the TreeNodeRepository should be created and maintained upstream!
ActionInputFileCache inputFileCache = actionExecutionContext.getActionInputFileCache();
@@ -260,38 +242,24 @@ final class RemoteSpawnStrategy implements SpawnActionContext {
verboseFailures, spawn.getArguments(), spawn.getEnvironment(), cwd);
throw new UserExecException(message + ": Exit " + result.getExitCode());
}
- } catch (RetryException e) {
- String stackTrace = "";
- if (verboseFailures) {
- stackTrace = "\n" + Throwables.getStackTraceAsString(e);
- }
- eventHandler.handle(Event.warn(mnemonic + " remote work failed (" + e + ")" + stackTrace));
- if (remoteOptions.remoteLocalFallback) {
- execLocally(spawn, actionExecutionContext, remoteCache, actionKey);
- } else {
- String cwd = actionExecutionContext.getExecRoot().getPathString();
- String message =
- CommandFailureUtils.describeCommandFailure(
- verboseFailures, spawn.getArguments(), spawn.getEnvironment(), cwd);
- throw new UserExecException(message, e.getCause());
- }
- } catch (CacheNotFoundException e) {
+ } catch (CacheNotFoundException | IOException e) {
+ Exception reportedCause = e;
// TODO(olaola): handle this exception by reuploading / reexecuting the action remotely.
- eventHandler.handle(Event.warn(mnemonic + " remote work results cache miss (" + e + ")"));
if (remoteOptions.remoteLocalFallback) {
- execLocally(spawn, actionExecutionContext, remoteCache, actionKey);
- } else {
- String cwd = actionExecutionContext.getExecRoot().getPathString();
- String message =
- CommandFailureUtils.describeCommandFailure(
- verboseFailures, spawn.getArguments(), spawn.getEnvironment(), cwd);
- throw new UserExecException(message, e);
+ actionExecutionContext.getEventHandler().handle(
+ Event.warn(mnemonic + " remote work failed (" + e + ")"));
+ try {
+ execLocally(spawn, actionExecutionContext, remoteCache, actionKey);
+ return;
+ } catch (IOException e2) {
+ reportedCause = e2;
+ }
}
- } catch (IOException e) {
- throw new UserExecException("Unexpected IO error.", e);
- } catch (UnsupportedOperationException e) {
- eventHandler.handle(
- Event.warn(mnemonic + " unsupported operation for action cache (" + e + ")"));
+ String cwd = actionExecutionContext.getExecRoot().getPathString();
+ String message =
+ CommandFailureUtils.describeCommandFailure(
+ verboseFailures, spawn.getArguments(), spawn.getEnvironment(), cwd);
+ throw new UserExecException(message, reportedCause);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/remote/Retrier.java b/src/main/java/com/google/devtools/build/lib/remote/Retrier.java
index 981af46199..cd69ad4d0f 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/Retrier.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/Retrier.java
@@ -24,6 +24,7 @@ import com.google.devtools.build.lib.util.Preconditions;
import io.grpc.Status;
import io.grpc.StatusException;
import io.grpc.StatusRuntimeException;
+import java.io.IOException;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.Callable;
@@ -280,9 +281,11 @@ public class Retrier {
* backoff/retry policy. Will raise the last encountered retriable error, or the first
* non-retriable error.
*
+ * <p>This method never throws {@link StatusRuntimeException} even if the passed-in Callable does.
+ *
* @param c The callable to execute.
*/
- public <T> T execute(Callable<T> c) throws InterruptedException, RetryException {
+ public <T> T execute(Callable<T> c) throws InterruptedException, IOException {
Backoff backoff = backoffSupplier.get();
while (true) {
try {
@@ -290,8 +293,11 @@ public class Retrier {
} catch (StatusException | StatusRuntimeException e) {
onFailure(backoff, Status.fromThrowable(e));
} catch (Exception e) {
- // Generic catch because Callable is declared to throw Exception.
+ // Generic catch because Callable is declared to throw Exception, we rethrow any unchecked
+ // exception as well as any exception we declared above.
Throwables.throwIfUnchecked(e);
+ Throwables.throwIfInstanceOf(e, IOException.class);
+ Throwables.throwIfInstanceOf(e, InterruptedException.class);
throw new RetryException(e, backoff.getRetryAttempts());
}
}
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 dd4237fc81..e299ba7873 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
@@ -173,7 +173,7 @@ public class GrpcRemoteExecutionClientTest {
Channel channel = InProcessChannelBuilder.forName(fakeServerName).directExecutor().build();
GrpcRemoteExecutor executor = new GrpcRemoteExecutor(channel, ChannelOptions.DEFAULT, options);
GrpcRemoteCache remoteCache = new GrpcRemoteCache(channel, ChannelOptions.DEFAULT, options);
- client = new RemoteSpawnRunner(execRoot, options, executor, remoteCache);
+ client = new RemoteSpawnRunner(execRoot, options, null, remoteCache, executor);
inputDigest = fakeFileCache.createScratchInput(simpleSpawn.getInputFiles().get(0), "xyz");
}
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RetrierTest.java b/src/test/java/com/google/devtools/build/lib/remote/RetrierTest.java
index ac54c26da7..9a8e464914 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/RetrierTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/RetrierTest.java
@@ -19,6 +19,7 @@ import static org.mockito.Mockito.when;
import com.google.common.collect.Range;
import io.grpc.Status;
+import java.io.IOException;
import java.time.Duration;
import org.junit.Before;
import org.junit.Test;
@@ -71,7 +72,7 @@ public class RetrierTest {
assertThat(backoff.nextDelayMillis()).isEqualTo(Retrier.Backoff.STOP);
}
- void assertThrows(Retrier retrier, int attempts) throws InterruptedException {
+ void assertThrows(Retrier retrier, int attempts) throws InterruptedException, IOException {
try {
retrier.execute(() -> fooMock.foo());
fail();
@@ -108,5 +109,30 @@ public class RetrierTest {
Mockito.verify(retrier, Mockito.times(2)).sleep(2000);
Mockito.verify(fooMock, Mockito.times(6)).foo();
}
-}
+ @Test
+ public void testInterruptedExceptionIsPassedThrough() throws Exception {
+ InterruptedException thrown = new InterruptedException();
+ try {
+ Retrier.NO_RETRIES.execute(() -> {
+ throw thrown;
+ });
+ fail();
+ } catch (InterruptedException expected) {
+ assertThat(expected).isSameAs(thrown);
+ }
+ }
+
+ @Test
+ public void testIOExceptionIsPassedThrough() throws Exception {
+ IOException thrown = new IOException();
+ try {
+ Retrier.NO_RETRIES.execute(() -> {
+ throw thrown;
+ });
+ fail();
+ } catch (IOException expected) {
+ assertThat(expected).isSameAs(thrown);
+ }
+ }
+}