diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java
index bdd21bd4ef2..f115e2ae378 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java
@@ -50,8 +50,19 @@ final class DisruptorUtil {
static final boolean ASYNC_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL = PropertiesUtil.getProperties()
.getBooleanProperty("AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull", true);
- static final int DISRUPTOR_MAJOR_VERSION =
- LoaderUtil.isClassAvailable("com.lmax.disruptor.SequenceReportingEventHandler") ? 3 : 4;
+ static final int DISRUPTOR_MAJOR_VERSION = detectDisruptorMajorVersion();
+
+ // TODO: replace with LoaderUtil.isClassAvailable() when TCCL is removed
+ // See: https://github.com/apache/logging-log4j2/issues/3706
+ private static int detectDisruptorMajorVersion() {
+ try {
+ Class.forName(
+ "com.lmax.disruptor.SequenceReportingEventHandler", true, DisruptorUtil.class.getClassLoader());
+ return 3;
+ } catch (final ClassNotFoundException e) {
+ return 4;
+ }
+ }
private DisruptorUtil() {}
diff --git a/log4j-osgi-test/pom.xml b/log4j-osgi-test/pom.xml
index b63666715e0..9517f3385c9 100644
--- a/log4j-osgi-test/pom.xml
+++ b/log4j-osgi-test/pom.xml
@@ -257,6 +257,7 @@
org.apache.logging.log4j.osgi.tests.DisruptorTest
+ org.apache.logging.log4j.osgi.tests.DisruptorTest$TestExceptionHandler
org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector
@@ -291,6 +292,7 @@
org.apache.logging.log4j.osgi.tests.DisruptorTest
+ org.apache.logging.log4j.osgi.tests.DisruptorTest$TestExceptionHandler
org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector
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 45ce5fef61c..813331210c5 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 @@
@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 Option[] config() {
}
@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 @@ private static LoggerContext getLoggerContext() {
assertEquals("AsyncDefault", ctx.getName());
return ctx;
}
+
+ public static class TestExceptionHandler implements ExceptionHandler {
+
+ private static final AtomicReference 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);
+ }
+ }
}
diff --git a/src/changelog/.2.x.x/3706_disruptor-tccl.xml b/src/changelog/.2.x.x/3706_disruptor-tccl.xml
new file mode 100644
index 00000000000..a6a446fb14f
--- /dev/null
+++ b/src/changelog/.2.x.x/3706_disruptor-tccl.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ Fix detection of the Disruptor major version in some environments.
+
+