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 */
018package org.apache.hadoop.util;
019
020import org.apache.commons.logging.Log;
021import org.apache.commons.logging.LogFactory;
022import org.apache.hadoop.classification.InterfaceAudience;
023import 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
030public 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() } for testing.
066   */
067  public static void disableSystemHalt() {
068    systemHaltDisabled = true;
069  }
070
071  /**
072   * @return true if terminate has been called
073   */
074  public static boolean terminateCalled() {
075    // Either we set this member or we actually called System#exit
076    return firstExitException != null;
077  }
078
079  /**
080   * @return true if halt has been called
081   */
082  public static boolean haltCalled() {
083    return firstHaltException != null;
084  }
085
086  /**
087   * @return the first ExitException thrown, null if none thrown yet
088   */
089  public static ExitException getFirstExitException() {
090    return firstExitException;
091  }
092
093  /**
094   * @return the first {@code HaltException} thrown, null if none thrown yet
095   */
096  public static HaltException getFirstHaltException() {
097    return firstHaltException;
098  }
099
100  /**
101   * Reset the tracking of process termination. This is for use in unit tests
102   * where one test in the suite expects an exit but others do not.
103   */
104  public static void resetFirstExitException() {
105    firstExitException = null;
106  }
107
108  public static void resetFirstHaltException() {
109    firstHaltException = null;
110  }
111
112  /**
113   * Terminate the current process. Note that terminate is the *only* method
114   * that should be used to terminate the daemon processes.
115   *
116   * @param status
117   *          exit code
118   * @param msg
119   *          message used to create the {@code ExitException}
120   * @throws ExitException
121   *           if System.exit is disabled for test purposes
122   */
123  public static void terminate(int status, String msg) throws ExitException {
124    LOG.info("Exiting with status " + status);
125    if (systemExitDisabled) {
126      ExitException ee = new ExitException(status, msg);
127      LOG.fatal("Terminate called", ee);
128      if (null == firstExitException) {
129        firstExitException = ee;
130      }
131      throw ee;
132    }
133    System.exit(status);
134  }
135
136  /**
137   * Forcibly terminates the currently running Java virtual machine.
138   *
139   * @param status
140   *          exit code
141   * @param msg
142   *          message used to create the {@code HaltException}
143   * @throws HaltException
144   *           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   *
163   * @param status
164   * @param t
165   *          throwable used to create the ExitException
166   * @throws ExitException
167   *           if System.exit is disabled for test purposes
168   */
169  public static void terminate(int status, Throwable t) throws ExitException {
170    terminate(status, StringUtils.stringifyException(t));
171  }
172
173  /**
174   * Forcibly terminates the currently running Java virtual machine.
175   *
176   * @param status
177   * @param t
178   * @throws ExitException
179   */
180  public static void halt(int status, Throwable t) throws HaltException {
181    halt(status, StringUtils.stringifyException(t));
182  }
183
184  /**
185   * Like {@link terminate(int, String)} without a message.
186   *
187   * @param status
188   * @throws ExitException
189   *           if System.exit is disabled for test purposes
190   */
191  public static void terminate(int status) throws ExitException {
192    terminate(status, "ExitException");
193  }
194
195  /**
196   * Forcibly terminates the currently running Java virtual machine.
197   * @param status
198   * @throws ExitException
199   */
200  public static void halt(int status) throws HaltException {
201    halt(status, "HaltException");
202  }
203}