Handle crashes in a more consistent way.
Unifies the crash handling of exceptions in async threads and exceptions in Skyframe. Both now go through BugReport#handleCrash and BlazeModule#blazeShutdownOnCrash.
Additionally, OOMs from a Skyframe thread are now reported to BES.
PiperOrigin-RevId: 339110985
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index e8ba5d5..b119cd4 100644
--- a/src/main/java/com/google/devtools/build/lib/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/BUILD
@@ -210,6 +210,7 @@
"runtime/BlazeCommandResult.java",
],
deps = [
+ "//src/main/java/com/google/devtools/build/lib/bugreport",
"//src/main/java/com/google/devtools/build/lib/concurrent",
"//src/main/java/com/google/devtools/build/lib/util:crash_failure_details",
"//src/main/java/com/google/devtools/build/lib/util:detailed_exit_code",
diff --git a/src/main/java/com/google/devtools/build/lib/bugreport/BUILD b/src/main/java/com/google/devtools/build/lib/bugreport/BUILD
index 776c765..bc8e2aa 100644
--- a/src/main/java/com/google/devtools/build/lib/bugreport/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/bugreport/BUILD
@@ -13,6 +13,7 @@
srcs = glob(["*.java"]),
deps = [
"//src/main/java/com/google/devtools/build/lib/analysis:blaze_version_info",
+ "//src/main/java/com/google/devtools/build/lib/events",
"//src/main/java/com/google/devtools/build/lib/util:TestType",
"//src/main/java/com/google/devtools/build/lib/util:crash_failure_details",
"//src/main/java/com/google/devtools/build/lib/util:custom_exit_code_publisher",
@@ -20,7 +21,6 @@
"//src/main/java/com/google/devtools/build/lib/util:detailed_exit_code",
"//src/main/java/com/google/devtools/build/lib/util:exit_code",
"//src/main/java/com/google/devtools/build/lib/util:logging",
- "//src/main/java/com/google/devtools/build/lib/util/io:out-err",
"//third_party:flogger",
"//third_party:guava",
"//third_party:jsr305",
diff --git a/src/main/java/com/google/devtools/build/lib/bugreport/BugReport.java b/src/main/java/com/google/devtools/build/lib/bugreport/BugReport.java
index 98ea588..bd09ec5 100644
--- a/src/main/java/com/google/devtools/build/lib/bugreport/BugReport.java
+++ b/src/main/java/com/google/devtools/build/lib/bugreport/BugReport.java
@@ -21,15 +21,12 @@
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.GoogleLogger;
import com.google.devtools.build.lib.analysis.BlazeVersionInfo;
-import com.google.devtools.build.lib.util.CrashFailureDetails;
+import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.util.CustomExitCodePublisher;
import com.google.devtools.build.lib.util.CustomFailureDetailPublisher;
import com.google.devtools.build.lib.util.DetailedExitCode;
-import com.google.devtools.build.lib.util.ExitCode;
import com.google.devtools.build.lib.util.LoggingUtil;
import com.google.devtools.build.lib.util.TestType;
-import com.google.devtools.build.lib.util.io.OutErr;
-import java.io.PrintStream;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
@@ -131,102 +128,60 @@
logException(exception, filterArgs(args), values);
}
- private static void logCrash(Throwable throwable, boolean sendBugReport, String... args) {
- logger.atSevere().withCause(throwable).log("Crash");
- if (sendBugReport) {
- BugReport.sendBugReport(throwable, Arrays.asList(args));
- }
- logThrowableToConsole(throwable);
- }
-
- private static void logThrowableToConsole(Throwable throwable) {
- BugReport.printBug(OutErr.SYSTEM_OUT_ERR, throwable, /*oomMessage=*/ null);
- System.err.println("ERROR: " + getProductName() + " crash in async thread:");
- throwable.printStackTrace();
- }
-
/**
- * Print, log, and then cause the current Blaze command to fail with the specified exit code, and
- * then cause the jvm to terminate.
+ * Convenience method equivalent to calling {@code BugReport.handleCrash(Crash.from(throwable),
+ * CrashContext.halt().withArgs(args)}.
*
- * <p>Has no effect if another crash has already been handled by {@link BugReport}.
- */
- public static RuntimeException handleCrashWithoutSendingBugReport(
- Throwable throwable, ExitCode exitCode, String... args) {
- throw handleCrash(
- throwable,
- /*sendBugReport=*/ false,
- DetailedExitCode.of(exitCode, CrashFailureDetails.forThrowable(throwable)),
- args);
- }
-
- /**
- * Print, log, send a bug report, and then cause the current Blaze command to fail with the
- * specified exit code, and then cause the jvm to terminate.
- *
- * <p>Has no effect if another crash has already been handled by {@link BugReport}.
- */
- public static RuntimeException handleCrash(
- Throwable throwable, ExitCode exitCode, String... args) {
- throw handleCrash(
- throwable,
- /*sendBugReport=*/ true,
- DetailedExitCode.of(exitCode, CrashFailureDetails.forThrowable(throwable)),
- args);
- }
-
- /**
- * Print, log, and send a bug report, and then cause the current Blaze command to fail with an
- * exit code inferred from the given {@link Throwable}, and then cause the jvm to terminate.
- *
- * <p>Has no effect if another crash has already been handled by {@link BugReport}.
+ * <p>Halts the JVM and does not return.
*/
public static RuntimeException handleCrash(Throwable throwable, String... args) {
- throw handleCrash(
- throwable,
- /*sendBugReport=*/ true,
- CrashFailureDetails.detailedExitCodeForThrowable(throwable),
- args);
+ handleCrash(Crash.from(throwable), CrashContext.halt().withArgs(args));
+ throw new IllegalStateException("Should have halted", throwable);
}
- private static RuntimeException handleCrash(
- Throwable throwable,
- boolean sendBugReport,
- DetailedExitCode detailedExitCode,
- String... args) {
- int numericExitCode = detailedExitCode.getExitCode().getNumericExitCode();
+ /**
+ * Handles a {@link Crash} according to the given {@link CrashContext}.
+ *
+ * <p>In the case of {@link CrashContext#halt}, the JVM is {@linkplain Runtime#halt halted}.
+ * Otherwise, for {@link CrashContext#keepAlive}, returns {@code null}, in which case the caller
+ * is responsible for shutting down the server.
+ */
+ public static void handleCrash(Crash crash, CrashContext ctx) {
+ int numericExitCode = crash.getDetailedExitCode().getExitCode().getNumericExitCode();
try {
synchronized (LOCK) {
- if (TestType.isInTest()) {
- unprocessedThrowableInTest = throwable;
- }
+ logger.atSevere().withCause(crash.getThrowable()).log("Handling crash with %s", ctx);
+
// Don't try to send a bug report during a crash in a test, it will throw itself.
- if (!TestType.isInTest() || !sendBugReport) {
- logCrash(throwable, sendBugReport, args);
- } else {
- logThrowableToConsole(throwable);
+ if (TestType.isInTest()) {
+ unprocessedThrowableInTest = crash.getThrowable();
+ } else if (ctx.shouldSendBugReport()) {
+ sendBugReport(crash.getThrowable(), ctx.getArgs());
}
- // TODO(b/167592709): remove verbose logging when bug resolved.
- logger.atInfo().log("Finished logging crash, runtime: %s", runtime);
+
+ String crashMsg = constructCrashMessageWithStackTrace(crash.getThrowable(), ctx);
+ ctx.getEventHandler().handle(Event.fatal(crashMsg));
+
try {
if (runtime != null) {
- runtime.cleanUpForCrash(detailedExitCode);
+ runtime.cleanUpForCrash(crash.getDetailedExitCode());
}
- logger.atInfo().log("Finished runtime cleanup");
- CustomExitCodePublisher.maybeWriteExitStatusFile(numericExitCode);
- logger.atInfo().log("Wrote exit status file");
+ // Writing the exit code status file is only necessary if we are halting. Otherwise, the
+ // caller is responsible for an orderly shutdown with the proper exit code.
+ if (ctx.shouldHaltJvm()) {
+ CustomExitCodePublisher.maybeWriteExitStatusFile(numericExitCode);
+ }
CustomFailureDetailPublisher.maybeWriteFailureDetailFile(
- detailedExitCode.getFailureDetail());
- logger.atInfo().log("Wrote failure detail file");
+ crash.getDetailedExitCode().getFailureDetail());
} finally {
- logger.atInfo().log("Entered inner finally block");
- // Avoid shutdown deadlock issues: If an application shutdown hook crashes, it will
- // trigger our Blaze crash handler (this method). Calling System#exit() here, would
- // therefore induce a deadlock. This call would block on the shutdown sequence completing,
- // but the shutdown sequence would in turn be blocked on this thread finishing. Instead,
- // exit fast via halt().
- Runtime.getRuntime().halt(numericExitCode);
- logger.atSevere().log("Failed to halt (inner block)!");
+ if (ctx.shouldHaltJvm()) {
+ // Avoid shutdown deadlock issues: If an application shutdown hook crashes, it will
+ // trigger our Blaze crash handler (this method). Calling System#exit() here, would
+ // therefore induce a deadlock. This call would block on the shutdown sequence
+ // completing, but the shutdown sequence would in turn be blocked on this thread
+ // finishing. Instead, exit fast via halt().
+ Runtime.getRuntime().halt(numericExitCode);
+ }
}
}
} catch (Throwable t) {
@@ -239,38 +194,29 @@
+ " and include the information below.");
System.err.println("Original uncaught exception:");
- throwable.printStackTrace(System.err);
+ crash.getThrowable().printStackTrace(System.err);
System.err.println("Exception encountered during BugReport#handleCrash:");
t.printStackTrace(System.err);
} finally {
- logger.atInfo().log("Entered outer finally block");
- Runtime.getRuntime().halt(numericExitCode);
- logger.atSevere().log("Failed to halt (outer block)!");
+ if (ctx.shouldHaltJvm()) {
+ Runtime.getRuntime().halt(numericExitCode);
+ }
+ }
+ if (!ctx.shouldHaltJvm()) {
+ return;
}
logger.atSevere().log("Failed to crash in handleCrash");
- throw new IllegalStateException("never get here", throwable);
+ throw new IllegalStateException("Should have halted", crash.getThrowable());
}
- private static void printThrowableTo(OutErr outErr, Throwable e) {
- PrintStream err = new PrintStream(outErr.getErrorStream());
- e.printStackTrace(err);
- err.flush();
- logger.atSevere().withCause(e).log("%s crashed", getProductName());
- }
-
- /**
- * Print user-helpful information about the bug/crash to the output.
- *
- * @param outErr where to write the output
- * @param e the exception thrown
- */
- public static void printBug(OutErr outErr, Throwable e, @Nullable String oomMessage) {
- if (e instanceof OutOfMemoryError) {
- outErr.printErr("ERROR: " + constructOomExitMessage(oomMessage));
- } else {
- printThrowableTo(outErr, e);
- }
+ /** Constructs a user-helpful message for a crash bug. */
+ private static String constructCrashMessageWithStackTrace(Throwable throwable, CrashContext ctx) {
+ String msg =
+ throwable instanceof OutOfMemoryError
+ ? constructOomExitMessage(ctx.getExtraOomInfo())
+ : getProductName() + " crashed due to an internal error.";
+ return msg + " Printing stack trace:\n" + Throwables.getStackTraceAsString(throwable);
}
public static String constructOomExitMessage(@Nullable String extraInfo) {
@@ -286,7 +232,7 @@
* <li>{@code --default_override} is spammy.
* </ul>
*/
- private static List<String> filterArgs(Iterable<String> args) {
+ private static ImmutableList<String> filterArgs(Iterable<String> args) {
if (args == null) {
return null;
}
@@ -302,12 +248,12 @@
return filteredArgs.build();
}
- // Log the exception. Because this method is only called in a blaze release,
- // this will result in a report being sent to a remote logging service.
+ // Log the exception. Because this method is only called in a blaze release, this will result in a
+ // report being sent to a remote logging service.
private static void logException(Throwable exception, List<String> args, String... values) {
logger.atSevere().withCause(exception).log("Exception");
- // The preamble is used in the crash watcher, so don't change it
- // unless you know what you're doing.
+ // The preamble is used in the crash watcher, so don't change it unless you know what you're
+ // doing.
String preamble =
getProductName()
+ (exception instanceof OutOfMemoryError ? " OOMError: " : " crashed with args: ");
@@ -315,7 +261,8 @@
LoggingUtil.logToRemote(Level.SEVERE, preamble + Joiner.on(' ').join(args), exception, values);
}
- private static class DefaultBugReporter implements BugReporter {
+ private static final class DefaultBugReporter implements BugReporter {
+
@Override
public void sendBugReport(Throwable exception) {
BugReport.sendBugReport(exception);
@@ -327,8 +274,8 @@
}
@Override
- public RuntimeException handleCrash(Throwable throwable, String... args) {
- throw BugReport.handleCrash(throwable, args);
+ public void handleCrash(Crash crash, CrashContext ctx) {
+ BugReport.handleCrash(crash, ctx);
}
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/bugreport/BugReporter.java b/src/main/java/com/google/devtools/build/lib/bugreport/BugReporter.java
index 3fc8ceac..03066a1 100644
--- a/src/main/java/com/google/devtools/build/lib/bugreport/BugReporter.java
+++ b/src/main/java/com/google/devtools/build/lib/bugreport/BugReporter.java
@@ -35,5 +35,5 @@
void sendBugReport(Throwable exception, List<String> args, String... values);
/** See {@link BugReport#handleCrash}. */
- RuntimeException handleCrash(Throwable throwable, String... args);
+ void handleCrash(Crash crash, CrashContext ctx);
}
diff --git a/src/main/java/com/google/devtools/build/lib/bugreport/Crash.java b/src/main/java/com/google/devtools/build/lib/bugreport/Crash.java
new file mode 100644
index 0000000..2205015
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bugreport/Crash.java
@@ -0,0 +1,64 @@
+// Copyright 2020 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.bugreport;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
+import com.google.devtools.build.lib.util.CrashFailureDetails;
+import com.google.devtools.build.lib.util.DetailedExitCode;
+import com.google.devtools.build.lib.util.ExitCode;
+
+/** Encapsulates the {@link Throwable} and {@link DetailedExitCode} for a crash. */
+public final class Crash {
+
+ /**
+ * Creates a crash caused by the given {@link Throwable}.
+ *
+ * <p>The exit code is generated by {@link CrashFailureDetails#detailedExitCodeForThrowable}.
+ */
+ public static Crash from(Throwable throwable) {
+ return new Crash(throwable, CrashFailureDetails.detailedExitCodeForThrowable(throwable));
+ }
+
+ /** Creates a crash caused by the given {@link Throwable} with a specified {@link ExitCode}. */
+ public static Crash from(Throwable throwable, ExitCode exitCode) {
+ return new Crash(
+ throwable, DetailedExitCode.of(exitCode, CrashFailureDetails.forThrowable(throwable)));
+ }
+
+ private final Throwable throwable;
+ private final DetailedExitCode detailedExitCode;
+
+ private Crash(Throwable throwable, DetailedExitCode detailedExitCode) {
+ this.throwable = checkNotNull(throwable);
+ this.detailedExitCode = checkNotNull(detailedExitCode);
+ }
+
+ public Throwable getThrowable() {
+ return throwable;
+ }
+
+ public DetailedExitCode getDetailedExitCode() {
+ return detailedExitCode;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("throwable", throwable)
+ .add("detailedExitCode", detailedExitCode)
+ .toString();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bugreport/CrashContext.java b/src/main/java/com/google/devtools/build/lib/bugreport/CrashContext.java
new file mode 100644
index 0000000..9b5a438
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bugreport/CrashContext.java
@@ -0,0 +1,125 @@
+// Copyright 2020 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.bugreport;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.EventKind;
+import java.util.List;
+
+/** Context describing when a {@link Crash} occurred and how it should be handled. */
+public final class CrashContext {
+
+ /**
+ * Creates a {@link CrashContext} that instructs {@link BugReporter} to halt the JVM when handling
+ * a crash.
+ *
+ * <p>This should only be used when it is not feasible to conduct an orderly shutdown, for example
+ * a crash in an async thread.
+ */
+ public static CrashContext halt() {
+ return new CrashContext(/*haltJvm=*/ true);
+ }
+
+ /**
+ * Creates a {@link CrashContext} that instructs {@link BugReporter} <em>not</em> to halt the JVM
+ * when handling a crash.
+ *
+ * <p>The caller is responsible for terminating the server with an appropriate exit code.
+ */
+ public static CrashContext keepAlive() {
+ return new CrashContext(/*haltJvm=*/ false);
+ }
+
+ private final boolean haltJvm;
+ private ImmutableList<String> args = ImmutableList.of();
+ private boolean sendBugReport = true;
+ private String extraOomInfo = "";
+ private EventHandler eventHandler =
+ event -> System.err.println(event.getKind() + ": " + event.getMessage());
+
+ private CrashContext(boolean haltJvm) {
+ this.haltJvm = haltJvm;
+ }
+
+ /** Sets the arguments that {@link BugReporter} should include with the bug report. */
+ public CrashContext withArgs(String... args) {
+ this.args = ImmutableList.copyOf(args);
+ return this;
+ }
+
+ /** Sets the arguments that {@link BugReporter} should include with the bug report. */
+ public CrashContext withArgs(List<String> args) {
+ this.args = ImmutableList.copyOf(args);
+ return this;
+ }
+
+ /** Disables bug reporting. */
+ public CrashContext withoutBugReport() {
+ sendBugReport = false;
+ return this;
+ }
+
+ /**
+ * Sets a custom additional message that should be including when handling an {@link
+ * OutOfMemoryError}.
+ */
+ public CrashContext withExtraOomInfo(String extraOomInfo) {
+ this.extraOomInfo = extraOomInfo;
+ return this;
+ }
+
+ /**
+ * Sets the {@link EventHandler} that should be notified about the {@link EventKind#FATAL} crash
+ * event.
+ *
+ * <p>If this method is not called, the event is printed to {@link System#err}.
+ */
+ public CrashContext reportingTo(EventHandler eventHandler) {
+ this.eventHandler = eventHandler;
+ return this;
+ }
+
+ boolean shouldHaltJvm() {
+ return haltJvm;
+ }
+
+ ImmutableList<String> getArgs() {
+ return args;
+ }
+
+ boolean shouldSendBugReport() {
+ return sendBugReport;
+ }
+
+ String getExtraOomInfo() {
+ return extraOomInfo;
+ }
+
+ EventHandler getEventHandler() {
+ return eventHandler;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("haltJvm", haltJvm)
+ .add("args", args)
+ .add("sendBugReport", sendBugReport)
+ .add("extraOomInfo", extraOomInfo)
+ .add("eventHandler", eventHandler)
+ .toString();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceUploader.java b/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceUploader.java
index b28b1ca..137bfa6 100644
--- a/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceUploader.java
+++ b/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceUploader.java
@@ -39,6 +39,7 @@
import com.google.devtools.build.lib.buildeventservice.BuildEventServiceUploaderCommands.StreamCompleteCommand;
import com.google.devtools.build.lib.buildeventservice.client.BuildEventServiceClient;
import com.google.devtools.build.lib.buildeventservice.client.BuildEventServiceClient.StreamContext;
+import com.google.devtools.build.lib.buildeventstream.AbortedEvent;
import com.google.devtools.build.lib.buildeventstream.ArtifactGroupNamer;
import com.google.devtools.build.lib.buildeventstream.BuildCompletingEvent;
import com.google.devtools.build.lib.buildeventstream.BuildEvent;
@@ -193,7 +194,15 @@
}
// BuildCompletingEvent marks the end of the build in the BEP event stream.
if (event instanceof BuildCompletingEvent) {
- this.buildStatus = extractBuildStatus((BuildCompletingEvent) event);
+ ExitCode exitCode = ((BuildCompletingEvent) event).getExitCode();
+ if (exitCode != null && exitCode.getNumericExitCode() == 0) {
+ buildStatus = COMMAND_SUCCEEDED;
+ } else {
+ buildStatus = COMMAND_FAILED;
+ }
+ } else if (event instanceof AbortedEvent && event.getEventId().hasBuildFinished()) {
+ // An AbortedEvent with a build finished ID means we are crashing.
+ buildStatus = COMMAND_FAILED;
}
ensureUploadThreadStarted();
@@ -342,8 +351,7 @@
}
private BuildEventStreamProtos.BuildEvent createSerializedRegularBuildEvent(
- PathConverter pathConverter,
- SendRegularBuildEventCommand buildEvent) {
+ PathConverter pathConverter, SendRegularBuildEventCommand buildEvent) {
BuildEventContext ctx =
new BuildEventContext() {
@Override
@@ -361,8 +369,7 @@
return buildEventProtocolOptions;
}
};
- BuildEventStreamProtos.BuildEvent serializedBepEvent =
- buildEvent.getEvent().asStreamProto(ctx);
+ BuildEventStreamProtos.BuildEvent serializedBepEvent = buildEvent.getEvent().asStreamProto(ctx);
// TODO(lpino): Remove this logging once we can make every single event smaller than 1MB
// as protobuf recommends.
@@ -553,7 +560,7 @@
}
acksReceived = 0;
eventQueue.addFirst(new OpenStreamCommand());
- }
+ }
break;
}
}
@@ -663,14 +670,6 @@
return Timestamps.fromMillis(clock.currentTimeMillis());
}
- private static Result extractBuildStatus(BuildCompletingEvent event) {
- if (event.getExitCode() != null && event.getExitCode().getNumericExitCode() == 0) {
- return COMMAND_SUCCEEDED;
- } else {
- return COMMAND_FAILED;
- }
- }
-
private static Status lastEventNotSentStatus() {
return Status.FAILED_PRECONDITION.withDescription(
"Server closed stream with status OK but not all events have been sent");
@@ -809,4 +808,3 @@
}
}
}
-
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
index d418563..91d48f8 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
@@ -147,10 +147,12 @@
// Error out early if multi_cpus is set, but we're not in build or test command.
if (!request.getMultiCpus().isEmpty()) {
- getReporter().handle(Event.warn(
- "The --experimental_multi_cpu option is _very_ experimental and only intended for "
- + "internal testing at this time. If you do not work on the build tool, then you "
- + "should stop now!"));
+ getReporter()
+ .handle(
+ Event.warn(
+ "The --experimental_multi_cpu option is _very_ experimental and only intended"
+ + " for internal testing at this time. If you do not work on the build"
+ + " tool, then you should stop now!"));
if (!"build".equals(request.getCommandName()) && !"test".equals(request.getCommandName())) {
throw new InvalidConfigurationException(
"The experimental setting to select multiple CPUs is only supported for 'build' and "
@@ -235,11 +237,7 @@
}
Profiler.instance().markPhase(ProfilePhase.FINISH);
} catch (Error | RuntimeException e) {
- request
- .getOutErr()
- .printErrLn(
- "Internal error thrown during build. Printing stack trace: "
- + Throwables.getStackTraceAsString(e));
+ // Don't handle the error here. We will do so in stopRequest.
catastrophe = true;
throw e;
} finally {
@@ -385,21 +383,20 @@
*
* <p>The caller is responsible for setting up and syncing the package cache.
*
- * <p>During this function's execution, the actualTargets and successfulTargets
- * fields of the request object are set.
+ * <p>During this function's execution, the actualTargets and successfulTargets fields of the
+ * request object are set.
*
* @param request the build request that this build tool is servicing, which specifies various
- * options; during this method's execution, the actualTargets and successfulTargets fields
- * of the request object are populated
+ * options; during this method's execution, the actualTargets and successfulTargets fields of
+ * the request object are populated
* @param validator target validator
* @return the result as a {@link BuildResult} object
*/
- public BuildResult processRequest(
- BuildRequest request, TargetValidator validator) {
+ public BuildResult processRequest(BuildRequest request, TargetValidator validator) {
BuildResult result = new BuildResult(request.getStartTime());
maybeSetStopOnFirstFailure(request, result);
int startSuspendCount = suspendCount();
- Throwable catastrophe = null;
+ Throwable crash = null;
DetailedExitCode detailedExitCode = null;
try {
buildTargets(request, result, validator);
@@ -468,34 +465,33 @@
.build());
reportExceptionError(e);
} catch (Throwable throwable) {
- detailedExitCode = CrashFailureDetails.detailedExitCodeForThrowable(throwable);
- catastrophe = throwable;
- Throwables.propagate(throwable);
+ crash = throwable;
+ detailedExitCode = CrashFailureDetails.detailedExitCodeForThrowable(crash);
+ Throwables.throwIfUnchecked(throwable);
+ throw new IllegalStateException(throwable);
} finally {
if (detailedExitCode == null) {
detailedExitCode =
CrashFailureDetails.detailedExitCodeForThrowable(
new IllegalStateException("Unspecified DetailedExitCode"));
}
- stopRequest(result, catastrophe, detailedExitCode, startSuspendCount);
+ stopRequest(result, crash, detailedExitCode, startSuspendCount);
}
return result;
}
- private void maybeSetStopOnFirstFailure(BuildRequest request, BuildResult result) {
+ private static void maybeSetStopOnFirstFailure(BuildRequest request, BuildResult result) {
if (shouldStopOnFailure(request)) {
result.setStopOnFirstFailure(true);
}
}
- private boolean shouldStopOnFailure(BuildRequest request) {
+ private static boolean shouldStopOnFailure(BuildRequest request) {
return !(request.getKeepGoing() && request.getExecutionOptions().testKeepGoing);
}
- /**
- * Initializes the output filter to the value given with {@code --output_filter}.
- */
+ /** Initializes the output filter to the value given with {@code --output_filter}. */
private void initializeOutputFilter(BuildRequest request) {
RegexPatternOption outputFilterOption = request.getBuildOptions().outputFilter;
if (outputFilterOption != null) {
@@ -515,14 +511,14 @@
* <p>This logs the build result, cleans up and stops the clock.
*
* @param result result to update
- * @param crash any unexpected {@link RuntimeException} or {@link Error}. May be null
+ * @param crash any unexpected {@link RuntimeException} or {@link Error}, may be null
* @param detailedExitCode describes the exit code and an optional detailed failure value to add
* to {@code result}
* @param startSuspendCount number of suspensions before the build started
*/
public void stopRequest(
BuildResult result,
- Throwable crash,
+ @Nullable Throwable crash,
DetailedExitCode detailedExitCode,
int startSuspendCount) {
Preconditions.checkState((crash == null) || !detailedExitCode.isSuccess());
@@ -530,6 +526,7 @@
Preconditions.checkState(startSuspendCount <= stopSuspendCount);
result.setUnhandledThrowable(crash);
result.setDetailedExitCode(detailedExitCode);
+
InterruptedException ie = null;
try {
env.getSkyframeExecutor().notifyCommandComplete(env.getReporter());
@@ -541,13 +538,17 @@
result.setStopTime(runtime.getClock().currentTimeMillis());
result.setWasSuspended(stopSuspendCount > startSuspendCount);
- env.getEventBus().post(new BuildPrecompleteEvent());
- env.getEventBus()
- .post(
- new BuildCompleteEvent(
- result,
- ImmutableList.of(
- BuildEventIdUtil.buildToolLogs(), BuildEventIdUtil.buildMetrics())));
+ // Skip the build complete events so that modules can run blazeShutdownOnCrash without thinking
+ // that the build completed normally. BlazeCommandDispatcher will call handleCrash.
+ if (crash == null) {
+ env.getEventBus().post(new BuildPrecompleteEvent());
+ env.getEventBus()
+ .post(
+ new BuildCompleteEvent(
+ result,
+ ImmutableList.of(
+ BuildEventIdUtil.buildToolLogs(), BuildEventIdUtil.buildMetrics())));
+ }
// Post the build tool logs event; the corresponding local files may be contributed from
// modules, and this has to happen after posting the BuildCompleteEvent because that's when
// modules add their data to the collection.
diff --git a/src/main/java/com/google/devtools/build/lib/collect/nestedset/BUILD b/src/main/java/com/google/devtools/build/lib/collect/nestedset/BUILD
index 5546df0..1a40e07 100644
--- a/src/main/java/com/google/devtools/build/lib/collect/nestedset/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/collect/nestedset/BUILD
@@ -35,7 +35,6 @@
"//src/main/java/net/starlark/java/annot",
"//src/main/java/net/starlark/java/eval",
"//third_party:auto_value",
- "//third_party:error_prone_annotations",
"//third_party:flogger",
"//third_party:guava",
"//third_party:jsr305",
diff --git a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java
index 196291d..8e725e5 100644
--- a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java
+++ b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java
@@ -21,6 +21,8 @@
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.devtools.build.lib.bugreport.BugReport;
+import com.google.devtools.build.lib.bugreport.Crash;
+import com.google.devtools.build.lib.bugreport.CrashContext;
import com.google.devtools.build.lib.collect.compacthashset.CompactHashSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetStore.MissingNestedSetException;
import com.google.devtools.build.lib.concurrent.MoreFutures;
@@ -293,8 +295,8 @@
System.err.println(
"An interrupted exception occurred during nested set deserialization, "
+ "exiting abruptly.");
- BugReport.handleCrash(e, ExitCode.INTERRUPTED);
- throw new IllegalStateException("Server should have shut down.", e);
+ BugReport.handleCrash(Crash.from(e, ExitCode.INTERRUPTED), CrashContext.halt());
+ throw new IllegalStateException("Should have halted", e);
}
} else {
return children;
diff --git a/src/main/java/com/google/devtools/build/lib/events/Event.java b/src/main/java/com/google/devtools/build/lib/events/Event.java
index a7f4f94..d201009 100644
--- a/src/main/java/com/google/devtools/build/lib/events/Event.java
+++ b/src/main/java/com/google/devtools/build/lib/events/Event.java
@@ -46,7 +46,6 @@
*/
@Immutable
public final class Event implements Serializable {
- private int hashCode;
private final EventKind kind;
@@ -66,6 +65,8 @@
*/
private final ImmutableClassToInstanceMap<Object> properties;
+ private int hashCode;
+
private Event(EventKind kind, Object message, ImmutableClassToInstanceMap<Object> properties) {
this.kind = checkNotNull(kind);
this.message = checkNotNull(message);
@@ -356,6 +357,11 @@
: of(kind, messageBytes, Location.class, location);
}
+ /** Constructs an event with kind {@link EventKind#FATAL}. */
+ public static Event fatal(String message) {
+ return of(EventKind.FATAL, message);
+ }
+
/** Constructs an event with kind {@link EventKind#ERROR}, with an optional {@link Location}. */
public static Event error(@Nullable Location location, String message) {
return location == null
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
index c8d3985..7e43f37 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
@@ -31,8 +31,9 @@
import com.google.common.io.Flushables;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.devtools.build.lib.analysis.NoBuildEvent;
-import com.google.devtools.build.lib.bugreport.BugReport;
import com.google.devtools.build.lib.bugreport.BugReporter;
+import com.google.devtools.build.lib.bugreport.Crash;
+import com.google.devtools.build.lib.bugreport.CrashContext;
import com.google.devtools.build.lib.buildeventstream.BuildEventProtocolOptions;
import com.google.devtools.build.lib.buildtool.buildevent.ProfilerStartedEvent;
import com.google.devtools.build.lib.clock.BlazeClock;
@@ -202,8 +203,7 @@
currentClientDescription);
outErr.printErrLn(message);
return createDetailedCommandResult(
- message,
- FailureDetails.Command.Code.ANOTHER_COMMAND_RUNNING);
+ message, FailureDetails.Command.Code.ANOTHER_COMMAND_RUNNING);
default:
throw new IllegalStateException();
@@ -225,8 +225,7 @@
String message = "Server shut down " + shutdownReason;
outErr.printErrLn(message);
return createDetailedCommandResult(
- message,
- FailureDetails.Command.Code.PREVIOUSLY_SHUTDOWN);
+ message, FailureDetails.Command.Code.PREVIOUSLY_SHUTDOWN);
}
BlazeCommandResult result =
execExclusively(
@@ -352,8 +351,7 @@
String message = "Starlark CPU profiler: " + ex.getMessage();
outErr.printErrLn(message);
return createDetailedCommandResult(
- message,
- FailureDetails.Command.Code.STARLARK_CPU_PROFILE_FILE_INITIALIZATION_FAILURE);
+ message, FailureDetails.Command.Code.STARLARK_CPU_PROFILE_FILE_INITIALIZATION_FAILURE);
}
try {
Starlark.startCpuProfile(out, Duration.ofMillis(10));
@@ -361,18 +359,17 @@
String message = Strings.nullToEmpty(ex.getMessage());
outErr.printErrLn(message);
return createDetailedCommandResult(
- message,
- FailureDetails.Command.Code.STARLARK_CPU_PROFILING_INITIALIZATION_FAILURE);
+ message, FailureDetails.Command.Code.STARLARK_CPU_PROFILING_INITIALIZATION_FAILURE);
}
}
BlazeCommandResult result =
createDetailedCommandResult(
- "Unknown command failure",
- FailureDetails.Command.Code.COMMAND_FAILURE_UNKNOWN);
- boolean afterCommandCalled = false;
+ "Unknown command failure", FailureDetails.Command.Code.COMMAND_FAILURE_UNKNOWN);
+ boolean needToCallAfterCommand = true;
Reporter reporter = env.getReporter();
- try (OutErr.SystemPatcher systemOutErrPatcher = reporter.getOutErr().getSystemPatcher()) {
+ OutErr.SystemPatcher systemOutErrPatcher = reporter.getOutErr().getSystemPatcher();
+ try {
// Temporary: there are modules that output events during beforeCommand, but the reporter
// isn't setup yet. Add the stored event handler to catch those events.
reporter.addHandler(storedEventHandler);
@@ -598,26 +595,27 @@
if (result.getDetailedExitCode().isSuccess()) { // don't clobber existing error
result =
createDetailedCommandResult(
- message,
- FailureDetails.Command.Code.STARLARK_CPU_PROFILE_FILE_WRITE_FAILURE);
+ message, FailureDetails.Command.Code.STARLARK_CPU_PROFILE_FILE_WRITE_FAILURE);
}
}
}
- afterCommandCalled = true;
+ needToCallAfterCommand = false;
return runtime.afterCommand(env, result);
} catch (Throwable e) {
- outErr.printErr(
- "Internal error thrown during build. Printing stack trace: "
- + Throwables.getStackTraceAsString(e));
- e.printStackTrace();
- BugReport.printBug(outErr, e, commonOptions.oomMessage);
- bugReporter.sendBugReport(e, args);
logger.atSevere().withCause(e).log("Shutting down due to exception");
- result = BlazeCommandResult.createShutdown(e);
+ Crash crash = Crash.from(e);
+ bugReporter.handleCrash(
+ crash,
+ CrashContext.keepAlive()
+ .withArgs(args)
+ .withExtraOomInfo(commonOptions.oomMessage)
+ .reportingTo(reporter));
+ needToCallAfterCommand = false; // We are crashing.
+ result = BlazeCommandResult.createShutdown(crash);
return result;
} finally {
- if (!afterCommandCalled) {
+ if (needToCallAfterCommand) {
BlazeCommandResult newResult = runtime.afterCommand(env, result);
if (!newResult.equals(result)) {
logger.atWarning().log("afterCommand yielded different result: %s %s", result, newResult);
@@ -627,6 +625,8 @@
Flushables.flushQuietly(outErr.getOutputStream());
Flushables.flushQuietly(outErr.getErrorStream());
+ systemOutErrPatcher.close();
+
env.getTimestampGranularityMonitor().waitForTimestampGranularity(outErr);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandResult.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandResult.java
index 293a76a..f4b7003 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandResult.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandResult.java
@@ -16,10 +16,10 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
+import com.google.devtools.build.lib.bugreport.Crash;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.server.CommandProtos.ExecRequest;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
-import com.google.devtools.build.lib.util.CrashFailureDetails;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.util.ExitCode;
import javax.annotation.Nullable;
@@ -83,8 +83,8 @@
return new BlazeCommandResult(DetailedExitCode.success(), null, true);
}
- static BlazeCommandResult createShutdown(Throwable e) {
- return new BlazeCommandResult(CrashFailureDetails.detailedExitCodeForThrowable(e), null, true);
+ static BlazeCommandResult createShutdown(Crash crash) {
+ return new BlazeCommandResult(crash.getDetailedExitCode(), null, true);
}
public static BlazeCommandResult success() {
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
index 1e2503f..3ad29cb 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
@@ -39,6 +39,8 @@
import com.google.devtools.build.lib.bazel.repository.downloader.Downloader;
import com.google.devtools.build.lib.bugreport.BugReport;
import com.google.devtools.build.lib.bugreport.BugReporter;
+import com.google.devtools.build.lib.bugreport.Crash;
+import com.google.devtools.build.lib.bugreport.CrashContext;
import com.google.devtools.build.lib.buildeventstream.BuildEvent.LocalFile.LocalFileType;
import com.google.devtools.build.lib.buildeventstream.BuildEventArtifactUploader;
import com.google.devtools.build.lib.buildeventstream.BuildEventArtifactUploader.UploadContext;
@@ -83,7 +85,6 @@
import com.google.devtools.build.lib.shell.SubprocessBuilder;
import com.google.devtools.build.lib.shell.SubprocessFactory;
import com.google.devtools.build.lib.util.AbruptExitException;
-import com.google.devtools.build.lib.util.CrashFailureDetails;
import com.google.devtools.build.lib.util.CustomExitCodePublisher;
import com.google.devtools.build.lib.util.CustomFailureDetailPublisher;
import com.google.devtools.build.lib.util.DebugLoggerConfigurator;
@@ -738,7 +739,7 @@
}
}
- /** Invokes {@link BlazeModule#blazeShutdownOnCrash()} on all registered modules. */
+ /** Invokes {@link BlazeModule#blazeShutdownOnCrash} on all registered modules. */
private void shutDownModulesOnCrash(DetailedExitCode exitCode) {
// TODO(b/167592709): remove verbose logging when bug resolved.
logger.atInfo().log("Shutting down modules on crash: %s", blazeModules);
@@ -775,11 +776,9 @@
// Run Blaze in server mode.
System.exit(serverMain(modules, OutErr.SYSTEM_OUT_ERR, args));
} catch (RuntimeException | Error e) { // A definite bug...
- BugReport.printBug(OutErr.SYSTEM_OUT_ERR, e, /* oomMessage = */ null);
- BugReport.sendBugReport(e, Arrays.asList(args));
- CustomFailureDetailPublisher.maybeWriteFailureDetailFile(CrashFailureDetails.forThrowable(e));
- System.exit(ExitCode.BLAZE_INTERNAL_ERROR.getNumericExitCode());
- throw e; // Shouldn't get here.
+ Crash crash = Crash.from(e);
+ BugReport.handleCrash(crash, CrashContext.keepAlive().withArgs(args));
+ System.exit(crash.getDetailedExitCode().getExitCode().getNumericExitCode());
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java b/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java
index 31fc9c3..d38bd56 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java
@@ -42,6 +42,7 @@
import com.google.devtools.build.lib.buildeventstream.BuildCompletingEvent;
import com.google.devtools.build.lib.buildeventstream.BuildEvent;
import com.google.devtools.build.lib.buildeventstream.BuildEventIdUtil;
+import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.Aborted;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.Aborted.AbortReason;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId;
import com.google.devtools.build.lib.buildeventstream.BuildEventTransport;
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/RetainedHeapLimiter.java b/src/main/java/com/google/devtools/build/lib/runtime/RetainedHeapLimiter.java
index 8ba6475..b5997b3 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/RetainedHeapLimiter.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/RetainedHeapLimiter.java
@@ -21,10 +21,10 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.GoogleLogger;
-import com.google.devtools.build.lib.bugreport.BugReport;
import com.google.devtools.build.lib.bugreport.BugReporter;
+import com.google.devtools.build.lib.bugreport.Crash;
+import com.google.devtools.build.lib.bugreport.CrashContext;
import com.google.devtools.build.lib.concurrent.ThreadSafety;
-import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.events.NullEventHandler;
import com.google.devtools.build.lib.server.FailureDetails;
@@ -208,20 +208,19 @@
if (percentUsed > occupiedHeapPercentageThreshold.getAsInt()) {
if (info.getGcCause().equals("System.gc()") && !throwingOom.getAndSet(true)) {
// Assume we got here from a GC initiated by the other branch.
- String exitMsg = BugReport.constructOomExitMessage(oomMessage);
- eventHandler.handle(Event.error(exitMsg));
- String detailedExitMsg =
- String.format(
- "%s RetainedHeapLimiter forcing exit due to GC thrashing: After back-to-back"
- + " full GCs, the tenured space is more than %s%% occupied (%s out of"
- + " a tenured space size of %s).",
- exitMsg,
- occupiedHeapPercentageThreshold.getAsInt(),
- space.getUsed(),
- space.getMax());
- logger.atSevere().log(detailedExitMsg);
+ OutOfMemoryError oom =
+ new OutOfMemoryError(
+ String.format(
+ "RetainedHeapLimiter forcing exit due to GC thrashing: After back-to-back"
+ + " full GCs, the tenured space is more than %s%% occupied (%s out of"
+ + " a tenured space size of %s).",
+ occupiedHeapPercentageThreshold.getAsInt(),
+ space.getUsed(),
+ space.getMax()));
// Exits the runtime.
- throw bugReporter.handleCrash(new OutOfMemoryError(detailedExitMsg));
+ bugReporter.handleCrash(
+ Crash.from(oom),
+ CrashContext.halt().withExtraOomInfo(oomMessage).reportingTo(eventHandler));
}
if (System.currentTimeMillis() - lastTriggeredGcInMilliseconds.get()
diff --git a/src/main/java/com/google/devtools/build/lib/util/BUILD b/src/main/java/com/google/devtools/build/lib/util/BUILD
index b7a8dc7..c5fa44a 100644
--- a/src/main/java/com/google/devtools/build/lib/util/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/util/BUILD
@@ -232,6 +232,7 @@
"ExitCode.java",
],
deps = [
+ "//third_party:error_prone_annotations",
"//third_party:guava",
"//third_party:jsr305",
],
diff --git a/src/main/java/com/google/devtools/build/lib/util/ExitCode.java b/src/main/java/com/google/devtools/build/lib/util/ExitCode.java
index 3e69108..c821a24 100644
--- a/src/main/java/com/google/devtools/build/lib/util/ExitCode.java
+++ b/src/main/java/com/google/devtools/build/lib/util/ExitCode.java
@@ -15,27 +15,27 @@
package com.google.devtools.build.lib.util;
import com.google.common.base.Objects;
+import com.google.errorprone.annotations.Immutable;
import java.util.Collection;
import java.util.HashMap;
import javax.annotation.Nullable;
/**
- * <p>Anything marked FAILURE is generally from a problem with the source code
- * under consideration. In these cases, a re-run in an identical client should
- * produce an identical return code all things being constant.
+ * Anything marked FAILURE is generally from a problem with the source code under consideration. In
+ * these cases, a re-run in an identical client should produce an identical return code all things
+ * being constant.
*
- * <p>Anything marked as an ERROR is generally a problem unrelated to the
- * source code itself. It is either something wrong with the user's command
- * line or the user's machine or environment.
+ * <p>Anything marked as an ERROR is generally a problem unrelated to the source code itself. It is
+ * either something wrong with the user's command line or the user's machine or environment.
*
- * <p>Note that these exit codes should be kept consistent with the codes
- * returned by Blaze's launcher in //devtools/blaze/main:blaze.cc
- * Blaze exit codes should be consistently classified as permanent vs.
- * transient (i.e. retriable) vs. unknown transient/permanent because users,
- * in particular infrastructure users, will use the exit code to decide whether
- * the request should be retried or not.
+ * <p>Note that these exit codes should be kept consistent with the codes returned by Blaze's
+ * launcher in //devtools/blaze/main:blaze.cc Blaze exit codes should be consistently classified as
+ * permanent vs. transient (i.e. retriable) vs. unknown transient/permanent because users, in
+ * particular infrastructure users, will use the exit code to decide whether the request should be
+ * retried or not.
*/
-public class ExitCode {
+@Immutable
+public final class ExitCode {
// Tracks all exit codes defined here and elsewhere in Bazel.
private static final HashMap<Integer, ExitCode> exitCodeRegistry = new HashMap<>();
diff --git a/src/main/java/com/google/devtools/build/lib/util/io/BUILD b/src/main/java/com/google/devtools/build/lib/util/io/BUILD
index 804b3c4..60b3782 100644
--- a/src/main/java/com/google/devtools/build/lib/util/io/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/util/io/BUILD
@@ -41,5 +41,8 @@
java_library(
name = "out-err",
srcs = OUT_ERR_SRCS,
- deps = ["//third_party:guava"],
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib/profiler",
+ "//third_party:guava",
+ ],
)
diff --git a/src/main/java/com/google/devtools/build/lib/util/io/OutErr.java b/src/main/java/com/google/devtools/build/lib/util/io/OutErr.java
index 08942f4..4e8de9f 100644
--- a/src/main/java/com/google/devtools/build/lib/util/io/OutErr.java
+++ b/src/main/java/com/google/devtools/build/lib/util/io/OutErr.java
@@ -15,6 +15,7 @@
package com.google.devtools.build.lib.util.io;
import com.google.common.base.Preconditions;
+import com.google.devtools.build.lib.profiler.SilentCloseable;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
@@ -31,9 +32,7 @@
public static final OutErr SYSTEM_OUT_ERR = create(System.out, System.err);
- /**
- * Creates a new OutErr instance from the specified output and error streams.
- */
+ /** Creates a new OutErr instance from the specified output and error streams. */
public static OutErr create(OutputStream out, OutputStream err) {
return new OutErr(out, err);
}
@@ -62,7 +61,7 @@
* ends the scope of the patch, returning {@link System#out} and {@link System#err} to what they
* were before.
*/
- public interface SystemPatcher extends AutoCloseable {
+ public interface SystemPatcher extends SilentCloseable {
void start();
}
@@ -109,38 +108,38 @@
}
/**
- * Creates a new OutErr instance from the specified stream.
- * Writes to either the output or err of the new OutErr are written
- * to outputStream, synchronized.
+ * Creates a new OutErr instance from the specified stream. Writes to either the output or err of
+ * the new OutErr are written to outputStream, synchronized.
*/
public static OutErr createSynchronizedFunnel(final OutputStream outputStream) {
- OutputStream syncOut = new OutputStream() {
+ OutputStream syncOut =
+ new OutputStream() {
- @Override
- public synchronized void write(int b) throws IOException {
- outputStream.write(b);
- }
+ @Override
+ public synchronized void write(int b) throws IOException {
+ outputStream.write(b);
+ }
- @Override
- public synchronized void write(byte b[]) throws IOException {
- outputStream.write(b);
- }
+ @Override
+ public synchronized void write(byte[] b) throws IOException {
+ outputStream.write(b);
+ }
- @Override
- public synchronized void write(byte b[], int off, int len) throws IOException {
- outputStream.write(b, off, len);
- }
+ @Override
+ public synchronized void write(byte[] b, int off, int len) throws IOException {
+ outputStream.write(b, off, len);
+ }
- @Override
- public synchronized void flush() throws IOException {
- outputStream.flush();
- }
+ @Override
+ public synchronized void flush() throws IOException {
+ outputStream.flush();
+ }
- @Override
- public synchronized void close() throws IOException {
- outputStream.close();
- }
- };
+ @Override
+ public synchronized void close() throws IOException {
+ outputStream.close();
+ }
+ };
return create(syncOut, syncOut);
}
@@ -153,9 +152,7 @@
return err;
}
- /**
- * Writes the specified string to the output stream, and flushes.
- */
+ /** Writes the specified string to the output stream, and flushes. */
public void printOut(String s) {
PrintWriter writer = new PrintWriter(out, true);
writer.print(s);
@@ -166,9 +163,7 @@
printOut(s + "\n");
}
- /**
- * Writes the specified string to the error stream, and flushes.
- */
+ /** Writes the specified string to the error stream, and flushes. */
public void printErr(String s) {
PrintWriter writer = new PrintWriter(err, true);
writer.print(s);
@@ -178,5 +173,4 @@
public void printErrLn(String s) {
printErr(s + "\n");
}
-
}
diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD
index e8986c1..efc7807 100644
--- a/src/test/java/com/google/devtools/build/lib/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/BUILD
@@ -406,6 +406,7 @@
"//src/main/java/com/google/devtools/build/lib/exec:execution_options",
"//src/main/java/com/google/devtools/build/lib/packages",
"//src/main/java/com/google/devtools/build/lib/packages/semantics",
+ "//src/main/java/com/google/devtools/build/lib/profiler",
"//src/main/java/com/google/devtools/build/lib/query2",
"//src/main/java/com/google/devtools/build/lib/query2/common:abstract-blaze-query-env",
"//src/main/java/com/google/devtools/build/lib/query2/engine",
diff --git a/src/test/java/com/google/devtools/build/lib/bugreport/BugReportTest.java b/src/test/java/com/google/devtools/build/lib/bugreport/BugReportTest.java
index 4199c3e..41d62e4 100644
--- a/src/test/java/com/google/devtools/build/lib/bugreport/BugReportTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bugreport/BugReportTest.java
@@ -20,36 +20,90 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Lists;
import com.google.devtools.build.lib.bugreport.BugReport.BlazeRuntimeInterface;
-import com.google.devtools.build.lib.server.FailureDetails.Crash;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.EventKind;
+import com.google.devtools.build.lib.server.FailureDetails;
import com.google.devtools.build.lib.server.FailureDetails.Crash.Code;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
-import com.google.devtools.build.lib.server.FailureDetails.Throwable;
import com.google.devtools.build.lib.util.CustomExitCodePublisher;
import com.google.devtools.build.lib.util.CustomFailureDetailPublisher;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.util.ExitCode;
import com.google.protobuf.ExtensionRegistry;
-import java.io.File;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.List;
+import java.util.Arrays;
import org.junit.After;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import org.mockito.ArgumentCaptor;
/** Tests for {@link BugReport}. */
-@RunWith(JUnit4.class)
-public class BugReportTest {
+@RunWith(Parameterized.class)
+public final class BugReportTest {
- private static final String TEST_EXCEPTION_NAME =
- "com.google.devtools.build.lib.bugreport.BugReportTest$TestException";
- @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+ private enum CrashType {
+ CRASH(ExitCode.BLAZE_INTERNAL_ERROR, Code.CRASH_UNKNOWN) {
+ @Override
+ Throwable createThrowable() {
+ return new IllegalStateException("Crashed");
+ }
+ },
+ OOM(ExitCode.OOM_ERROR, Code.CRASH_OOM) {
+ @Override
+ Throwable createThrowable() {
+ return new OutOfMemoryError("Java heap space");
+ }
+ };
+
+ private final ExitCode expectedExitCode;
+ private final Code expectedFailureDetailCode;
+
+ CrashType(ExitCode expectedExitCode, Code expectedFailureDetailCode) {
+ this.expectedExitCode = expectedExitCode;
+ this.expectedFailureDetailCode = expectedFailureDetailCode;
+ }
+
+ abstract Throwable createThrowable();
+ }
+
+ @Parameters
+ public static CrashType[] params() {
+ return CrashType.values();
+ }
+
+ @Rule public final TemporaryFolder tmp = new TemporaryFolder();
+
+ private final CrashType crashType;
+ private final BlazeRuntimeInterface mockRuntime = mock(BlazeRuntimeInterface.class);
+
+ private Path exitCodeFile;
+ private Path failureDetailFile;
+
+ public BugReportTest(CrashType crashType) {
+ this.crashType = crashType;
+ }
+
+ @Before
+ public void setup() throws Exception {
+ when(mockRuntime.getProductName()).thenReturn("myProductName");
+ BugReport.setRuntime(mockRuntime);
+
+ exitCodeFile = tmp.newFolder().toPath().resolve("exit_code_to_use_on_abrupt_exit");
+ failureDetailFile = tmp.newFolder().toPath().resolve("failure_detail");
+
+ CustomExitCodePublisher.setAbruptExitStatusFileDir(exitCodeFile.getParent().toString());
+ CustomFailureDetailPublisher.setFailureDetailFilePath(failureDetailFile.toString());
+ }
@After
public void resetPublishers() {
@@ -58,60 +112,94 @@
}
@Test
- public void handleCrash() throws Exception {
- BlazeRuntimeInterface mockRuntime = mock(BlazeRuntimeInterface.class);
- when(mockRuntime.getProductName()).thenReturn("myProductName");
- BugReport.setRuntime(mockRuntime);
+ public void convenienceMethod() throws Exception {
+ Throwable t = crashType.createThrowable();
+ FailureDetail expectedFailureDetail =
+ createExpectedFailureDetail(t, crashType.expectedFailureDetailCode);
- File folderForExitStatusFile = temporaryFolder.newFolder();
- CustomExitCodePublisher.setAbruptExitStatusFileDir(folderForExitStatusFile.getPath());
+ assertThrows(SecurityException.class, () -> BugReport.handleCrash(t));
+ assertThrows(t.getClass(), BugReport::maybePropagateUnprocessedThrowableIfInTest);
- Path failureDetailFilePath =
- folderForExitStatusFile.toPath().resolve("failure_detail.rawproto");
- CustomFailureDetailPublisher.setFailureDetailFilePath(failureDetailFilePath.toString());
+ verify(mockRuntime)
+ .cleanUpForCrash(DetailedExitCode.of(crashType.expectedExitCode, expectedFailureDetail));
+ verifyExitCodeWritten(crashType.expectedExitCode.getNumericExitCode());
+ verifyFailureDetailWritten(expectedFailureDetail);
+ }
+
+ @Test
+ public void halt() throws Exception {
+ Throwable t = crashType.createThrowable();
+ FailureDetail expectedFailureDetail =
+ createExpectedFailureDetail(t, crashType.expectedFailureDetailCode);
assertThrows(
- SecurityException.class,
- () ->
- BugReport.handleCrashWithoutSendingBugReport(
- functionForStackFrameTest(), ExitCode.BLAZE_INTERNAL_ERROR));
+ SecurityException.class, () -> BugReport.handleCrash(Crash.from(t), CrashContext.halt()));
+ assertThrows(t.getClass(), BugReport::maybePropagateUnprocessedThrowableIfInTest);
- Path exitStatusFile =
- folderForExitStatusFile.toPath().resolve("exit_code_to_use_on_abrupt_exit");
- List<String> strings = Files.readAllLines(exitStatusFile, StandardCharsets.UTF_8);
- assertThat(strings).containsExactly("37");
-
- FailureDetail failureDetail =
- FailureDetail.parseFrom(
- Files.readAllBytes(failureDetailFilePath), ExtensionRegistry.getEmptyRegistry());
- assertThat(failureDetail.getMessage())
- .isEqualTo(String.format("Crashed: (%s) myMessage", TEST_EXCEPTION_NAME));
- assertThat(failureDetail.hasCrash()).isTrue();
- Crash crash = failureDetail.getCrash();
- assertThat(crash.getCode()).isEqualTo(Code.CRASH_UNKNOWN);
- assertThat(crash.getCausesList()).hasSize(1);
- Throwable cause = crash.getCauses(0);
- assertThat(cause.getMessage()).isEqualTo("myMessage");
- assertThat(cause.getThrowableClass()).isEqualTo(TEST_EXCEPTION_NAME);
- assertThat(cause.getStackTraceCount()).isAtLeast(1);
- assertThat(cause.getStackTrace(0))
- .contains(
- "com.google.devtools.build.lib.bugreport.BugReportTest.functionForStackFrameTest");
- ArgumentCaptor<DetailedExitCode> exitCodeCaptor =
- ArgumentCaptor.forClass(DetailedExitCode.class);
- verify(mockRuntime).cleanUpForCrash(exitCodeCaptor.capture());
- DetailedExitCode exitCode = exitCodeCaptor.getValue();
- assertThat(exitCode.getExitCode()).isEqualTo(ExitCode.BLAZE_INTERNAL_ERROR);
- assertThat(exitCode.getFailureDetail()).isEqualTo(failureDetail);
+ verify(mockRuntime)
+ .cleanUpForCrash(DetailedExitCode.of(crashType.expectedExitCode, expectedFailureDetail));
+ verifyExitCodeWritten(crashType.expectedExitCode.getNumericExitCode());
+ verifyFailureDetailWritten(expectedFailureDetail);
}
- private TestException functionForStackFrameTest() {
- return new TestException("myMessage");
+ @Test
+ public void keepAlive() throws Exception {
+ Throwable t = crashType.createThrowable();
+ FailureDetail expectedFailureDetail =
+ createExpectedFailureDetail(t, crashType.expectedFailureDetailCode);
+
+ BugReport.handleCrash(Crash.from(t), CrashContext.keepAlive());
+ assertThrows(t.getClass(), BugReport::maybePropagateUnprocessedThrowableIfInTest);
+
+ verify(mockRuntime)
+ .cleanUpForCrash(DetailedExitCode.of(crashType.expectedExitCode, expectedFailureDetail));
+ verifyNoExitCodeWritten();
+ verifyFailureDetailWritten(expectedFailureDetail);
}
- private static class TestException extends Exception {
- private TestException(String message) {
- super(message);
- }
+ @Test
+ public void customEventHandler() {
+ Throwable t = crashType.createThrowable();
+ EventHandler handler = mock(EventHandler.class);
+ ArgumentCaptor<Event> event = ArgumentCaptor.forClass(Event.class);
+
+ BugReport.handleCrash(Crash.from(t), CrashContext.keepAlive().reportingTo(handler));
+ assertThrows(t.getClass(), BugReport::maybePropagateUnprocessedThrowableIfInTest);
+
+ verify(handler).handle(event.capture());
+ assertThat(event.getValue().getKind()).isEqualTo(EventKind.FATAL);
+ assertThat(event.getValue().getMessage()).contains(Throwables.getStackTraceAsString(t));
+ }
+
+ private void verifyExitCodeWritten(int exitCode) throws Exception {
+ assertThat(Files.readAllLines(exitCodeFile)).containsExactly(String.valueOf(exitCode));
+ }
+
+ private void verifyNoExitCodeWritten() {
+ assertThat(exitCodeFile.toFile().exists()).isFalse();
+ }
+
+ private void verifyFailureDetailWritten(FailureDetail expected) throws Exception {
+ assertThat(
+ FailureDetail.parseFrom(
+ Files.readAllBytes(failureDetailFile), ExtensionRegistry.getEmptyRegistry()))
+ .isEqualTo(expected);
+ }
+
+ private static FailureDetail createExpectedFailureDetail(
+ Throwable t, Code expectedFailureDetailCode) {
+ return FailureDetail.newBuilder()
+ .setMessage(String.format("Crashed: (%s) %s", t.getClass().getName(), t.getMessage()))
+ .setCrash(
+ FailureDetails.Crash.newBuilder()
+ .setCode(expectedFailureDetailCode)
+ .addCauses(
+ FailureDetails.Throwable.newBuilder()
+ .setThrowableClass(t.getClass().getName())
+ .setMessage(t.getMessage())
+ .addAllStackTrace(
+ Lists.transform(
+ Arrays.asList(t.getStackTrace()), StackTraceElement::toString))))
+ .build();
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/util/BUILD b/src/test/java/com/google/devtools/build/lib/buildtool/util/BUILD
index daa4253..162e4b1 100644
--- a/src/test/java/com/google/devtools/build/lib/buildtool/util/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/util/BUILD
@@ -50,6 +50,7 @@
"//src/main/java/com/google/devtools/build/lib/analysis:workspace_status_action",
"//src/main/java/com/google/devtools/build/lib/bazel:main",
"//src/main/java/com/google/devtools/build/lib/bazel:repository_module",
+ "//src/main/java/com/google/devtools/build/lib/bugreport",
"//src/main/java/com/google/devtools/build/lib/buildeventstream",
"//src/main/java/com/google/devtools/build/lib/clock",
"//src/main/java/com/google/devtools/build/lib/cmdline",
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java b/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
index 7fc4dc8..1eee17c 100644
--- a/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
@@ -14,6 +14,8 @@
package com.google.devtools.build.lib.buildtool.util;
+import static com.google.common.base.Preconditions.checkNotNull;
+
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -25,6 +27,8 @@
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.ServerDirectories;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
+import com.google.devtools.build.lib.bugreport.Crash;
+import com.google.devtools.build.lib.bugreport.CrashContext;
import com.google.devtools.build.lib.buildeventstream.BuildEventProtocolOptions;
import com.google.devtools.build.lib.buildtool.BuildRequest;
import com.google.devtools.build.lib.buildtool.BuildRequestOptions;
@@ -65,18 +69,18 @@
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParser;
import com.google.devtools.common.options.OptionsParsingException;
-import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
- * A wrapper for {@link BlazeRuntime} for testing purposes that makes it possible to exercise
- * (most) of the build machinery in integration tests. Note that {@code BlazeCommandDispatcher}
- * is not exercised here.
+ * A wrapper for {@link BlazeRuntime} for testing purposes that makes it possible to exercise (most)
+ * of the build machinery in integration tests. Note that {@code BlazeCommandDispatcher} is not
+ * exercised here.
*/
public class BlazeRuntimeWrapper {
+
private final BlazeRuntime runtime;
private CommandEnvironment env;
private final EventCollectionApparatus events;
@@ -93,9 +97,12 @@
private final List<Object> eventBusSubscribers = new ArrayList<>();
public BlazeRuntimeWrapper(
- EventCollectionApparatus events, ServerDirectories serverDirectories,
- BlazeDirectories directories, BinTools binTools, BlazeRuntime.Builder builder)
- throws Exception {
+ EventCollectionApparatus events,
+ ServerDirectories serverDirectories,
+ BlazeDirectories directories,
+ BinTools binTools,
+ BlazeRuntime.Builder builder)
+ throws Exception {
this.events = events;
runtime =
builder
@@ -174,7 +181,7 @@
}
}
- public BlazeRuntime getRuntime() {
+ public final BlazeRuntime getRuntime() {
return runtime;
}
@@ -196,10 +203,9 @@
runtime.afterCommand(env, BlazeCommandResult.success());
}
- if (optionsParser == null) {
- throw new IllegalArgumentException("The options parser must be initialized before creating "
- + "a new command environment");
- }
+ checkNotNull(
+ optionsParser,
+ "The options parser must be initialized before creating a new command environment");
env =
runtime
@@ -245,7 +251,7 @@
return optionsParser.getOptions(optionsClass);
}
- protected void finalizeBuildResult(@SuppressWarnings("unused") BuildResult request) {}
+ void finalizeBuildResult(@SuppressWarnings("unused") BuildResult request) {}
/**
* Initializes a new options parser, parsing all the options set by {@link
@@ -266,31 +272,27 @@
}
commandCreated = false;
BuildTool buildTool = new BuildTool(env);
- PrintStream origSystemOut = System.out;
- PrintStream origSystemErr = System.err;
- try {
+ try (OutErr.SystemPatcher systemOutErrPatcher =
+ env.getReporter().getOutErr().getSystemPatcher()) {
Profiler.instance()
.start(
- ImmutableSet.of(),
- /* stream= */ null,
- /* format= */ null,
- /* outputBase= */ null,
- /* buildID= */ null,
- /* recordAllDurations= */ false,
+ /*profiledTasks=*/ ImmutableSet.of(),
+ /*stream=*/ null,
+ /*format=*/ null,
+ /*outputBase=*/ null,
+ /*buildID=*/ null,
+ /*recordAllDurations=*/ false,
new JavaClock(),
- /* execStartTimeNanos= */ 42,
- /* enabledCpuUsageProfiling= */ false,
- /* slimProfile= */ false,
- /* includePrimaryOutput= */ false,
- /* includeTargetLabel= */ false);
- OutErr outErr = env.getReporter().getOutErr();
- System.setOut(new PrintStream(outErr.getOutputStream(), /*autoflush=*/ true));
- System.setErr(new PrintStream(outErr.getErrorStream(), /*autoflush=*/ true));
+ /*execStartTimeNanos=*/ 42,
+ /*enabledCpuUsageProfiling=*/ false,
+ /*slimProfile=*/ false,
+ /*includePrimaryOutput=*/ false,
+ /*includeTargetLabel=*/ false);
// This cannot go into newCommand, because we hook up the EventCollectionApparatus as a
// module, and after that ran, further changes to the apparatus aren't reflected on the
// reporter.
- for (BlazeModule module : getRuntime().getBlazeModules()) {
+ for (BlazeModule module : runtime.getBlazeModules()) {
module.beforeCommand(env);
}
EventBus eventBus = env.getEventBus();
@@ -302,36 +304,40 @@
lastRequest = createRequest(env.getCommandName(), targets);
lastResult = new BuildResult(lastRequest.getStartTime());
- boolean success = false;
- for (BlazeModule module : getRuntime().getBlazeModules()) {
+ for (BlazeModule module : runtime.getBlazeModules()) {
env.getSkyframeExecutor().injectExtraPrecomputedValues(module.getPrecomputedValues());
}
+ Crash crash = null;
+ DetailedExitCode detailedExitCode = DetailedExitCode.of(createGenericDetailedFailure());
try {
try (SilentCloseable c = Profiler.instance().profile("syncPackageLoading")) {
env.syncPackageLoading(lastRequest);
}
buildTool.buildTargets(lastRequest, lastResult, null);
- success = true;
+ detailedExitCode = DetailedExitCode.success();
+ } catch (RuntimeException | Error e) {
+ crash = Crash.from(e);
+ detailedExitCode = crash.getDetailedExitCode();
+ throw e;
} finally {
- env
- .getTimestampGranularityMonitor()
- .waitForTimestampGranularity(lastRequest.getOutErr());
+ env.getTimestampGranularityMonitor().waitForTimestampGranularity(lastRequest.getOutErr());
this.configurations = lastResult.getBuildConfigurationCollection();
finalizeBuildResult(lastResult);
buildTool.stopRequest(
lastResult,
- null,
- success
- ? DetailedExitCode.success()
- : DetailedExitCode.of(createGenericDetailedFailure()),
+ crash != null ? crash.getThrowable() : null,
+ detailedExitCode,
/*startSuspendCount=*/ 0);
getSkyframeExecutor().notifyCommandComplete(env.getReporter());
+ if (crash != null) {
+ runtime
+ .getBugReporter()
+ .handleCrash(crash, CrashContext.keepAlive().reportingTo(env.getReporter()));
+ }
}
} finally {
- System.setOut(origSystemOut);
- System.setErr(origSystemErr);
Profiler.instance().stop();
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcherTest.java b/src/test/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcherTest.java
index 18fca18..68bc52c 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcherTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcherTest.java
@@ -16,7 +16,6 @@
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.util.Arrays.asList;
-import static org.mockito.Mockito.mock;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -31,6 +30,8 @@
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventKind;
import com.google.devtools.build.lib.events.Reporter;
+import com.google.devtools.build.lib.profiler.MemoryProfiler;
+import com.google.devtools.build.lib.profiler.Profiler;
import com.google.devtools.build.lib.runtime.CommandDispatcher.LockingMode;
import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
import com.google.devtools.build.lib.server.FailureDetails;
@@ -66,6 +67,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -130,6 +132,13 @@
errorOnAfterCommand = null;
}
+ @After
+ public void stopProfilers() throws Exception {
+ // Needs to be done because we are simulating crashes but keeping the jvm alive.
+ Profiler.instance().stop();
+ MemoryProfiler.instance().stop();
+ }
+
/** Options for {@link FooCommand}. */
public static class FooOptions extends OptionsBase {
@@ -248,8 +257,8 @@
throw new OutOfMemoryError("oom message");
});
runtime.overrideCommands(ImmutableList.of(crashCommand));
- // Mocked bug reporter to avoid hard crash.
- BlazeCommandDispatcher dispatch = new BlazeCommandDispatcher(runtime, mock(BugReporter.class));
+ BlazeCommandDispatcher dispatch =
+ new BlazeCommandDispatcher(runtime, BugReporter.defaultInstance());
BlazeCommandResult directResult =
dispatch.exec(ImmutableList.of("testcommand"), "clientdesc", outErr);
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/RetainedHeapLimiterTest.java b/src/test/java/com/google/devtools/build/lib/runtime/RetainedHeapLimiterTest.java
index bd14bfc..6133e37 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/RetainedHeapLimiterTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/RetainedHeapLimiterTest.java
@@ -40,7 +40,6 @@
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.MemoryUsage;
import java.lang.ref.WeakReference;
-import java.util.List;
import javax.management.ListenerNotFoundException;
import javax.management.Notification;
import javax.management.NotificationEmitter;
@@ -136,38 +135,21 @@
@Test
public void overThreshold_oom() throws Exception {
- class OomThrowingBugReporter implements BugReporter {
- @Override
- public void sendBugReport(Throwable exception) {
- throw new IllegalStateException(exception);
- }
-
- @Override
- public void sendBugReport(Throwable exception, List<String> args, String... values) {
- throw new IllegalStateException(exception);
- }
-
- @Override
- public RuntimeException handleCrash(Throwable exception, String... args) {
- assertThat(exception).isInstanceOf(OutOfMemoryError.class);
- throw (OutOfMemoryError) exception;
- }
- }
RetainedHeapLimiter underTest =
RetainedHeapLimiter.createFromBeans(
- ImmutableList.of(mockBean), new OomThrowingBugReporter());
+ ImmutableList.of(mockBean), BugReporter.defaultInstance());
underTest.update(90, "Build fewer targets!", events);
+ assertThrows(
+ SecurityException.class, // From attempt to halt jvm in test.
+ () -> underTest.handleNotification(percentUsedAfterForcedGc(91), null));
OutOfMemoryError oom =
- assertThrows(
- OutOfMemoryError.class,
- () -> underTest.handleNotification(percentUsedAfterForcedGc(91), null));
+ assertThrows(OutOfMemoryError.class, BugReport::maybePropagateUnprocessedThrowableIfInTest);
- String expectedOomMessage = BugReport.constructOomExitMessage("Build fewer targets!");
- assertThat(oom).hasMessageThat().contains(expectedOomMessage);
assertThat(oom).hasMessageThat().contains("forcing exit due to GC thrashing");
assertThat(oom).hasMessageThat().contains("tenured space is more than 90% occupied");
- assertContainsEvent(events, expectedOomMessage, EventKind.ERROR);
+ assertContainsEvent(
+ events, BugReport.constructOomExitMessage("Build fewer targets!"), EventKind.FATAL);
}
@Test