Skip to content

Commit a8a679f

Browse files
committed
Merge branch 'branch-3.3' into HADOOP-18832-remove-rs-api-3.3
2 parents 9d0544f + 3a6d865 commit a8a679f

File tree

3 files changed

+245
-39
lines changed

3 files changed

+245
-39
lines changed

hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FSDataInputStream.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ public boolean seekToNewSource(long targetPos) throws IOException {
144144
*
145145
* @return the underlying input stream
146146
*/
147-
@InterfaceAudience.LimitedPrivate({"HDFS"})
147+
@InterfaceAudience.Public
148+
@InterfaceStability.Stable
148149
public InputStream getWrappedStream() {
149150
return in;
150151
}

hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/ExitUtil.java

Lines changed: 116 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
*/
1818
package org.apache.hadoop.util;
1919

20+
import java.util.concurrent.atomic.AtomicReference;
21+
2022
import org.apache.hadoop.classification.InterfaceAudience;
2123
import org.apache.hadoop.classification.InterfaceStability;
2224
import 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
/**
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.util;
19+
20+
import static org.apache.hadoop.test.LambdaTestUtils.intercept;
21+
import static org.junit.Assert.assertFalse;
22+
import static org.junit.Assert.assertNull;
23+
import static org.junit.Assert.assertSame;
24+
import static org.junit.Assert.assertTrue;
25+
26+
import org.junit.After;
27+
import org.junit.Before;
28+
import org.junit.Test;
29+
30+
import org.apache.hadoop.util.ExitUtil.ExitException;
31+
import org.apache.hadoop.util.ExitUtil.HaltException;
32+
import org.apache.hadoop.test.AbstractHadoopTestBase;
33+
34+
public class TestExitUtil extends AbstractHadoopTestBase {
35+
36+
@Before
37+
public void before() {
38+
ExitUtil.disableSystemExit();
39+
ExitUtil.disableSystemHalt();
40+
ExitUtil.resetFirstExitException();
41+
ExitUtil.resetFirstHaltException();
42+
}
43+
44+
@After
45+
public void after() {
46+
ExitUtil.resetFirstExitException();
47+
ExitUtil.resetFirstHaltException();
48+
}
49+
50+
@Test
51+
public void testGetSetExitExceptions() throws Throwable {
52+
// prepare states and exceptions
53+
ExitException ee1 = new ExitException(1, "TestExitUtil forged 1st ExitException");
54+
ExitException ee2 = new ExitException(2, "TestExitUtil forged 2nd ExitException");
55+
// check proper initial settings
56+
assertFalse("ExitUtil.terminateCalled initial value should be false",
57+
ExitUtil.terminateCalled());
58+
assertNull("ExitUtil.getFirstExitException initial value should be null",
59+
ExitUtil.getFirstExitException());
60+
61+
// simulate/check 1st call
62+
ExitException ee = intercept(ExitException.class, ()->ExitUtil.terminate(ee1));
63+
assertSame("ExitUtil.terminate should have rethrown its ExitException argument but it "
64+
+ "had thrown something else", ee1, ee);
65+
assertTrue("ExitUtil.terminateCalled should be true after 1st ExitUtil.terminate call",
66+
ExitUtil.terminateCalled());
67+
assertSame("ExitUtil.terminate should store its 1st call's ExitException",
68+
ee1, ExitUtil.getFirstExitException());
69+
70+
// simulate/check 2nd call not overwritting 1st one
71+
ee = intercept(ExitException.class, ()->ExitUtil.terminate(ee2));
72+
assertSame("ExitUtil.terminate should have rethrown its HaltException argument but it "
73+
+ "had thrown something else", ee2, ee);
74+
assertTrue("ExitUtil.terminateCalled should still be true after 2nd ExitUtil.terminate call",
75+
ExitUtil.terminateCalled());
76+
// 2nd call rethrown the 2nd ExitException yet only the 1st only should have been stored
77+
assertSame("ExitUtil.terminate when called twice should only remember 1st call's "
78+
+ "ExitException", ee1, ExitUtil.getFirstExitException());
79+
80+
// simulate cleanup, also tries to make sure state is ok for all junit still has to do
81+
ExitUtil.resetFirstExitException();
82+
assertFalse("ExitUtil.terminateCalled should be false after "
83+
+ "ExitUtil.resetFirstExitException call", ExitUtil.terminateCalled());
84+
assertNull("ExitUtil.getFirstExitException should be null after "
85+
+ "ExitUtil.resetFirstExitException call", ExitUtil.getFirstExitException());
86+
}
87+
88+
@Test
89+
public void testGetSetHaltExceptions() throws Throwable {
90+
// prepare states and exceptions
91+
ExitUtil.disableSystemHalt();
92+
ExitUtil.resetFirstHaltException();
93+
HaltException he1 = new HaltException(1, "TestExitUtil forged 1st HaltException");
94+
HaltException he2 = new HaltException(2, "TestExitUtil forged 2nd HaltException");
95+
96+
// check proper initial settings
97+
assertFalse("ExitUtil.haltCalled initial value should be false",
98+
ExitUtil.haltCalled());
99+
assertNull("ExitUtil.getFirstHaltException initial value should be null",
100+
ExitUtil.getFirstHaltException());
101+
102+
// simulate/check 1st call
103+
HaltException he = intercept(HaltException.class, ()->ExitUtil.halt(he1));
104+
assertSame("ExitUtil.halt should have rethrown its HaltException argument but it had "
105+
+"thrown something else", he1, he);
106+
assertTrue("ExitUtil.haltCalled should be true after 1st ExitUtil.halt call",
107+
ExitUtil.haltCalled());
108+
assertSame("ExitUtil.halt should store its 1st call's HaltException",
109+
he1, ExitUtil.getFirstHaltException());
110+
111+
// simulate/check 2nd call not overwritting 1st one
112+
he = intercept(HaltException.class, ()->ExitUtil.halt(he2));
113+
assertSame("ExitUtil.halt should have rethrown its HaltException argument but it had "
114+
+"thrown something else", he2, he);
115+
assertTrue("ExitUtil.haltCalled should still be true after 2nd ExitUtil.halt call",
116+
ExitUtil.haltCalled());
117+
assertSame("ExitUtil.halt when called twice should only remember 1st call's HaltException",
118+
he1, ExitUtil.getFirstHaltException());
119+
120+
// simulate cleanup, also tries to make sure state is ok for all junit still has to do
121+
ExitUtil.resetFirstHaltException();
122+
assertFalse("ExitUtil.haltCalled should be false after "
123+
+ "ExitUtil.resetFirstHaltException call", ExitUtil.haltCalled());
124+
assertNull("ExitUtil.getFirstHaltException should be null after "
125+
+ "ExitUtil.resetFirstHaltException call", ExitUtil.getFirstHaltException());
126+
}
127+
}

0 commit comments

Comments
 (0)