diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Utils.java b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Utils.java index 8d1c9d61a0b..9470424b381 100644 --- a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Utils.java +++ b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Utils.java @@ -38,12 +38,9 @@ import org.elasticsearch.common.logging.ESLoggerFactory; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Optional; -import java.util.Queue; import java.util.concurrent.atomic.AtomicBoolean; public class Netty4Utils { @@ -171,7 +168,8 @@ public class Netty4Utils { * @param cause the throwable to test */ public static void maybeDie(final Throwable cause) { - final Optional maybeError = maybeError(cause); + final Logger logger = ESLoggerFactory.getLogger(Netty4Utils.class); + final Optional maybeError = ExceptionsHelper.maybeError(cause, logger); if (maybeError.isPresent()) { /* * Here be dragons. We want to rethrow this so that it bubbles up to the uncaught exception handler. Yet, Netty wraps too many @@ -182,7 +180,6 @@ public class Netty4Utils { try { // try to log the current stack trace final String formatted = ExceptionsHelper.formatStackTrace(Thread.currentThread().getStackTrace()); - final Logger logger = ESLoggerFactory.getLogger(Netty4Utils.class); logger.error("fatal error on the network layer\n{}", formatted); } finally { new Thread( @@ -194,40 +191,4 @@ public class Netty4Utils { } } - static final int MAX_ITERATIONS = 1024; - - /** - * Unwrap the specified throwable looking for any suppressed errors or errors as a root cause of the specified throwable. - * - * @param cause the root throwable - * - * @return an optional error if one is found suppressed or a root cause in the tree rooted at the specified throwable - */ - static Optional maybeError(final Throwable cause) { - // early terminate if the cause is already an error - if (cause instanceof Error) { - return Optional.of((Error) cause); - } - - final Queue queue = new LinkedList<>(); - queue.add(cause); - int iterations = 0; - while (!queue.isEmpty()) { - iterations++; - if (iterations > MAX_ITERATIONS) { - ESLoggerFactory.getLogger(Netty4Utils.class).warn("giving up looking for fatal errors on the network layer", cause); - break; - } - final Throwable current = queue.remove(); - if (current instanceof Error) { - return Optional.of((Error) current); - } - Collections.addAll(queue, current.getSuppressed()); - if (current.getCause() != null) { - queue.add(current.getCause()); - } - } - return Optional.empty(); - } - } diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/transport/netty4/Netty4UtilsTests.java b/modules/transport-netty4/src/test/java/org/elasticsearch/transport/netty4/Netty4UtilsTests.java index 43be6f0efdd..8372a8540b8 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/transport/netty4/Netty4UtilsTests.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/transport/netty4/Netty4UtilsTests.java @@ -22,7 +22,6 @@ package org.elasticsearch.transport.netty4; import io.netty.buffer.ByteBuf; import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.Unpooled; -import io.netty.handler.codec.DecoderException; import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.bytes.AbstractBytesReferenceTestCase; import org.elasticsearch.common.bytes.BytesArray; @@ -33,9 +32,6 @@ import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; import org.elasticsearch.test.ESTestCase; import java.io.IOException; -import java.util.Optional; - -import static org.hamcrest.CoreMatchers.equalTo; public class Netty4UtilsTests extends ESTestCase { @@ -79,60 +75,6 @@ public class Netty4UtilsTests extends ESTestCase { assertArrayEquals(BytesReference.toBytes(ref), BytesReference.toBytes(bytesReference)); } - public void testMaybeError() { - final Error outOfMemoryError = new OutOfMemoryError(); - assertError(outOfMemoryError, outOfMemoryError); - - final DecoderException decoderException = new DecoderException(outOfMemoryError); - assertError(decoderException, outOfMemoryError); - - final Exception e = new Exception(); - e.addSuppressed(decoderException); - assertError(e, outOfMemoryError); - - final int depth = randomIntBetween(1, 16); - Throwable cause = new Exception(); - boolean fatal = false; - Error error = null; - for (int i = 0; i < depth; i++) { - final int length = randomIntBetween(1, 4); - for (int j = 0; j < length; j++) { - if (!fatal && rarely()) { - error = new Error(); - cause.addSuppressed(error); - fatal = true; - } else { - cause.addSuppressed(new Exception()); - } - } - if (!fatal && rarely()) { - cause = error = new Error(cause); - fatal = true; - } else { - cause = new Exception(cause); - } - } - if (fatal) { - assertError(cause, error); - } else { - assertFalse(Netty4Utils.maybeError(cause).isPresent()); - } - - assertFalse(Netty4Utils.maybeError(new Exception(new DecoderException())).isPresent()); - - Throwable chain = outOfMemoryError; - for (int i = 0; i < Netty4Utils.MAX_ITERATIONS; i++) { - chain = new Exception(chain); - } - assertFalse(Netty4Utils.maybeError(chain).isPresent()); - } - - private void assertError(final Throwable cause, final Error error) { - final Optional maybeError = Netty4Utils.maybeError(cause); - assertTrue(maybeError.isPresent()); - assertThat(maybeError.get(), equalTo(error)); - } - private BytesReference getRandomizedBytesReference(int length) throws IOException { // we know bytes stream output always creates a paged bytes reference, we use it to create randomized content ReleasableBytesStreamOutput out = new ReleasableBytesStreamOutput(length, bigarrays); diff --git a/server/src/main/java/org/elasticsearch/ExceptionsHelper.java b/server/src/main/java/org/elasticsearch/ExceptionsHelper.java index 902bcee63fb..05ac4d942b3 100644 --- a/server/src/main/java/org/elasticsearch/ExceptionsHelper.java +++ b/server/src/main/java/org/elasticsearch/ExceptionsHelper.java @@ -34,8 +34,12 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; +import java.util.Optional; +import java.util.Queue; import java.util.Set; import java.util.stream.Collectors; @@ -128,6 +132,42 @@ public final class ExceptionsHelper { return Arrays.stream(stackTrace).skip(1).map(e -> "\tat " + e).collect(Collectors.joining("\n")); } + static final int MAX_ITERATIONS = 1024; + + /** + * Unwrap the specified throwable looking for any suppressed errors or errors as a root cause of the specified throwable. + * + * @param cause the root throwable + * + * @return an optional error if one is found suppressed or a root cause in the tree rooted at the specified throwable + */ + public static Optional maybeError(final Throwable cause, final Logger logger) { + // early terminate if the cause is already an error + if (cause instanceof Error) { + return Optional.of((Error) cause); + } + + final Queue queue = new LinkedList<>(); + queue.add(cause); + int iterations = 0; + while (!queue.isEmpty()) { + iterations++; + if (iterations > MAX_ITERATIONS) { + logger.warn("giving up looking for fatal errors", cause); + break; + } + final Throwable current = queue.remove(); + if (current instanceof Error) { + return Optional.of((Error) current); + } + Collections.addAll(queue, current.getSuppressed()); + if (current.getCause() != null) { + queue.add(current.getCause()); + } + } + return Optional.empty(); + } + /** * Rethrows the first exception in the list and adds all remaining to the suppressed list. * If the given list is empty no exception is thrown diff --git a/server/src/test/java/org/elasticsearch/ExceptionsHelperTests.java b/server/src/test/java/org/elasticsearch/ExceptionsHelperTests.java new file mode 100644 index 00000000000..011f5b380ec --- /dev/null +++ b/server/src/test/java/org/elasticsearch/ExceptionsHelperTests.java @@ -0,0 +1,87 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch 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.elasticsearch; + +import org.apache.commons.codec.DecoderException; +import org.elasticsearch.test.ESTestCase; + +import java.util.Optional; + +import static org.elasticsearch.ExceptionsHelper.MAX_ITERATIONS; +import static org.elasticsearch.ExceptionsHelper.maybeError; +import static org.hamcrest.CoreMatchers.equalTo; + +public class ExceptionsHelperTests extends ESTestCase { + + public void testMaybeError() { + final Error outOfMemoryError = new OutOfMemoryError(); + assertError(outOfMemoryError, outOfMemoryError); + + final DecoderException decoderException = new DecoderException(outOfMemoryError); + assertError(decoderException, outOfMemoryError); + + final Exception e = new Exception(); + e.addSuppressed(decoderException); + assertError(e, outOfMemoryError); + + final int depth = randomIntBetween(1, 16); + Throwable cause = new Exception(); + boolean fatal = false; + Error error = null; + for (int i = 0; i < depth; i++) { + final int length = randomIntBetween(1, 4); + for (int j = 0; j < length; j++) { + if (!fatal && rarely()) { + error = new Error(); + cause.addSuppressed(error); + fatal = true; + } else { + cause.addSuppressed(new Exception()); + } + } + if (!fatal && rarely()) { + cause = error = new Error(cause); + fatal = true; + } else { + cause = new Exception(cause); + } + } + if (fatal) { + assertError(cause, error); + } else { + assertFalse(maybeError(cause, logger).isPresent()); + } + + assertFalse(maybeError(new Exception(new DecoderException()), logger).isPresent()); + + Throwable chain = outOfMemoryError; + for (int i = 0; i < MAX_ITERATIONS; i++) { + chain = new Exception(chain); + } + assertFalse(maybeError(chain, logger).isPresent()); + } + + private void assertError(final Throwable cause, final Error error) { + final Optional maybeError = maybeError(cause, logger); + assertTrue(maybeError.isPresent()); + assertThat(maybeError.get(), equalTo(error)); + } + +}