Skip to content

Commit 2edabdb

Browse files
TESTS: Check for Netty resource leaks (elastic#31861)
* Enabled advanced leak detection when loading `EsTestCase` * Added custom `Appender` to collect leak logs and check for logged errors in a way similar to what is done for the `StatusLogger` * Fixes elastic#20398
1 parent a2c0107 commit 2edabdb

File tree

1 file changed

+33
-0
lines changed

1 file changed

+33
-0
lines changed

test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@
3232
import org.apache.logging.log4j.Level;
3333
import org.apache.logging.log4j.LogManager;
3434
import org.apache.logging.log4j.Logger;
35+
import org.apache.logging.log4j.core.Appender;
36+
import org.apache.logging.log4j.core.LogEvent;
3537
import org.apache.logging.log4j.core.LoggerContext;
38+
import org.apache.logging.log4j.core.appender.AbstractAppender;
3639
import org.apache.logging.log4j.core.config.Configurator;
40+
import org.apache.logging.log4j.core.layout.PatternLayout;
3741
import org.apache.logging.log4j.status.StatusConsoleListener;
3842
import org.apache.logging.log4j.status.StatusData;
3943
import org.apache.logging.log4j.status.StatusLogger;
@@ -181,6 +185,8 @@ public abstract class ESTestCase extends LuceneTestCase {
181185

182186
private static final AtomicInteger portGenerator = new AtomicInteger();
183187

188+
private static final Collection<String> nettyLoggedLeaks = new ArrayList<>();
189+
184190
@AfterClass
185191
public static void resetPortCounter() {
186192
portGenerator.set(0);
@@ -190,8 +196,28 @@ public static void resetPortCounter() {
190196
System.setProperty("log4j.shutdownHookEnabled", "false");
191197
System.setProperty("log4j2.disable.jmx", "true");
192198

199+
// Enable Netty leak detection and monitor logger for logged leak errors
200+
System.setProperty("io.netty.leakDetection.level", "advanced");
201+
String leakLoggerName = "io.netty.util.ResourceLeakDetector";
202+
Logger leakLogger = LogManager.getLogger(leakLoggerName);
203+
Appender leakAppender = new AbstractAppender(leakLoggerName, null,
204+
PatternLayout.newBuilder().withPattern("%m").build()) {
205+
@Override
206+
public void append(LogEvent event) {
207+
String message = event.getMessage().getFormattedMessage();
208+
if (Level.ERROR.equals(event.getLevel()) && message.contains("LEAK:")) {
209+
synchronized (nettyLoggedLeaks) {
210+
nettyLoggedLeaks.add(message);
211+
}
212+
}
213+
}
214+
};
215+
leakAppender.start();
216+
Loggers.addAppender(leakLogger, leakAppender);
217+
193218
// shutdown hook so that when the test JVM exits, logging is shutdown too
194219
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
220+
leakAppender.stop();
195221
LoggerContext context = (LoggerContext) LogManager.getContext(false);
196222
Configurator.shutdown(context);
197223
}));
@@ -438,6 +464,13 @@ protected static void checkStaticState(boolean afterClass) throws Exception {
438464
statusData.clear();
439465
}
440466
}
467+
synchronized (nettyLoggedLeaks) {
468+
try {
469+
assertThat(nettyLoggedLeaks, empty());
470+
} finally {
471+
nettyLoggedLeaks.clear();
472+
}
473+
}
441474
}
442475

443476
// this must be a separate method from other ensure checks above so suite scoped integ tests can call...TODO: fix that

0 commit comments

Comments
 (0)