blob: dbe6663b2e7150478df4dae576ee201248ce2174 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.hadoop.util;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Facilitates hooking process termination for tests, debugging
* and embedding.
*
* Hadoop code that attempts to call {@link System#exit(int)}
* or {@link Runtime#halt(int)} MUST invoke it via these methods.
*/
@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce", "YARN"})
@InterfaceStability.Unstable
public final class ExitUtil {
private static final Logger
LOG = LoggerFactory.getLogger(ExitUtil.class.getName());
private static volatile boolean systemExitDisabled = false;
private static volatile boolean systemHaltDisabled = false;
private static volatile ExitException firstExitException;
private static volatile HaltException firstHaltException;
/** Message raised from an exit exception if none were provided: {@value}. */
public static final String EXIT_EXCEPTION_MESSAGE = "ExitException";
/** Message raised from a halt exception if none were provided: {@value}. */
public static final String HALT_EXCEPTION_MESSAGE = "HaltException";
private ExitUtil() {
}
/**
* An exception raised when a call to {@link #terminate(int)} was
* called and system exits were blocked.
*/
public static class ExitException extends RuntimeException
implements ExitCodeProvider {
private static final long serialVersionUID = 1L;
/**
* The status code.
*/
public final int status;
public ExitException(int status, String msg) {
super(msg);
this.status = status;
}
public ExitException(int status,
String message,
Throwable cause) {
super(message, cause);
this.status = status;
}
public ExitException(int status, Throwable cause) {
super(cause);
this.status = status;
}
@Override
public int getExitCode() {
return status;
}
/**
* String value does not include exception type, just exit code and message.
* @return the exit code and any message
*/
@Override
public String toString() {
String message = getMessage();
if (message == null) {
message = super.toString();
}
return Integer.toString(status) + ": " + message;
}
}
/**
* An exception raised when a call to {@link #terminate(int)} was
* called and system halts were blocked.
*/
public static class HaltException extends RuntimeException
implements ExitCodeProvider {
private static final long serialVersionUID = 1L;
public final int status;
public HaltException(int status, Throwable cause) {
super(cause);
this.status = status;
}
public HaltException(int status, String msg) {
super(msg);
this.status = status;
}
public HaltException(int status,
String message,
Throwable cause) {
super(message, cause);
this.status = status;
}
@Override
public int getExitCode() {
return status;
}
/**
* String value does not include exception type, just exit code and message.
* @return the exit code and any message
*/
@Override
public String toString() {
String message = getMessage();
if (message == null) {
message = super.toString();
}
return Integer.toString(status) + ": " + message;
}
}
/**
* Disable the use of System.exit for testing.
*/
public static void disableSystemExit() {
systemExitDisabled = true;
}
/**
* Disable the use of {@code Runtime.getRuntime().halt() } for testing.
*/
public static void disableSystemHalt() {
systemHaltDisabled = true;
}
/**
* @return true if terminate has been called.
*/
public static boolean terminateCalled() {
// Either we set this member or we actually called System#exit
return firstExitException != null;
}
/**
* @return true if halt has been called.
*/
public static boolean haltCalled() {
return firstHaltException != null;
}
/**
* @return the first ExitException thrown, null if none thrown yet.
*/
public static ExitException getFirstExitException() {
return firstExitException;
}
/**
* @return the first {@code HaltException} thrown, null if none thrown yet.
*/
public static HaltException getFirstHaltException() {
return firstHaltException;
}
/**
* Reset the tracking of process termination. This is for use in unit tests
* where one test in the suite expects an exit but others do not.
*/
public static void resetFirstExitException() {
firstExitException = null;
}
public static void resetFirstHaltException() {
firstHaltException = null;
}
/**
* Inner termination: either exit with the exception's exit code,
* or, if system exits are disabled, rethrow the exception.
* @param ee exit exception
*/
public static synchronized void terminate(ExitException ee)
throws ExitException {
int status = ee.getExitCode();
String msg = ee.getMessage();
if (status != 0) {
//exit indicates a problem, log it
LOG.debug("Exiting with status {}: {}", status, msg, ee);
LOG.info("Exiting with status {}: {}", status, msg);
}
if (systemExitDisabled) {
LOG.error("Terminate called", ee);
if (!terminateCalled()) {
firstExitException = ee;
}
throw ee;
}
System.exit(status);
}
/**
* Forcibly terminates the currently running Java virtual machine.
* The exception argument is rethrown if JVM halting is disabled.
* @param ee the exception containing the status code, message and any stack
* trace.
* @throws HaltException if {@link Runtime#halt(int)} is disabled.
*/
public static synchronized void halt(HaltException ee) throws HaltException {
int status = ee.getExitCode();
String msg = ee.getMessage();
try {
if (status != 0) {
//exit indicates a problem, log it
LOG.debug("Halt with status {}: {}", status, msg, ee);
LOG.info("Halt with status {}: {}", status, msg, msg);
}
} catch (Exception ignored) {
// ignore exceptions here, as it may be due to an out of memory situation
}
if (systemHaltDisabled) {
LOG.error("Halt called", ee);
if (!haltCalled()) {
firstHaltException = ee;
}
throw ee;
}
Runtime.getRuntime().halt(status);
}
/**
* Like {@link #terminate(int, String)} but uses the given throwable to
* build the message to display or throw as an
* {@link ExitException}.
* <p>
* @param status exit code to use if the exception is not an ExitException.
* @param t throwable which triggered the termination. If this exception
* is an {@link ExitException} its status overrides that passed in.
* @throws ExitException if {@link System#exit(int)} is disabled.
*/
public static void terminate(int status, Throwable t) throws ExitException {
if (t instanceof ExitException) {
terminate((ExitException) t);
} else {
terminate(new ExitException(status, t));
}
}
/**
* Forcibly terminates the currently running Java virtual machine.
*
* @param status exit code to use if the exception is not a HaltException.
* @param t throwable which triggered the termination. If this exception
* is a {@link HaltException} its status overrides that passed in.
* @throws HaltException if {@link System#exit(int)} is disabled.
*/
public static void halt(int status, Throwable t) throws HaltException {
if (t instanceof HaltException) {
halt((HaltException) t);
} else {
halt(new HaltException(status, t));
}
}
/**
* Like {@link #terminate(int, Throwable)} without a message.
*
* @param status exit code
* @throws ExitException if {@link System#exit(int)} is disabled.
*/
public static void terminate(int status) throws ExitException {
terminate(status, EXIT_EXCEPTION_MESSAGE);
}
/**
* Terminate the current process. Note that terminate is the *only* method
* that should be used to terminate the daemon processes.
*
* @param status exit code
* @param msg message used to create the {@code ExitException}
* @throws ExitException if {@link System#exit(int)} is disabled.
*/
public static void terminate(int status, String msg) throws ExitException {
terminate(new ExitException(status, msg));
}
/**
* Forcibly terminates the currently running Java virtual machine.
* @param status status code
* @throws HaltException if {@link Runtime#halt(int)} is disabled.
*/
public static void halt(int status) throws HaltException {
halt(status, HALT_EXCEPTION_MESSAGE);
}
/**
* Forcibly terminates the currently running Java virtual machine.
* @param status status code
* @param message message
* @throws HaltException if {@link Runtime#halt(int)} is disabled.
*/
public static void halt(int status, String message) throws HaltException {
halt(new HaltException(status, message));
}
/**
* Handler for out of memory events -no attempt is made here
* to cleanly shutdown or support halt blocking; a robust
* printing of the event to stderr is all that can be done.
* @param oome out of memory event
*/
public static void haltOnOutOfMemory(OutOfMemoryError oome) {
//After catching an OOM java says it is undefined behavior, so don't
//even try to clean up or we can get stuck on shutdown.
try {
System.err.println("Halting due to Out Of Memory Error...");
} catch (Throwable err) {
//Again we done want to exit because of logging issues.
}
Runtime.getRuntime().halt(-1);
}
}