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() } 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    }