// Copyright 2014 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.runtime; import com.google.common.base.Joiner; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.analysis.BlazeVersionInfo; import com.google.devtools.build.lib.util.CustomExitCodePublisher; import com.google.devtools.build.lib.util.ExitCode; import com.google.devtools.build.lib.util.LoggingUtil; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.util.io.OutErr; import java.io.PrintStream; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; /** * Utility methods for sending bug reports. * * <p> Note, code in this class must be extremely robust. There's nothing * worse than a crash-handler that itself crashes! */ public abstract class BugReport { private BugReport() {} private static Logger LOG = Logger.getLogger(BugReport.class.getName()); private static BlazeVersionInfo versionInfo = BlazeVersionInfo.instance(); private static BlazeRuntime runtime = null; private static AtomicBoolean alreadyHandlingCrash = new AtomicBoolean(false); public static void setRuntime(BlazeRuntime newRuntime) { Preconditions.checkNotNull(newRuntime); Preconditions.checkState(runtime == null, "runtime already set: %s, %s", runtime, newRuntime); runtime = newRuntime; } private static String getProductName() { return runtime != null ? runtime.getProductName() : "<unknown>"; } /** * Logs the unhandled exception with a special prefix signifying that this was a crash. * * @param exception the unhandled exception to display. * @param args additional values to record in the message. * @param values Additional string values to clarify the exception. */ public static void sendBugReport(Throwable exception, List<String> args, String... values) { if (!versionInfo.isReleasedBlaze()) { LOG.info("(Not a released binary; not logged.)"); return; } logException(exception, filterClientEnv(args), values); } private static void logCrash(Throwable throwable, String... args) { LOG.severe("Crash: " + Throwables.getStackTraceAsString(throwable)); BugReport.sendBugReport(throwable, Arrays.asList(args)); BugReport.printBug(OutErr.SYSTEM_OUT_ERR, throwable); System.err.println(getProductName() + " crash in async thread:"); throwable.printStackTrace(); } /** * Print and send a bug report, and exit with the proper Blaze code. Does not exit if called a * second time. This method tries hard to catch any throwables thrown during its execution and * halts the runtime in that case. */ public static void handleCrash(Throwable throwable, String... args) { int exitCode = getExitCodeForThrowable(throwable); try { if (alreadyHandlingCrash.compareAndSet(false, true)) { try { logCrash(throwable, args); if (runtime != null) { runtime.notifyCommandComplete(exitCode); // We don't call runtime#shutDown() here because all it does is shut down the modules, // and who knows if they can be trusted. Instead, we call runtime#shutdownOnCrash() // which attempts to cleanly shutdown those modules that might have something pending // to do as a best-effort operation. runtime.shutdownOnCrash(); } CustomExitCodePublisher.maybeWriteExitStatusFile(exitCode); } finally { // 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(exitCode); } } else { logCrash(throwable, args); } } catch (Throwable t) { System.err.println( "An crash occurred while " + getProductName() + " was trying to handle a crash! Please file a bug against " + getProductName() + " and include the information below."); System.err.println("Original uncaught exception:"); throwable.printStackTrace(System.err); System.err.println("Exception encountered during BugReport#handleCrash:"); t.printStackTrace(System.err); Runtime.getRuntime().halt(exitCode); } } /** Get exit code corresponding to throwable. */ public static int getExitCodeForThrowable(Throwable throwable) { return (Throwables.getRootCause(throwable) instanceof OutOfMemoryError) ? ExitCode.OOM_ERROR.getNumericExitCode() : ExitCode.BLAZE_INTERNAL_ERROR.getNumericExitCode(); } private static void printThrowableTo(OutErr outErr, Throwable e) { PrintStream err = new PrintStream(outErr.getErrorStream()); e.printStackTrace(err); err.flush(); LOG.log(Level.SEVERE, getProductName() + " crashed", e); } /** * 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) { if (e instanceof OutOfMemoryError) { outErr.printErr( e.getMessage() + "\n\n" + getProductName() + " ran out of memory and crashed.\n"); } else { printThrowableTo(outErr, e); } } /** * Filters {@code args} by removing any item that starts with "--client_env", * then returns this as an immutable list. * * <p>The client's environment variables may contain sensitive data, so we filter it out. */ private static List<String> filterClientEnv(Iterable<String> args) { if (args == null) { return null; } ImmutableList.Builder<String> filteredArgs = ImmutableList.builder(); for (String arg : args) { if (arg != null && !arg.startsWith("--client_env")) { filteredArgs.add(arg); } } 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. private static void logException(Throwable exception, List<String> args, String... values) { LOG.severe("Exception: " + Throwables.getStackTraceAsString(exception)); // 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: "); LoggingUtil.logToRemote(Level.SEVERE, preamble + Joiner.on(' ').join(args), exception, values); } }