improve remote execution error messages
If the error originated in the gRPC library (it usually does) then
display the error as "STATUS CODE: error message" directly to the
user.
Before this change:
"java.io.IOException: io.grpc.StatusRuntimeException: UNAVAILABLE:
failed to dns resolve hostname"
After this change:
"UNAVAILABLE: failed to dns resolve hostname"
Closes #8599.
PiperOrigin-RevId: 254617861
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java
index b09163c..5d10cee 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java
@@ -39,6 +39,7 @@
import com.google.devtools.build.lib.remote.options.RemoteOutputsMode;
import com.google.devtools.build.lib.remote.util.DigestUtil;
import com.google.devtools.build.lib.remote.util.TracingMetadataUtils;
+import com.google.devtools.build.lib.remote.util.Utils;
import com.google.devtools.build.lib.runtime.BlazeModule;
import com.google.devtools.build.lib.runtime.BuildEventArtifactUploaderFactory;
import com.google.devtools.build.lib.runtime.Command;
@@ -228,7 +229,7 @@
capabilities = rsc.get(buildRequestId, invocationId);
} catch (IOException e) {
throw new AbruptExitException(
- "Failed to query remote execution capabilities: " + e.getMessage(),
+ "Failed to query remote execution capabilities: " + Utils.grpcAwareErrorMessage(e),
ExitCode.REMOTE_ERROR,
e);
} catch (InterruptedException e) {
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnCache.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnCache.java
index 72ba687..e711b27 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnCache.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnCache.java
@@ -51,6 +51,7 @@
import com.google.devtools.build.lib.remote.util.DigestUtil;
import com.google.devtools.build.lib.remote.util.DigestUtil.ActionKey;
import com.google.devtools.build.lib.remote.util.TracingMetadataUtils;
+import com.google.devtools.build.lib.remote.util.Utils;
import com.google.devtools.build.lib.remote.util.Utils.InMemoryOutput;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
@@ -194,7 +195,7 @@
} catch (CacheNotFoundException e) {
// Intentionally left blank
} catch (IOException e) {
- String errorMsg = e.getMessage();
+ String errorMsg = Utils.grpcAwareErrorMessage(e);
if (isNullOrEmpty(errorMsg)) {
errorMsg = e.getClass().getSimpleName();
}
@@ -247,7 +248,7 @@
remoteCache.upload(
actionKey, action, command, execRoot, files, context.getFileOutErr());
} catch (IOException e) {
- String errorMsg = e.getMessage();
+ String errorMsg = Utils.grpcAwareErrorMessage(e);
if (isNullOrEmpty(errorMsg)) {
errorMsg = e.getClass().getSimpleName();
}
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 cb6463a..2a09f3c 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
@@ -55,7 +55,6 @@
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.exec.ExecutionOptions;
-import com.google.devtools.build.lib.exec.SpawnExecException;
import com.google.devtools.build.lib.exec.SpawnRunner;
import com.google.devtools.build.lib.profiler.Profiler;
import com.google.devtools.build.lib.profiler.ProfilerTask;
@@ -66,6 +65,7 @@
import com.google.devtools.build.lib.remote.util.DigestUtil;
import com.google.devtools.build.lib.remote.util.DigestUtil.ActionKey;
import com.google.devtools.build.lib.remote.util.TracingMetadataUtils;
+import com.google.devtools.build.lib.remote.util.Utils;
import com.google.devtools.build.lib.remote.util.Utils.InMemoryOutput;
import com.google.devtools.build.lib.util.ExitCode;
import com.google.devtools.build.lib.util.io.FileOutErr;
@@ -439,17 +439,24 @@
} else {
status = Status.EXECUTION_FAILED;
}
- throw new SpawnExecException(
- verboseFailures ? Throwables.getStackTraceAsString(exception) : exception.getMessage(),
- new SpawnResult.Builder()
- .setRunnerName(getName())
- .setStatus(status)
- .setExitCode(ExitCode.REMOTE_ERROR.getNumericExitCode())
- .setFailureMessage(exception.getMessage())
- .build(),
- /* forciblyRunRemotely= */ false);
+
+ final String errorMessage;
+ if (!verboseFailures) {
+ errorMessage = Utils.grpcAwareErrorMessage(exception);
+ } else {
+ // On --verbose_failures print the whole stack trace
+ errorMessage = Throwables.getStackTraceAsString(exception);
+ }
+
+ return new SpawnResult.Builder()
+ .setRunnerName(getName())
+ .setStatus(status)
+ .setExitCode(ExitCode.REMOTE_ERROR.getNumericExitCode())
+ .setFailureMessage(errorMessage)
+ .build();
}
+
static Action buildAction(Digest command, Digest inputRoot, Duration timeout, boolean cacheable) {
Action.Builder action = Action.newBuilder();
diff --git a/src/main/java/com/google/devtools/build/lib/remote/util/Utils.java b/src/main/java/com/google/devtools/build/lib/remote/util/Utils.java
index 1ab04da..818a4da 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/util/Utils.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/util/Utils.java
@@ -107,6 +107,16 @@
return !Collections.disjoint(outputs, topLevelOutputs);
}
+ public static String grpcAwareErrorMessage(IOException e) {
+ io.grpc.Status errStatus = io.grpc.Status.fromThrowable(e);
+ if (!errStatus.getCode().equals(io.grpc.Status.UNKNOWN.getCode())) {
+ // If the error originated in the gRPC library then display it as "STATUS: error message"
+ // to the user
+ return String.format("%s: %s", errStatus.getCode().name(), errStatus.getDescription());
+ }
+ return e.getMessage();
+ }
+
/** An in-memory output file. */
public static final class InMemoryOutput {
private final ActionInput output;
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 40e7074..157c3a3 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
@@ -14,7 +14,6 @@
package com.google.devtools.build.lib.remote;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.fail;
import static org.mockito.AdditionalAnswers.answerVoid;
@@ -50,7 +49,6 @@
import com.google.common.util.concurrent.MoreExecutors;
import com.google.devtools.build.lib.actions.ActionInput;
import com.google.devtools.build.lib.actions.ActionInputHelper;
-import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.ResourceSet;
import com.google.devtools.build.lib.actions.SimpleSpawn;
import com.google.devtools.build.lib.actions.SpawnResult;
@@ -59,7 +57,6 @@
import com.google.devtools.build.lib.authandtls.GoogleAuthUtils;
import com.google.devtools.build.lib.clock.JavaClock;
import com.google.devtools.build.lib.exec.ExecutionOptions;
-import com.google.devtools.build.lib.exec.SpawnExecException;
import com.google.devtools.build.lib.exec.util.FakeOwner;
import com.google.devtools.build.lib.remote.RemoteRetrier.ExponentialBackoff;
import com.google.devtools.build.lib.remote.options.RemoteOptions;
@@ -222,7 +219,7 @@
remoteOptions,
Options.getDefaults(ExecutionOptions.class),
null,
- true,
+ /* verboseFailures= */ true,
/*cmdlineReporter=*/ null,
"build-req-id",
"command-id",
@@ -857,13 +854,10 @@
FakeSpawnExecutionContext policy =
new FakeSpawnExecutionContext(simpleSpawn, fakeFileCache, execRoot, outErr);
- SpawnExecException expected =
- assertThrows(SpawnExecException.class, () -> client.exec(simpleSpawn, policy));
- assertThat(expected.getSpawnResult().status())
- .isEqualTo(SpawnResult.Status.EXECUTION_FAILED_CATASTROPHICALLY);
- // Ensure we also got back the stack trace.
- assertThat(expected)
- .hasMessageThat()
+ SpawnResult result = client.exec(simpleSpawn, policy);
+ assertThat(result.status()).isEqualTo(SpawnResult.Status.EXECUTION_FAILED_CATASTROPHICALLY);
+ // Ensure we also got back the stack trace due to verboseFailures=true
+ assertThat(result.getFailureMessage())
.contains("GrpcRemoteExecutionClientTest.passUnavailableErrorWithStackTrace");
}
@@ -880,12 +874,10 @@
FakeSpawnExecutionContext policy =
new FakeSpawnExecutionContext(simpleSpawn, fakeFileCache, execRoot, outErr);
- ExecException expected =
- assertThrows(ExecException.class, () -> client.exec(simpleSpawn, policy));
- assertThat(expected).hasMessageThat().contains("whoa"); // Error details.
+ SpawnResult result = client.exec(simpleSpawn, policy);
+ assertThat(result.getFailureMessage()).contains("whoa"); // Error details.
// Ensure we also got back the stack trace.
- assertThat(expected)
- .hasMessageThat()
+ assertThat(result.getFailureMessage())
.contains("GrpcRemoteExecutionClientTest.passInternalErrorWithStackTrace");
}
@@ -941,14 +933,11 @@
FakeSpawnExecutionContext policy =
new FakeSpawnExecutionContext(simpleSpawn, fakeFileCache, execRoot, outErr);
- SpawnExecException expected =
- assertThrows(SpawnExecException.class, () -> client.exec(simpleSpawn, policy));
- assertThat(expected.getSpawnResult().status())
- .isEqualTo(SpawnResult.Status.REMOTE_CACHE_FAILED);
- assertThat(expected).hasMessageThat().contains(DIGEST_UTIL.toString(stdOutDigest));
+ SpawnResult result = client.exec(simpleSpawn, policy);
+ assertThat(result.status()).isEqualTo(SpawnResult.Status.REMOTE_CACHE_FAILED);
+ assertThat(result.getFailureMessage()).contains(DIGEST_UTIL.toString(stdOutDigest));
// Ensure we also got back the stack trace.
- assertThat(expected)
- .hasMessageThat()
+ assertThat(result.getFailureMessage())
.contains("GrpcRemoteExecutionClientTest.passCacheMissErrorWithStackTrace");
}
@@ -1004,14 +993,11 @@
FakeSpawnExecutionContext policy =
new FakeSpawnExecutionContext(simpleSpawn, fakeFileCache, execRoot, outErr);
- SpawnExecException expected =
- assertThrows(SpawnExecException.class, () -> client.exec(simpleSpawn, policy));
- assertThat(expected.getSpawnResult().status())
- .isEqualTo(SpawnResult.Status.REMOTE_CACHE_FAILED);
- assertThat(expected).hasMessageThat().contains(DIGEST_UTIL.toString(stdOutDigest));
- // Ensure we also got back the stack trace.
- assertThat(expected)
- .hasMessageThat()
+ SpawnResult result = client.exec(simpleSpawn, policy);
+ assertThat(result.status()).isEqualTo(SpawnResult.Status.REMOTE_CACHE_FAILED);
+ assertThat(result.getFailureMessage()).contains(DIGEST_UTIL.toString(stdOutDigest));
+ // Ensure we also got back the stack trace because verboseFailures=true
+ assertThat(result.getFailureMessage())
.contains("passRepeatedOrphanedCacheMissErrorWithStackTrace");
}
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
index 5dd3713..567629a 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
@@ -61,7 +61,6 @@
import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.exec.ExecutionOptions;
-import com.google.devtools.build.lib.exec.SpawnExecException;
import com.google.devtools.build.lib.exec.SpawnRunner;
import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionContext;
import com.google.devtools.build.lib.exec.util.FakeOwner;
@@ -739,9 +738,9 @@
SpawnExecutionContext policy =
new FakeSpawnExecutionContext(spawn, fakeFileCache, execRoot, outErr);
- SpawnExecException e = assertThrows(SpawnExecException.class, () -> runner.exec(spawn, policy));
- assertThat(e.getSpawnResult().exitCode()).isEqualTo(ExitCode.REMOTE_ERROR.getNumericExitCode());
- assertThat(e.getSpawnResult().getDetailMessage("", "", false, false)).contains("reasons");
+ SpawnResult result = runner.exec(spawn, policy);
+ assertThat(result.exitCode()).isEqualTo(ExitCode.REMOTE_ERROR.getNumericExitCode());
+ assertThat(result.getDetailMessage("", "", false, false)).contains("reasons");
}
@Test
@@ -759,9 +758,9 @@
SpawnExecutionContext policy =
new FakeSpawnExecutionContext(spawn, fakeFileCache, execRoot, outErr);
- SpawnExecException e = assertThrows(SpawnExecException.class, () -> runner.exec(spawn, policy));
- assertThat(e.getSpawnResult().exitCode()).isEqualTo(ExitCode.REMOTE_ERROR.getNumericExitCode());
- assertThat(e.getSpawnResult().getDetailMessage("", "", false, false)).contains("reasons");
+ SpawnResult result = runner.exec(spawn, policy);
+ assertThat(result.exitCode()).isEqualTo(ExitCode.REMOTE_ERROR.getNumericExitCode());
+ assertThat(result.getDetailMessage("", "", false, false)).contains("reasons");
}
@Test
@@ -910,8 +909,8 @@
new FakeSpawnExecutionContext(spawn, fakeFileCache, execRoot, outErr);
// act
- SpawnExecException e = assertThrows(SpawnExecException.class, () -> runner.exec(spawn, policy));
- assertThat(e.getMessage()).isEqualTo(downloadFailure.getMessage());
+ SpawnResult result = runner.exec(spawn, policy);
+ assertThat(result.getFailureMessage()).isEqualTo(downloadFailure.getMessage());
// assert
verify(cache)
diff --git a/src/test/java/com/google/devtools/build/lib/remote/UtilsTest.java b/src/test/java/com/google/devtools/build/lib/remote/UtilsTest.java
new file mode 100644
index 0000000..ad1b4c0
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/remote/UtilsTest.java
@@ -0,0 +1,39 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.remote;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.devtools.build.lib.remote.util.Utils;
+import io.grpc.Status;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for remote utility methods */
+@RunWith(JUnit4.class)
+public class UtilsTest {
+
+ @Test
+ public void testGrpcAwareErrorMessages() {
+ IOException ioError = new IOException("io error");
+ IOException wrappedGrpcError =
+ new IOException(
+ "wrapped error", Status.ABORTED.withDescription("grpc error").asRuntimeException());
+
+ assertThat(Utils.grpcAwareErrorMessage(ioError)).isEqualTo("io error");
+ assertThat(Utils.grpcAwareErrorMessage(wrappedGrpcError)).isEqualTo("ABORTED: grpc error");
+ }
+}