1717 */
1818package org .apache .hadoop .util ;
1919
20+ import java .util .concurrent .atomic .AtomicReference ;
21+
2022import org .apache .hadoop .classification .InterfaceAudience ;
2123import org .apache .hadoop .classification .InterfaceStability ;
2224import org .slf4j .Logger ;
@@ -36,8 +38,10 @@ public final class ExitUtil {
3638 LOG = LoggerFactory .getLogger (ExitUtil .class .getName ());
3739 private static volatile boolean systemExitDisabled = false ;
3840 private static volatile boolean systemHaltDisabled = false ;
39- private static volatile ExitException firstExitException ;
40- private static volatile HaltException firstHaltException ;
41+ private static final AtomicReference <ExitException > FIRST_EXIT_EXCEPTION =
42+ new AtomicReference <>();
43+ private static final AtomicReference <HaltException > FIRST_HALT_EXCEPTION =
44+ new AtomicReference <>();
4145 /** Message raised from an exit exception if none were provided: {@value}. */
4246 public static final String EXIT_EXCEPTION_MESSAGE = "ExitException" ;
4347 /** Message raised from a halt exception if none were provided: {@value}. */
@@ -159,92 +163,166 @@ public static void disableSystemHalt() {
159163 */
160164 public static boolean terminateCalled () {
161165 // Either we set this member or we actually called System#exit
162- return firstExitException != null ;
166+ return FIRST_EXIT_EXCEPTION . get () != null ;
163167 }
164168
165169 /**
166170 * @return true if halt has been called.
167171 */
168172 public static boolean haltCalled () {
169- return firstHaltException != null ;
173+ // Either we set this member or we actually called Runtime#halt
174+ return FIRST_HALT_EXCEPTION .get () != null ;
170175 }
171176
172177 /**
173- * @return the first ExitException thrown, null if none thrown yet.
178+ * @return the first {@code ExitException} thrown, null if none thrown yet.
174179 */
175180 public static ExitException getFirstExitException () {
176- return firstExitException ;
181+ return FIRST_EXIT_EXCEPTION . get () ;
177182 }
178183
179184 /**
180185 * @return the first {@code HaltException} thrown, null if none thrown yet.
181186 */
182187 public static HaltException getFirstHaltException () {
183- return firstHaltException ;
188+ return FIRST_HALT_EXCEPTION . get () ;
184189 }
185190
186191 /**
187192 * Reset the tracking of process termination. This is for use in unit tests
188193 * where one test in the suite expects an exit but others do not.
189194 */
190195 public static void resetFirstExitException () {
191- firstExitException = null ;
196+ FIRST_EXIT_EXCEPTION . set ( null ) ;
192197 }
193198
199+ /**
200+ * Reset the tracking of process termination. This is for use in unit tests
201+ * where one test in the suite expects a halt but others do not.
202+ */
194203 public static void resetFirstHaltException () {
195- firstHaltException = null ;
204+ FIRST_HALT_EXCEPTION . set ( null ) ;
196205 }
197206
198207 /**
208+ * Suppresses if legit and returns the first non-null of the two. Legit means
209+ * <code>suppressor</code> if neither <code>null</code> nor <code>suppressed</code>.
210+ * @param suppressor <code>Throwable</code> that suppresses <code>suppressed</code>
211+ * @param suppressed <code>Throwable</code> that is suppressed by <code>suppressor</code>
212+ * @return <code>suppressor</code> if not <code>null</code>, <code>suppressed</code> otherwise
213+ */
214+ private static <T extends Throwable > T addSuppressed (T suppressor , T suppressed ) {
215+ if (suppressor == null ) {
216+ return suppressed ;
217+ }
218+ if (suppressor != suppressed ) {
219+ suppressor .addSuppressed (suppressed );
220+ }
221+ return suppressor ;
222+ }
223+
224+ /**
225+ * Exits the JVM if exit is enabled, rethrow provided exception or any raised error otherwise.
199226 * Inner termination: either exit with the exception's exit code,
200227 * or, if system exits are disabled, rethrow the exception.
201228 * @param ee exit exception
229+ * @throws ExitException if {@link System#exit(int)} is disabled and not suppressed by an Error
230+ * @throws Error if {@link System#exit(int)} is disabled and one Error arise, suppressing
231+ * anything else, even <code>ee</code>
202232 */
203- public static synchronized void terminate (ExitException ee )
204- throws ExitException {
205- int status = ee .getExitCode ();
206- String msg = ee .getMessage ();
233+ public static void terminate (final ExitException ee ) throws ExitException {
234+ final int status = ee .getExitCode ();
235+ Error caught = null ;
207236 if (status != 0 ) {
208- //exit indicates a problem, log it
209- LOG .debug ("Exiting with status {}: {}" , status , msg , ee );
210- LOG .info ("Exiting with status {}: {}" , status , msg );
237+ try {
238+ // exit indicates a problem, log it
239+ String msg = ee .getMessage ();
240+ LOG .debug ("Exiting with status {}: {}" , status , msg , ee );
241+ LOG .info ("Exiting with status {}: {}" , status , msg );
242+ } catch (Error e ) {
243+ // errors have higher priority than HaltException, it may be re-thrown.
244+ // OOM and ThreadDeath are 2 examples of Errors to re-throw
245+ caught = e ;
246+ } catch (Throwable t ) {
247+ // all other kind of throwables are suppressed
248+ addSuppressed (ee , t );
249+ }
211250 }
212251 if (systemExitDisabled ) {
213- LOG .error ("Terminate called" , ee );
214- if (!terminateCalled ()) {
215- firstExitException = ee ;
252+ try {
253+ LOG .error ("Terminate called" , ee );
254+ } catch (Error e ) {
255+ // errors have higher priority again, if it's a 2nd error, the 1st one suprpesses it
256+ caught = addSuppressed (caught , e );
257+ } catch (Throwable t ) {
258+ // all other kind of throwables are suppressed
259+ addSuppressed (ee , t );
216260 }
261+ FIRST_EXIT_EXCEPTION .compareAndSet (null , ee );
262+ if (caught != null ) {
263+ caught .addSuppressed (ee );
264+ throw caught ;
265+ }
266+ // not suppressed by a higher prority error
217267 throw ee ;
268+ } else {
269+ // when exit is enabled, whatever Throwable happened, we exit the VM
270+ System .exit (status );
218271 }
219- System .exit (status );
220272 }
221273
222274 /**
223- * Forcibly terminates the currently running Java virtual machine.
224- * The exception argument is rethrown if JVM halting is disabled.
225- * @param ee the exception containing the status code, message and any stack
275+ * Halts the JVM if halt is enabled, rethrow provided exception or any raised error otherwise.
276+ * If halt is disabled, this method throws either the exception argument if no
277+ * error arise, the first error if at least one arise, suppressing <code>he</code>.
278+ * If halt is enabled, all throwables are caught, even errors.
279+ *
280+ * @param he the exception containing the status code, message and any stack
226281 * trace.
227- * @throws HaltException if {@link Runtime#halt(int)} is disabled.
282+ * @throws HaltException if {@link Runtime#halt(int)} is disabled and not suppressed by an Error
283+ * @throws Error if {@link Runtime#halt(int)} is disabled and one Error arise, suppressing
284+ * anyuthing else, even <code>he</code>
228285 */
229- public static synchronized void halt (HaltException ee ) throws HaltException {
230- int status = ee .getExitCode ();
231- String msg = ee .getMessage ();
232- try {
233- if (status != 0 ) {
234- //exit indicates a problem, log it
235- LOG .info ("Halt with status {}: {}" , status , msg , ee );
286+ public static void halt (final HaltException he ) throws HaltException {
287+ final int status = he .getExitCode ();
288+ Error caught = null ;
289+ if (status != 0 ) {
290+ try {
291+ // exit indicates a problem, log it
292+ String msg = he .getMessage ();
293+ LOG .info ("Halt with status {}: {}" , status , msg , he );
294+ } catch (Error e ) {
295+ // errors have higher priority than HaltException, it may be re-thrown.
296+ // OOM and ThreadDeath are 2 examples of Errors to re-throw
297+ caught = e ;
298+ } catch (Throwable t ) {
299+ // all other kind of throwables are suppressed
300+ addSuppressed (he , t );
236301 }
237- } catch (Exception ignored ) {
238- // ignore exceptions here, as it may be due to an out of memory situation
239302 }
303+ // systemHaltDisabled is volatile and not used in scenario nheding atomicty,
304+ // thus it does not nhed a synchronized access nor a atomic access
240305 if (systemHaltDisabled ) {
241- LOG .error ("Halt called" , ee );
242- if (!haltCalled ()) {
243- firstHaltException = ee ;
306+ try {
307+ LOG .error ("Halt called" , he );
308+ } catch (Error e ) {
309+ // errors have higher priority again, if it's a 2nd error, the 1st one suprpesses it
310+ caught = addSuppressed (caught , e );
311+ } catch (Throwable t ) {
312+ // all other kind of throwables are suppressed
313+ addSuppressed (he , t );
244314 }
245- throw ee ;
315+ FIRST_HALT_EXCEPTION .compareAndSet (null , he );
316+ if (caught != null ) {
317+ caught .addSuppressed (he );
318+ throw caught ;
319+ }
320+ // not suppressed by a higher prority error
321+ throw he ;
322+ } else {
323+ // when halt is enabled, whatever Throwable happened, we halt the VM
324+ Runtime .getRuntime ().halt (status );
246325 }
247- Runtime .getRuntime ().halt (status );
248326 }
249327
250328 /**
0 commit comments