diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java index 4c00527a932..5d555ece438 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java @@ -32,8 +32,12 @@ import com.carrotsearch.randomizedtesting.rules.TestRuleAdapter; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.status.StatusConsoleListener; import org.apache.logging.log4j.status.StatusData; import org.apache.logging.log4j.status.StatusLogger; @@ -183,6 +187,8 @@ public abstract class ESTestCase extends LuceneTestCase { private static final AtomicInteger portGenerator = new AtomicInteger(); + private static final Collection nettyLoggedLeaks = new ArrayList<>(); + @AfterClass public static void resetPortCounter() { portGenerator.set(0); @@ -192,8 +198,28 @@ public abstract class ESTestCase extends LuceneTestCase { System.setProperty("log4j.shutdownHookEnabled", "false"); System.setProperty("log4j2.disable.jmx", "true"); + // Enable Netty leak detection and monitor logger for logged leak errors + System.setProperty("io.netty.leakDetection.level", "advanced"); + String leakLoggerName = "io.netty.util.ResourceLeakDetector"; + Logger leakLogger = LogManager.getLogger(leakLoggerName); + Appender leakAppender = new AbstractAppender(leakLoggerName, null, + PatternLayout.newBuilder().withPattern("%m").build()) { + @Override + public void append(LogEvent event) { + String message = event.getMessage().getFormattedMessage(); + if (Level.ERROR.equals(event.getLevel()) && message.contains("LEAK:")) { + synchronized (nettyLoggedLeaks) { + nettyLoggedLeaks.add(message); + } + } + } + }; + leakAppender.start(); + Loggers.addAppender(leakLogger, leakAppender); + // shutdown hook so that when the test JVM exits, logging is shutdown too Runtime.getRuntime().addShutdownHook(new Thread(() -> { + leakAppender.stop(); LoggerContext context = (LoggerContext) LogManager.getContext(false); Configurator.shutdown(context); })); @@ -440,6 +466,13 @@ public abstract class ESTestCase extends LuceneTestCase { statusData.clear(); } } + synchronized (nettyLoggedLeaks) { + try { + assertThat(nettyLoggedLeaks, empty()); + } finally { + nettyLoggedLeaks.clear(); + } + } } // this must be a separate method from other ensure checks above so suite scoped integ tests can call...TODO: fix that