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    }