001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 package org.apache.hadoop.util;
019
020 import org.apache.commons.logging.Log;
021 import org.apache.commons.logging.LogFactory;
022 import org.apache.hadoop.classification.InterfaceAudience;
023 import org.apache.hadoop.classification.InterfaceStability;
024
025 /**
026 * Facilitates hooking process termination for tests and debugging.
027 */
028 @InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
029 @InterfaceStability.Unstable
030 public final class ExitUtil {
031 private final static Log LOG = LogFactory.getLog(ExitUtil.class.getName());
032 private static volatile boolean systemExitDisabled = false;
033 private static volatile ExitException firstExitException;
034
035 public static class ExitException extends RuntimeException {
036 private static final long serialVersionUID = 1L;
037 public final int status;
038
039 public ExitException(int status, String msg) {
040 super(msg);
041 this.status = status;
042 }
043 }
044
045 /**
046 * Disable the use of System.exit for testing.
047 */
048 public static void disableSystemExit() {
049 systemExitDisabled = true;
050 }
051
052 /**
053 * @return true if terminate has been called
054 */
055 public static boolean terminateCalled() {
056 // Either we set this member or we actually called System#exit
057 return firstExitException != null;
058 }
059
060 /**
061 * @return the first ExitException thrown, null if none thrown yet
062 */
063 public static ExitException getFirstExitException() {
064 return firstExitException;
065 }
066
067 /**
068 * Reset the tracking of process termination. This is for use
069 * in unit tests where one test in the suite expects an exit
070 * but others do not.
071 */
072 public static void resetFirstExitException() {
073 firstExitException = null;
074 }
075
076 /**
077 * Terminate the current process. Note that terminate is the *only* method
078 * that should be used to terminate the daemon processes.
079 * @param status exit code
080 * @param msg message used to create the ExitException
081 * @throws ExitException if System.exit is disabled for test purposes
082 */
083 public static void terminate(int status, String msg) throws ExitException {
084 LOG.info("Exiting with status " + status);
085 if (systemExitDisabled) {
086 ExitException ee = new ExitException(status, msg);
087 LOG.fatal("Terminate called", ee);
088 if (null == firstExitException) {
089 firstExitException = ee;
090 }
091 throw ee;
092 }
093 System.exit(status);
094 }
095
096 /**
097 * Like {@link terminate(int, String)} but uses the given throwable to
098 * initialize the ExitException.
099 * @param status
100 * @param t throwable used to create the ExitException
101 * @throws ExitException if System.exit is disabled for test purposes
102 */
103 public static void terminate(int status, Throwable t) throws ExitException {
104 terminate(status, StringUtils.stringifyException(t));
105 }
106
107 /**
108 * Like {@link terminate(int, String)} without a message.
109 * @param status
110 * @throws ExitException if System.exit is disabled for test purposes
111 */
112 public static void terminate(int status) throws ExitException {
113 terminate(status, "ExitException");
114 }
115 }