This is an automated email from the ASF dual-hosted git repository. pkarwasz pushed a commit to branch fix/3706_disruptor-tccl in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 50a86c710a7e2c6b03eccea74886fd973c692ede Author: Piotr P. Karwasz <[email protected]> AuthorDate: Wed Jun 25 09:56:41 2025 +0200 fix(test): fail `DisruptorTest` on async thread exceptions Ensure that DisruptorTest explicitly fails when an exception occurs on an asynchronous thread. This improves error detection and prevents silent test passes in the presence of async failures. --- log4j-osgi-test/pom.xml | 2 + .../logging/log4j/osgi/tests/DisruptorTest.java | 100 +++++++++++++++++---- 2 files changed, 87 insertions(+), 15 deletions(-) diff --git a/log4j-osgi-test/pom.xml b/log4j-osgi-test/pom.xml index b63666715e..9517f3385c 100644 --- a/log4j-osgi-test/pom.xml +++ b/log4j-osgi-test/pom.xml @@ -257,6 +257,7 @@ <include>org.apache.logging.log4j.osgi.tests.DisruptorTest</include> </includes> <systemPropertyVariables> + <log4j2.asyncLoggerExceptionHandler>org.apache.logging.log4j.osgi.tests.DisruptorTest$TestExceptionHandler</log4j2.asyncLoggerExceptionHandler> <log4j2.contextSelector>org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector</log4j2.contextSelector> </systemPropertyVariables> </configuration> @@ -291,6 +292,7 @@ <include>org.apache.logging.log4j.osgi.tests.DisruptorTest</include> </includes> <systemPropertyVariables> + <log4j2.asyncLoggerExceptionHandler>org.apache.logging.log4j.osgi.tests.DisruptorTest$TestExceptionHandler</log4j2.asyncLoggerExceptionHandler> <log4j2.contextSelector>org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector</log4j2.contextSelector> </systemPropertyVariables> </configuration> diff --git a/log4j-osgi-test/src/test/java/org/apache/logging/log4j/osgi/tests/DisruptorTest.java b/log4j-osgi-test/src/test/java/org/apache/logging/log4j/osgi/tests/DisruptorTest.java index 45ce5fef61..813331210c 100644 --- a/log4j-osgi-test/src/test/java/org/apache/logging/log4j/osgi/tests/DisruptorTest.java +++ b/log4j-osgi-test/src/test/java/org/apache/logging/log4j/osgi/tests/DisruptorTest.java @@ -17,19 +17,26 @@ package org.apache.logging.log4j.osgi.tests; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.ops4j.pax.exam.CoreOptions.junitBundles; import static org.ops4j.pax.exam.CoreOptions.linkBundle; import static org.ops4j.pax.exam.CoreOptions.options; +import com.lmax.disruptor.ExceptionHandler; +import java.io.IOException; +import java.net.URL; +import java.util.concurrent.atomic.AtomicReference; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.async.AsyncLoggerContext; +import org.apache.logging.log4j.core.async.RingBufferLogEvent; import org.junit.Test; import org.junit.runner.RunWith; +import org.junitpioneer.jupiter.Issue; import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.junit.PaxExam; import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy; @@ -39,6 +46,8 @@ import org.ops4j.pax.exam.spi.reactors.PerClass; @ExamReactorStrategy(PerClass.class) public class DisruptorTest { + private static final int MESSAGE_COUNT = 128; + @org.ops4j.pax.exam.Configuration public Option[] config() { return options( @@ -59,21 +68,58 @@ public class DisruptorTest { } @Test - public void testDisruptorLog() { - // Logger context - LoggerContext context = getLoggerContext(); - assertTrue("LoggerContext is an instance of AsyncLoggerContext", context instanceof AsyncLoggerContext); - final CustomConfiguration custom = (CustomConfiguration) context.getConfiguration(); - // Logging - final Logger logger = LogManager.getLogger(getClass()); - logger.info("Hello OSGI from Log4j2!"); - - context.stop(); - assertEquals(1, custom.getEvents().size()); - final LogEvent event = custom.getEvents().get(0); - assertEquals("Hello OSGI from Log4j2!", event.getMessage().getFormattedMessage()); - assertEquals(Level.INFO, event.getLevel()); - custom.clearEvents(); + @Issue("https://github.com/apache/logging-log4j2/issues/3706") + public void testDisruptorLog() throws IOException { + ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader classLoader = createClassLoader(); + try { + // Set the context classloader to an empty classloader, so attempts to use the TCCL will not find any + // classes. + Thread.currentThread().setContextClassLoader(classLoader); + // Logger context + LoggerContext context = getLoggerContext(); + assertTrue("LoggerContext is an instance of AsyncLoggerContext", context instanceof AsyncLoggerContext); + final CustomConfiguration custom = (CustomConfiguration) context.getConfiguration(); + // Logging + final Logger logger = LogManager.getLogger(getClass()); + for (int i = 0; i < MESSAGE_COUNT; i++) { + logger.info("Hello OSGI from Log4j2! {}", i); + } + + context.stop(); + assertEquals(MESSAGE_COUNT, custom.getEvents().size()); + for (int i = 0; i < MESSAGE_COUNT; i++) { + final LogEvent event = custom.getEvents().get(i); + assertEquals( + "Message nr " + i, + "Hello OSGI from Log4j2! " + i, + event.getMessage().getFormattedMessage()); + assertEquals(Level.INFO, event.getLevel()); + } + custom.clearEvents(); + assertNull("Asynchronous exception", TestExceptionHandler.exception.get()); + } finally { + Thread.currentThread().setContextClassLoader(threadContextClassLoader); + } + } + + private static ClassLoader createClassLoader() { + // We want a classloader capable only of loading TestExceptionHandler. + // This is needed to detect exceptions thrown by the asynchronous thread. + return new ClassLoader() { + @Override + public Class<?> loadClass(String name) throws ClassNotFoundException { + if (name.equals(TestExceptionHandler.class.getName())) { + return TestExceptionHandler.class; + } + throw new ClassNotFoundException(name); + } + + @Override + public URL getResource(String name) { + return null; // No resources available. + } + }; } private static LoggerContext getLoggerContext() { @@ -81,4 +127,28 @@ public class DisruptorTest { assertEquals("AsyncDefault", ctx.getName()); return ctx; } + + public static class TestExceptionHandler implements ExceptionHandler<RingBufferLogEvent> { + + private static final AtomicReference<Throwable> exception = new AtomicReference<>(); + + @Override + public void handleEventException(Throwable ex, long sequence, RingBufferLogEvent event) { + setException(ex); + } + + @Override + public void handleOnStartException(Throwable ex) { + setException(ex); + } + + @Override + public void handleOnShutdownException(Throwable ex) { + setException(ex); + } + + private static void setException(Throwable ex) { + exception.compareAndSet(null, ex); + } + } }
