| // Copyright 2018 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.util; | |
| import com.google.common.base.Throwables; | |
| import java.util.logging.Logger; | |
| import javax.annotation.Nullable; | |
| /** Utilities for crashing the jvm. */ | |
| public class BazelCrashUtils { | |
| private static final Logger logger = Logger.getLogger(BazelCrashUtils.class.getName()); | |
| private static final Object HALT_LOCK = new Object(); | |
| @Nullable private static volatile Throwable unprocessedThrowableInTest = null; | |
| private BazelCrashUtils() {} | |
| /** | |
| * In tests, System.exit is disabled. Thus, the main thread should call this method whenever it is | |
| * about to block on thread completion that might hang because of a failed halt below. | |
| */ | |
| public static void maybePropagateUnprocessedThrowableIfInTest() { | |
| synchronized (HALT_LOCK) { | |
| Throwable lastUnprocessedThrowableInTest = unprocessedThrowableInTest; | |
| unprocessedThrowableInTest = null; | |
| if (lastUnprocessedThrowableInTest != null) { | |
| Throwables.throwIfUnchecked(lastUnprocessedThrowableInTest); | |
| throw new RuntimeException(lastUnprocessedThrowableInTest); | |
| } | |
| } | |
| } | |
| /** Terminates the jvm with the exit code {@link ExitCode#BLAZE_INTERNAL_ERROR}. */ | |
| public static RuntimeException halt(Throwable cause) { | |
| return halt(cause, ExitCode.BLAZE_INTERNAL_ERROR.getNumericExitCode()); | |
| } | |
| /** | |
| * Terminates the jvm with the given {@code exitCode}. Note that, in server mode, the jvm's exit | |
| * code has nothing to do with the exit code picked by the blaze client, except as communicated by | |
| * the 'custom_exit_code_on_abrupt_exit' behavior. If this fails, the exit code picked by the | |
| * client will default to INTERNAL_ERROR. | |
| */ | |
| public static RuntimeException halt(Throwable crash, int exitCode) { | |
| synchronized (HALT_LOCK) { | |
| try { | |
| logCrashDetails(crash); | |
| unprocessedThrowableInTest = crash; | |
| CustomExitCodePublisher.maybeWriteExitStatusFile(exitCode); | |
| } catch (OutOfMemoryError oom) { | |
| // In case we tried to halt with a non-OOM, but OOMed during the lead up to the halt. | |
| Runtime.getRuntime().halt(exitCode); | |
| } finally { | |
| doDie(crash, exitCode); | |
| } | |
| throw new IllegalStateException("impossible"); | |
| } | |
| } | |
| private static void logCrashDetails(Throwable crash) { | |
| logger.severe( | |
| "About to call System#exit. Halt requested due to crash. See stderr for details (" | |
| + crash.getMessage() | |
| + ")"); | |
| System.err.println("About to call System#exit. Halt requested due to crash"); | |
| crash.printStackTrace(); | |
| System.err.println("... called from"); | |
| new Exception().printStackTrace(); | |
| } | |
| private static void doDie(Throwable crash, int exitCode) { | |
| try { | |
| // Bypass shutdown hooks if we encountered an OOM, they're likely to take way too long to be | |
| // sensible to run in such a hostile environment. | |
| Throwable cause = crash; | |
| while (cause != null) { | |
| if (cause instanceof OutOfMemoryError) { | |
| Runtime.getRuntime().halt(exitCode); | |
| } | |
| cause = cause.getCause(); | |
| } | |
| } finally { | |
| System.exit(exitCode); | |
| } | |
| } | |
| } |