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 }