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 boolean systemHaltDisabled = false;
034 private static volatile ExitException firstExitException;
035 private static volatile HaltException firstHaltException;
036
037 public static class ExitException extends RuntimeException {
038 private static final long serialVersionUID = 1L;
039 public final int status;
040
041 public ExitException(int status, String msg) {
042 super(msg);
043 this.status = status;
044 }
045 }
046
047 public static class HaltException extends RuntimeException {
048 private static final long serialVersionUID = 1L;
049 public final int status;
050
051 public HaltException(int status, String msg) {
052 super(msg);
053 this.status = status;
054 }
055 }
056
057 /**
058 * Disable the use of System.exit for testing.
059 */
060 public static void disableSystemExit() {
061 systemExitDisabled = true;
062 }
063
064 /**
065 * Disable the use of {@code Runtime.getRuntime().halt() }
066 * for testing.
067 */
068 public static void disableSystemHalt() {
069 systemHaltDisabled = true;
070 }
071
072 /**
073 * @return true if terminate has been called
074 */
075 public static boolean terminateCalled() {
076 // Either we set this member or we actually called System#exit
077 return firstExitException != null;
078 }
079
080 /**
081 * @return true if halt has been called
082 */
083 public static boolean haltCalled() {
084 return firstHaltException != null;
085 }
086
087 /**
088 * @return the first ExitException thrown, null if none thrown yet
089 */
090 public static ExitException getFirstExitException() {
091 return firstExitException;
092 }
093
094 /**
095 * @return the first {@code HaltException} thrown, null if none thrown yet
096 */
097 public static HaltException getFirstHaltException() {
098 return firstHaltException;
099 }
100
101 /**
102 * Reset the tracking of process termination. This is for use
103 * in unit tests where one test in the suite expects an exit
104 * but others do not.
105 */
106 public static void resetFirstExitException() {
107 firstExitException = null;
108 }
109
110 public static void resetFirstHaltException() {
111 firstHaltException = null;
112 }
113 /**
114 * Clear the previous exit record.
115 */
116 public static void clearTerminateCalled() {
117 resetFirstExitException();
118 }
119
120 /**
121 * Terminate the current process. Note that terminate is the *only* method
122 * that should be used to terminate the daemon processes.
123 * @param status exit code
124 * @param msg message used to create the {@code ExitException}
125 * @throws ExitException if System.exit is disabled for test purposes
126 */
127 public static void terminate(int status, String msg) throws ExitException {
128 LOG.info("Exiting with status " + status);
129 if (systemExitDisabled) {
130 ExitException ee = new ExitException(status, msg);
131 LOG.fatal("Terminate called", ee);
132 if (null == firstExitException) {
133 firstExitException = ee;
134 }
135 throw ee;
136 }
137 System.exit(status);
138 }
139
140 /**
141 * Forcibly terminates the currently running Java virtual machine.
142 * @param status exit code
143 * @param msg message used to create the {@code HaltException}
144 * @throws HaltException if Runtime.getRuntime().halt() is disabled for test purposes
145 */
146 public static void halt(int status, String msg) throws HaltException {
147 LOG.info("Halt with status " + status + " Message: " + msg);
148 if (systemHaltDisabled) {
149 HaltException ee = new HaltException(status, msg);
150 LOG.fatal("Halt called", ee);
151 if (null == firstHaltException) {
152 firstHaltException = ee;
153 }
154 throw ee;
155 }
156 Runtime.getRuntime().halt(status);
157 }
158
159 /**
160 * Like {@link terminate(int, String)} but uses the given throwable to
161 * initialize the ExitException.
162 * @param status
163 * @param t throwable used to create the ExitException
164 * @throws ExitException if System.exit is disabled for test purposes
165 */
166 public static void terminate(int status, Throwable t) throws ExitException {
167 terminate(status, StringUtils.stringifyException(t));
168 }
169 /**
170 * Forcibly terminates the currently running Java virtual machine.
171 * @param status
172 * @param t
173 * @throws ExitException
174 */
175 public static void halt(int status, Throwable t) throws HaltException {
176 halt(status, StringUtils.stringifyException(t));
177 }
178 /**
179 * Like {@link terminate(int, String)} without a message.
180 * @param status
181 * @throws ExitException if System.exit is disabled for test purposes
182 */
183 public static void terminate(int status) throws ExitException {
184 terminate(status, "ExitException");
185 }
186 /**
187 * Forcibly terminates the currently running Java virtual machine.
188 * @param status
189 * @throws ExitException
190 */
191 public static void halt(int status) throws HaltException {
192 halt(status, "HaltException");
193 }
194 }