3838import java .util .Arrays ;
3939import java .util .Collections ;
4040import java .util .HashSet ;
41+ import java .util .IdentityHashMap ;
4142import java .util .LinkedList ;
4243import java .util .List ;
4344import java .util .Objects ;
4445import java .util .Optional ;
4546import java .util .Queue ;
4647import java .util .Set ;
48+ import java .util .function .Predicate ;
4749import java .util .stream .Collectors ;
4850
4951public final class ExceptionsHelper {
@@ -185,22 +187,14 @@ public static <T extends Throwable> T useOrSuppress(T first, T second) {
185187 * @return Corruption indicating exception if one is found, otherwise {@code null}
186188 */
187189 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 ;
200194 }
201- } while (( t = t . getCause ()) != null );
202- }
203- return null ;
195+ }
196+ return false ;
197+ }). orElse ( null ) ;
204198 }
205199
206200 /**
@@ -213,7 +207,11 @@ public static IOException unwrapCorruption(Throwable t) {
213207 */
214208 public static Throwable unwrap (Throwable t , Class <?>... clazzes ) {
215209 if (t != null ) {
210+ final Set <Throwable > seen = Collections .newSetFromMap (new IdentityHashMap <>());
216211 do {
212+ if (seen .add (t ) == false ) {
213+ return null ;
214+ }
217215 for (Class <?> clazz : clazzes ) {
218216 if (clazz .isInstance (t )) {
219217 return t ;
@@ -246,33 +244,22 @@ public static boolean reThrowIfNotNull(@Nullable Throwable e) {
246244 return true ;
247245 }
248246
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 );
261251 }
262252
263253 final Queue <Throwable > queue = new LinkedList <>();
264254 queue .add (cause );
265- int iterations = 0 ;
255+ final Set < Throwable > seen = Collections . newSetFromMap ( new IdentityHashMap <>()) ;
266256 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- }
273257 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 );
276263 }
277264 Collections .addAll (queue , current .getSuppressed ());
278265 if (current .getCause () != null ) {
@@ -283,21 +270,24 @@ public static Optional<Error> maybeError(final Throwable cause, final Logger log
283270 }
284271
285272 /**
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
287277 */
288278 public static Optional <Error > maybeError (final Throwable cause ) {
289- return maybeError (cause , logger );
279+ return unwrapCausesAndSuppressed (cause , t -> t instanceof Error );
290280 }
291281
292282 /**
293283 * If the specified cause is an unrecoverable error, this method will rethrow the cause on a separate thread so that it can not be
294284 * 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.
296286 *
297287 * @param throwable the throwable to possibly throw on another thread
298288 */
299289 public static void maybeDieOnAnotherThread (final Throwable throwable ) {
300- ExceptionsHelper .maybeError (throwable , logger ).ifPresent (error -> {
290+ ExceptionsHelper .maybeError (throwable ).ifPresent (error -> {
301291 /*
302292 * Here be dragons. We want to rethrow this so that it bubbles up to the uncaught exception handler. Yet, sometimes the stack
303293 * contains statements that catch any throwable (e.g., Netty, and the JDK futures framework). This means that a rethrow here
0 commit comments