38
38
import java .util .Arrays ;
39
39
import java .util .Collections ;
40
40
import java .util .HashSet ;
41
+ import java .util .IdentityHashMap ;
41
42
import java .util .LinkedList ;
42
43
import java .util .List ;
43
44
import java .util .Objects ;
44
45
import java .util .Optional ;
45
46
import java .util .Queue ;
46
47
import java .util .Set ;
48
+ import java .util .function .Predicate ;
47
49
import java .util .stream .Collectors ;
48
50
49
51
public final class ExceptionsHelper {
@@ -185,22 +187,14 @@ public static <T extends Throwable> T useOrSuppress(T first, T second) {
185
187
* @return Corruption indicating exception if one is found, otherwise {@code null}
186
188
*/
187
189
public static IOException unwrapCorruption (Throwable t ) {
188
- if (t != null ) {
189
- do {
190
- for (Class <?> clazz : CORRUPTION_EXCEPTIONS ) {
191
- if (clazz .isInstance (t )) {
192
- return (IOException ) t ;
193
- }
194
- }
195
- for (Throwable suppressed : t .getSuppressed ()) {
196
- IOException corruptionException = unwrapCorruption (suppressed );
197
- if (corruptionException != null ) {
198
- return corruptionException ;
199
- }
190
+ return t == null ? null : ExceptionsHelper .<IOException >unwrapCausesAndSuppressed (t , cause -> {
191
+ for (Class <?> clazz : CORRUPTION_EXCEPTIONS ) {
192
+ if (clazz .isInstance (cause )) {
193
+ return true ;
200
194
}
201
- } while (( t = t . getCause ()) != null );
202
- }
203
- return null ;
195
+ }
196
+ return false ;
197
+ }). orElse ( null ) ;
204
198
}
205
199
206
200
/**
@@ -213,7 +207,11 @@ public static IOException unwrapCorruption(Throwable t) {
213
207
*/
214
208
public static Throwable unwrap (Throwable t , Class <?>... clazzes ) {
215
209
if (t != null ) {
210
+ final Set <Throwable > seen = Collections .newSetFromMap (new IdentityHashMap <>());
216
211
do {
212
+ if (seen .add (t ) == false ) {
213
+ return null ;
214
+ }
217
215
for (Class <?> clazz : clazzes ) {
218
216
if (clazz .isInstance (t )) {
219
217
return t ;
@@ -246,33 +244,22 @@ public static boolean reThrowIfNotNull(@Nullable Throwable e) {
246
244
return true ;
247
245
}
248
246
249
- static final int MAX_ITERATIONS = 1024 ;
250
-
251
- /**
252
- * Unwrap the specified throwable looking for any suppressed errors or errors as a root cause of the specified throwable.
253
- *
254
- * @param cause the root throwable
255
- * @return an optional error if one is found suppressed or a root cause in the tree rooted at the specified throwable
256
- */
257
- public static Optional <Error > maybeError (final Throwable cause , final Logger logger ) {
258
- // early terminate if the cause is already an error
259
- if (cause instanceof Error ) {
260
- return Optional .of ((Error ) cause );
247
+ @ SuppressWarnings ("unchecked" )
248
+ private static <T extends Throwable > Optional <T > unwrapCausesAndSuppressed (Throwable cause , Predicate <Throwable > predicate ) {
249
+ if (predicate .test (cause )) {
250
+ return Optional .of ((T ) cause );
261
251
}
262
252
263
253
final Queue <Throwable > queue = new LinkedList <>();
264
254
queue .add (cause );
265
- int iterations = 0 ;
255
+ final Set < Throwable > seen = Collections . newSetFromMap ( new IdentityHashMap <>()) ;
266
256
while (queue .isEmpty () == false ) {
267
- iterations ++;
268
- // this is a guard against deeply nested or circular chains of exceptions
269
- if (iterations > MAX_ITERATIONS ) {
270
- logger .warn ("giving up looking for fatal errors" , cause );
271
- break ;
272
- }
273
257
final Throwable current = queue .remove ();
274
- if (current instanceof Error ) {
275
- return Optional .of ((Error ) current );
258
+ if (seen .add (current ) == false ) {
259
+ continue ;
260
+ }
261
+ if (predicate .test (current )) {
262
+ return Optional .of ((T ) current );
276
263
}
277
264
Collections .addAll (queue , current .getSuppressed ());
278
265
if (current .getCause () != null ) {
@@ -283,21 +270,24 @@ public static Optional<Error> maybeError(final Throwable cause, final Logger log
283
270
}
284
271
285
272
/**
286
- * See {@link #maybeError(Throwable, Logger)}. Uses the class-local logger.
273
+ * Unwrap the specified throwable looking for any suppressed errors or errors as a root cause of the specified throwable.
274
+ *
275
+ * @param cause the root throwable
276
+ * @return an optional error if one is found suppressed or a root cause in the tree rooted at the specified throwable
287
277
*/
288
278
public static Optional <Error > maybeError (final Throwable cause ) {
289
- return maybeError (cause , logger );
279
+ return unwrapCausesAndSuppressed (cause , t -> t instanceof Error );
290
280
}
291
281
292
282
/**
293
283
* If the specified cause is an unrecoverable error, this method will rethrow the cause on a separate thread so that it can not be
294
284
* caught and bubbles up to the uncaught exception handler. Note that the cause tree is examined for any {@link Error}. See
295
- * {@link #maybeError(Throwable, Logger )} for the semantics.
285
+ * {@link #maybeError(Throwable)} for the semantics.
296
286
*
297
287
* @param throwable the throwable to possibly throw on another thread
298
288
*/
299
289
public static void maybeDieOnAnotherThread (final Throwable throwable ) {
300
- ExceptionsHelper .maybeError (throwable , logger ).ifPresent (error -> {
290
+ ExceptionsHelper .maybeError (throwable ).ifPresent (error -> {
301
291
/*
302
292
* Here be dragons. We want to rethrow this so that it bubbles up to the uncaught exception handler. Yet, sometimes the stack
303
293
* contains statements that catch any throwable (e.g., Netty, and the JDK futures framework). This means that a rethrow here
0 commit comments