This is an automated email from the ASF dual-hosted git repository.

kturner pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/accumulo.git


The following commit(s) were added to refs/heads/main by this push:
     new 3546261  checks for nested error in AccumuloUncaughtExceptionHandler 
(#2530)
3546261 is described below

commit 3546261f007a8780b0f478ab870a4f1a1d6cb3fc
Author: Keith Turner <ktur...@apache.org>
AuthorDate: Mon Feb 28 12:22:12 2022 -0500

    checks for nested error in AccumuloUncaughtExceptionHandler (#2530)
---
 .../threads/AccumuloUncaughtExceptionHandler.java  |  31 ++++++-
 .../AccumuloUncaughtExceptionHandlerTest.java      | 103 +++++++++++++++++++++
 2 files changed, 133 insertions(+), 1 deletion(-)

diff --git 
a/core/src/main/java/org/apache/accumulo/core/util/threads/AccumuloUncaughtExceptionHandler.java
 
b/core/src/main/java/org/apache/accumulo/core/util/threads/AccumuloUncaughtExceptionHandler.java
index a9c4004..babfea6 100644
--- 
a/core/src/main/java/org/apache/accumulo/core/util/threads/AccumuloUncaughtExceptionHandler.java
+++ 
b/core/src/main/java/org/apache/accumulo/core/util/threads/AccumuloUncaughtExceptionHandler.java
@@ -32,11 +32,40 @@ class AccumuloUncaughtExceptionHandler implements 
UncaughtExceptionHandler {
 
   private static final Logger LOG = 
LoggerFactory.getLogger(AccumuloUncaughtExceptionHandler.class);
 
+  private static boolean isError(Throwable t, int depth) {
+
+    if (depth > 32) {
+      // This is a peculiar exception. No error has been found, but recursing 
too deep may cause a
+      // stack overflow so going to stop.
+      return false;
+    }
+
+    while (t != null) {
+      if (t instanceof Error) {
+        return true;
+      }
+
+      for (Throwable suppressed : t.getSuppressed()) {
+        if (isError(suppressed, depth + 1)) {
+          return true;
+        }
+      }
+
+      t = t.getCause();
+    }
+
+    return false;
+  }
+
+  static boolean isError(Throwable t) {
+    return isError(t, 0);
+  }
+
   @Override
   public void uncaughtException(Thread t, Throwable e) {
     if (e instanceof Exception) {
       LOG.error("Caught an Exception in {}. Thread is dead.", t, e);
-    } else if (e instanceof Error) {
+    } else if (isError(e)) {
       try {
         e.printStackTrace();
         System.err.println("Error thrown in thread: " + t + ", halting VM.");
diff --git 
a/core/src/test/java/org/apache/accumulo/core/util/threads/AccumuloUncaughtExceptionHandlerTest.java
 
b/core/src/test/java/org/apache/accumulo/core/util/threads/AccumuloUncaughtExceptionHandlerTest.java
new file mode 100644
index 0000000..88a0f7a
--- /dev/null
+++ 
b/core/src/test/java/org/apache/accumulo/core/util/threads/AccumuloUncaughtExceptionHandlerTest.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.accumulo.core.util.threads;
+
+import static 
org.apache.accumulo.core.util.threads.AccumuloUncaughtExceptionHandler.isError;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+
+import org.junit.Test;
+
+public class AccumuloUncaughtExceptionHandlerTest {
+
+  @Test
+  public void testIsError_noerror() {
+    assertFalse(isError(new IOException()));
+    assertFalse(isError(new UncheckedIOException(new IOException())));
+
+    Exception e = new UncheckedIOException(new IOException());
+    e.addSuppressed(new RuntimeException());
+    e.addSuppressed(new RuntimeException());
+    assertFalse(isError(e));
+  }
+
+  @Test
+  public void testIsError_error() {
+    assertTrue(isError(new UnsatisfiedLinkError()));
+    assertTrue(isError(new RuntimeException(new OutOfMemoryError())));
+    assertTrue(isError(new RuntimeException(new RuntimeException(new 
UnsatisfiedLinkError()))));
+
+    // check for cases where error has a non-error cause
+    assertTrue(isError(new Error(new RuntimeException())));
+    assertTrue(isError(new RuntimeException(new Error(new 
RuntimeException()))));
+    assertTrue(
+        isError(new RuntimeException(new RuntimeException(new Error(new 
RuntimeException())))));
+
+    // check for suppressed exception that has error
+    Exception e = new UncheckedIOException(new IOException());
+    e.addSuppressed(new RuntimeException());
+    e.addSuppressed(new RuntimeException());
+    e.addSuppressed(new RuntimeException(new UnsatisfiedLinkError()));
+    assertTrue(isError(e));
+    assertTrue(isError(new RuntimeException(e)));
+
+    // check for suppressed exception that has non terminal error
+    Exception e2 = new UncheckedIOException(new IOException());
+    e2.addSuppressed(new RuntimeException());
+    e2.addSuppressed(new RuntimeException());
+    e2.addSuppressed(new RuntimeException(new Error(new RuntimeException())));
+    assertTrue(isError(e2));
+    assertTrue(isError(new RuntimeException(e2)));
+
+    // test suppressed with error a few levels deep
+    Exception ed1 = new UncheckedIOException(new IOException());
+    Exception ed2 = new UncheckedIOException(new IOException());
+    Exception ed3 = new RuntimeException(new OutOfMemoryError());
+    ed1.addSuppressed(ed2);
+    ed2.addSuppressed(ed3);
+    assertTrue(isError(ed1));
+    assertTrue(isError(new RuntimeException(ed1)));
+
+    // test case where suppressed is an error
+    Exception e4 = new UncheckedIOException(new IOException());
+    e4.addSuppressed(new RuntimeException());
+    e4.addSuppressed(new RuntimeException());
+    e4.addSuppressed(new Error(new RuntimeException())); // try direct error 
(not nested as cause)
+    assertTrue(isError(e4));
+    assertTrue(isError(new RuntimeException(e4)));
+  }
+
+  @Test
+  public void testIsError_loop() {
+    Exception e1 = new UncheckedIOException(new IOException());
+    Exception e2 = new RuntimeException(new RuntimeException());
+    Exception e3 = new IllegalStateException();
+
+    // create a chain of suppressed exceptions that forms a loop
+    e1.addSuppressed(e2);
+    e2.addSuppressed(e3);
+    e3.addSuppressed(e1);
+
+    assertFalse(isError(e1));
+    assertFalse(isError(new RuntimeException(e1)));
+  }
+}

Reply via email to