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);
+        }
+    }
 }

Reply via email to