From f25c5e430f631a3082fcbf38366066fa34f2060c Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Mon, 1 Apr 2024 23:54:22 +0200 Subject: [PATCH] Move ConversantMedia disruptor support to new module (#2914) Moves `DisruptorBlockingQueue` to a separate module. Includes peer review (cf. #2914). --- .../.log4j-plugin-processing-activator | 1 + log4j-conversant/pom.xml | 90 ++++++++++++ .../DisruptorBlockingQueueFactory.java | 9 +- .../DisruptorRecyclerFactoryProvider.java | 130 ++++++++++++++++++ .../DisruptorRecyclerProperties.java | 25 ++++ .../log4j/conversant/package-info.java | 22 +++ .../DisruptorBlockingQueueFactoryTest.java | 92 +++++++++++++ .../DisruptorRecyclerFactoryProviderTest.java | 44 ++++++ .../DisruptorBlockingQueueFactoryTest.xml | 37 +++++ log4j-core-test/pom.xml | 7 +- log4j-core/pom.xml | 33 ++--- .../.log4j-plugin-processing-activator | 1 + .../jctools/JCToolsBlockingQueueFactory.java | 2 +- .../JCToolsBlockingQueueFactoryTest.java | 25 +++- .../JCToolsBlockingQueueFactoryTest.xml | 24 ++-- ...log4j.kit.recycler.RecyclerFactoryProvider | 6 - log4j-kit/pom.xml | 1 + .../kit/recycler/RecyclerProperties.java | 13 +- .../kit/recycler/internal/CapacityUtil.java | 33 +++++ .../internal/RecyclerFactoryRegistryTest.java | 4 +- log4j-parent/pom.xml | 7 - log4j-perf-test/pom.xml | 12 +- pom.xml | 7 + .../.3.x.x/2914_move_conversant_queue.xml | 8 ++ .../antora/modules/ROOT/pages/components.adoc | 17 +++ .../pages/manual/appenders/delegating.adoc | 25 +--- .../ROOT/pages/manual/garbagefree.adoc | 52 ++++--- .../modules/ROOT/pages/manual/migration.adoc | 5 +- .../modules/ROOT/pages/plugin-reference.adoc | 2 +- .../partials/components/log4j-conversant.adoc | 41 ++++++ .../partials/features/queue-conversant.adoc | 21 +++ .../properties-async-logger.adoc | 4 +- .../properties-garbage-collection.adoc | 23 +++- 33 files changed, 702 insertions(+), 121 deletions(-) create mode 100644 log4j-conversant/.log4j-plugin-processing-activator create mode 100644 log4j-conversant/pom.xml rename {log4j-core/src/main/java/org/apache/logging/log4j/core/async => log4j-conversant/src/main/java/org/apache/logging/log4j/conversant}/DisruptorBlockingQueueFactory.java (84%) create mode 100644 log4j-conversant/src/main/java/org/apache/logging/log4j/conversant/DisruptorRecyclerFactoryProvider.java create mode 100644 log4j-conversant/src/main/java/org/apache/logging/log4j/conversant/DisruptorRecyclerProperties.java create mode 100644 log4j-conversant/src/main/java/org/apache/logging/log4j/conversant/package-info.java create mode 100644 log4j-conversant/src/test/java/org/apache/logging/log4j/conversant/test/DisruptorBlockingQueueFactoryTest.java create mode 100644 log4j-conversant/src/test/java/org/apache/logging/log4j/conversant/test/DisruptorRecyclerFactoryProviderTest.java create mode 100644 log4j-conversant/src/test/resources/DisruptorBlockingQueueFactoryTest.xml create mode 100644 log4j-jctools/.log4j-plugin-processing-activator delete mode 100644 log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider create mode 100644 log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/CapacityUtil.java create mode 100644 src/changelog/.3.x.x/2914_move_conversant_queue.xml create mode 100644 src/site/antora/modules/ROOT/partials/components/log4j-conversant.adoc create mode 100644 src/site/antora/modules/ROOT/partials/features/queue-conversant.adoc diff --git a/log4j-conversant/.log4j-plugin-processing-activator b/log4j-conversant/.log4j-plugin-processing-activator new file mode 100644 index 00000000000..ba133f36961 --- /dev/null +++ b/log4j-conversant/.log4j-plugin-processing-activator @@ -0,0 +1 @@ +This file is here to activate the `plugin-processing` Maven profile. diff --git a/log4j-conversant/pom.xml b/log4j-conversant/pom.xml new file mode 100644 index 00000000000..52dd59f118c --- /dev/null +++ b/log4j-conversant/pom.xml @@ -0,0 +1,90 @@ + + + + 4.0.0 + + org.apache.logging.log4j + log4j + ${revision} + ../log4j-parent + + + log4j-conversant + Apache Log4j Conversant Disruptor-based supplements + Provides ConversantMedia Disruptor-based data structure implementations for the Apache Log4j. + + + 1.2.21 + + + + + + + com.conversantmedia + disruptor + ${conversant.disruptor.version} + + + + + + + + + org.jspecify + jspecify + provided + + + + com.conversantmedia + disruptor + + + + org.apache.logging.log4j + log4j-core + + + + org.apache.logging.log4j + log4j-plugins + + + + org.assertj + assertj-core + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.apache.logging.log4j + log4j-core-test + test + + + + + diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java b/log4j-conversant/src/main/java/org/apache/logging/log4j/conversant/DisruptorBlockingQueueFactory.java similarity index 84% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java rename to log4j-conversant/src/main/java/org/apache/logging/log4j/conversant/DisruptorBlockingQueueFactory.java index 4bd278b9da3..dd8ce2a84c5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java +++ b/log4j-conversant/src/main/java/org/apache/logging/log4j/conversant/DisruptorBlockingQueueFactory.java @@ -14,24 +14,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.core.async; +package org.apache.logging.log4j.conversant; import com.conversantmedia.util.concurrent.DisruptorBlockingQueue; import com.conversantmedia.util.concurrent.SpinPolicy; import java.util.concurrent.BlockingQueue; +import org.apache.logging.log4j.core.async.BlockingQueueFactory; import org.apache.logging.log4j.plugins.Configurable; import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.plugins.PluginAttribute; import org.apache.logging.log4j.plugins.PluginFactory; /** - * Factory for creating instances of {@link DisruptorBlockingQueue}. + * A {@link BlockingQueueFactory} based on Conversant Disruptor BlockingQueue. * - * @since 2.7 + * @since 3.0.0 */ @Configurable(elementType = BlockingQueueFactory.ELEMENT_TYPE, printObject = true) @Plugin("DisruptorBlockingQueue") -public class DisruptorBlockingQueueFactory implements BlockingQueueFactory { +public final class DisruptorBlockingQueueFactory implements BlockingQueueFactory { private final SpinPolicy spinPolicy; diff --git a/log4j-conversant/src/main/java/org/apache/logging/log4j/conversant/DisruptorRecyclerFactoryProvider.java b/log4j-conversant/src/main/java/org/apache/logging/log4j/conversant/DisruptorRecyclerFactoryProvider.java new file mode 100644 index 00000000000..2d10eb425bd --- /dev/null +++ b/log4j-conversant/src/main/java/org/apache/logging/log4j/conversant/DisruptorRecyclerFactoryProvider.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.conversant; + +import static java.util.Objects.requireNonNull; + +import aQute.bnd.annotation.spi.ServiceProvider; +import com.conversantmedia.util.concurrent.DisruptorBlockingQueue; +import com.conversantmedia.util.concurrent.SpinPolicy; +import java.util.Queue; +import java.util.function.Consumer; +import java.util.function.Supplier; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.util.Integers; +import org.apache.logging.log4j.kit.env.PropertyEnvironment; +import org.apache.logging.log4j.kit.recycler.Recycler; +import org.apache.logging.log4j.kit.recycler.RecyclerFactory; +import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider; +import org.apache.logging.log4j.kit.recycler.RecyclerProperties; +import org.apache.logging.log4j.kit.recycler.support.AbstractRecycler; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * A {@link Recycler} factory provider implementation based on + * Recycler create(final Supplier supplier, final Consumer cleaner) { + requireNonNull(supplier, "supplier"); + requireNonNull(cleaner, "cleaner"); + final DisruptorBlockingQueue queue = new DisruptorBlockingQueue<>(capacity, spinPolicy); + return new DisruptorRecycler<>(supplier, cleaner, queue); + } + + private static Integer validateCapacity(final int capacity) { + if (capacity < MIN_CAPACITY) { + LOGGER.warn( + "Invalid DisruptorBlockingQueue capacity {}, using minimum size {}.", capacity, MIN_CAPACITY); + return MIN_CAPACITY; + } + final int roundedCapacity = Integers.ceilingNextPowerOfTwo(capacity); + if (capacity != roundedCapacity) { + LOGGER.warn( + "Invalid DisruptorBlockingQueue size {}, using rounded size {}.", capacity, roundedCapacity); + } + return roundedCapacity; + } + + private static final class DisruptorRecycler extends AbstractRecycler { + + private final Consumer cleaner; + + private final Queue queue; + + private DisruptorRecycler(final Supplier supplier, final Consumer cleaner, final Queue queue) { + super(supplier); + this.cleaner = cleaner; + this.queue = queue; + } + + @Override + public V acquire() { + final V value = queue.poll(); + return value != null ? value : createInstance(); + } + + @Override + public void release(final V value) { + requireNonNull(value, "value"); + cleaner.accept(value); + queue.offer(value); + } + } + } +} diff --git a/log4j-conversant/src/main/java/org/apache/logging/log4j/conversant/DisruptorRecyclerProperties.java b/log4j-conversant/src/main/java/org/apache/logging/log4j/conversant/DisruptorRecyclerProperties.java new file mode 100644 index 00000000000..412545a1e73 --- /dev/null +++ b/log4j-conversant/src/main/java/org/apache/logging/log4j/conversant/DisruptorRecyclerProperties.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.conversant; + +import com.conversantmedia.util.concurrent.SpinPolicy; +import org.apache.logging.log4j.kit.env.Log4jProperty; +import org.jspecify.annotations.NullMarked; + +@NullMarked +@Log4jProperty(name = "recycler.conversant") +public record DisruptorRecyclerProperties(@Log4jProperty(defaultValue = "WAITING") SpinPolicy spinPolicy) {} diff --git a/log4j-conversant/src/main/java/org/apache/logging/log4j/conversant/package-info.java b/log4j-conversant/src/main/java/org/apache/logging/log4j/conversant/package-info.java new file mode 100644 index 00000000000..50f3f35e2a9 --- /dev/null +++ b/log4j-conversant/src/main/java/org/apache/logging/log4j/conversant/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@Export +@Version("3.0.0") +package org.apache.logging.log4j.conversant; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-conversant/src/test/java/org/apache/logging/log4j/conversant/test/DisruptorBlockingQueueFactoryTest.java b/log4j-conversant/src/test/java/org/apache/logging/log4j/conversant/test/DisruptorBlockingQueueFactoryTest.java new file mode 100644 index 00000000000..5284cd7ff80 --- /dev/null +++ b/log4j-conversant/src/test/java/org/apache/logging/log4j/conversant/test/DisruptorBlockingQueueFactoryTest.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.conversant.test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.conversantmedia.util.concurrent.DisruptorBlockingQueue; +import java.lang.reflect.Field; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.LoggingException; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AsyncAppender; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.spi.ExtendedLogger; +import org.junit.jupiter.api.Test; + +class DisruptorBlockingQueueFactoryTest { + + private static void exceptionTest(final LoggerContext context) throws InterruptedException { + final ExtendedLogger logger = context.getLogger(AsyncAppender.class); + final Exception parent = new IllegalStateException("Test"); + final Throwable child = new LoggingException("This is a test", parent); + logger.error("This is a test", child); + final ListAppender appender = context.getConfiguration().getAppender("LIST"); + final List messages; + try { + messages = appender.getMessages(1, 2, TimeUnit.SECONDS); + } finally { + appender.clear(); + } + assertNotNull(messages); + assertEquals(1, messages.size()); + assertTrue(messages.get(0).contains(parent.getClass().getName())); + } + + private static void rewriteTest(final LoggerContext context) throws InterruptedException { + final ExtendedLogger logger = context.getLogger(AsyncAppender.class); + logger.error("This is a test"); + logger.warn("Hello world!"); + final ListAppender appender = context.getConfiguration().getAppender("LIST"); + final List messages; + try { + messages = appender.getMessages(2, 2, TimeUnit.SECONDS); + } finally { + appender.clear(); + } + assertNotNull(messages); + assertEquals(2, messages.size()); + assertEquals("This is a test", messages.get(0)); + assertEquals("Hello world!", messages.get(1)); + } + + private static void assertConversantDisruptorIsUsed(final LoggerContext context) { + final AsyncAppender appender = context.getConfiguration().getAppender("ASYNC"); + assertThat(appender).isNotNull(); + final BlockingQueue queue = (BlockingQueue) assertDoesNotThrow(() -> { + Field queueField = AsyncAppender.class.getDeclaredField("queue"); + queueField.setAccessible(true); + return queueField.get(appender); + }); + assertThat(queue).isInstanceOf(DisruptorBlockingQueue.class); + } + + @Test + @LoggerContextSource("DisruptorBlockingQueueFactoryTest.xml") + public void testJcToolsBlockingQueue(final LoggerContext context) throws InterruptedException { + assertConversantDisruptorIsUsed(context); + rewriteTest(context); + exceptionTest(context); + } +} diff --git a/log4j-conversant/src/test/java/org/apache/logging/log4j/conversant/test/DisruptorRecyclerFactoryProviderTest.java b/log4j-conversant/src/test/java/org/apache/logging/log4j/conversant/test/DisruptorRecyclerFactoryProviderTest.java new file mode 100644 index 00000000000..3d2f6053474 --- /dev/null +++ b/log4j-conversant/src/test/java/org/apache/logging/log4j/conversant/test/DisruptorRecyclerFactoryProviderTest.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.conversant.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Comparator; +import java.util.List; +import java.util.ServiceLoader; +import org.apache.logging.log4j.conversant.DisruptorRecyclerFactoryProvider; +import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.ServiceLoaderUtil; +import org.junit.jupiter.api.Test; + +class DisruptorRecyclerFactoryProviderTest { + + @Test + void verify_is_the_first() { + final List> providerClasses = ServiceLoaderUtil.safeStream( + RecyclerFactoryProvider.class, + ServiceLoader.load( + RecyclerFactoryProvider.class, getClass().getClassLoader()), + StatusLogger.getLogger()) + .sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder)) + .>map(RecyclerFactoryProvider::getClass) + .toList(); + assertThat(providerClasses).startsWith(DisruptorRecyclerFactoryProvider.class); + } +} diff --git a/log4j-conversant/src/test/resources/DisruptorBlockingQueueFactoryTest.xml b/log4j-conversant/src/test/resources/DisruptorBlockingQueueFactoryTest.xml new file mode 100644 index 00000000000..b1f0ca990aa --- /dev/null +++ b/log4j-conversant/src/test/resources/DisruptorBlockingQueueFactoryTest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/pom.xml b/log4j-core-test/pom.xml index ec83eae4178..e492bff9177 100644 --- a/log4j-core-test/pom.xml +++ b/log4j-core-test/pom.xml @@ -108,12 +108,7 @@ commons-compress true - - - com.conversantmedia - disruptor - true - + com.lmax diff --git a/log4j-core/pom.xml b/log4j-core/pom.xml index c007ffc6c36..ecc230b326f 100644 --- a/log4j-core/pom.xml +++ b/log4j-core/pom.xml @@ -44,11 +44,8 @@ org.jspecify.*;resolution:=optional, - com.conversantmedia.util.concurrent;resolution:=optional; - com.lmax.disruptor.*;resolution:=optional, org.apache.commons.compress.*;resolution:=optional, org.fusesource.jansi;resolution:=optional, - org.jspecify.annotations.*;resolution:=optional, java.lang.management;resolution:=optional, @@ -64,8 +61,6 @@ - com.conversantmedia.disruptor;transitive=false, - com.lmax.disruptor;transitive=false, java.management;transitive=false, java.sql;transitive=false, java.xml;transitive=false, @@ -77,6 +72,7 @@ + org.jspecify jspecify @@ -87,44 +83,45 @@ org.osgi.framework provided + org.apache.logging.log4j log4j-api + org.apache.logging.log4j log4j-kit + org.apache.logging.log4j log4j-plugins - - - org.apache.logging.log4j - log4j-plugin-processor - test - + org.apache.commons commons-compress true - - - com.conversantmedia - disruptor - true - + org.fusesource.jansi jansi true + + + + org.apache.logging.log4j + log4j-plugin-processor + test + + diff --git a/log4j-jctools/.log4j-plugin-processing-activator b/log4j-jctools/.log4j-plugin-processing-activator new file mode 100644 index 00000000000..ba133f36961 --- /dev/null +++ b/log4j-jctools/.log4j-plugin-processing-activator @@ -0,0 +1 @@ +This file is here to activate the `plugin-processing` Maven profile. diff --git a/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsBlockingQueueFactory.java b/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsBlockingQueueFactory.java index d35801cdd6a..a61e87c8855 100644 --- a/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsBlockingQueueFactory.java +++ b/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsBlockingQueueFactory.java @@ -56,7 +56,7 @@ public static JCToolsBlockingQueueFactory createFactory( /** * BlockingQueue wrapper for JCTools multiple producer single consumer array queue. */ - private static final class MpscBlockingQueue extends MpscArrayQueue implements BlockingQueue { + static final class MpscBlockingQueue extends MpscArrayQueue implements BlockingQueue { private final JCToolsBlockingQueueFactory.WaitStrategy waitStrategy; diff --git a/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsBlockingQueueFactoryTest.java b/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsBlockingQueueFactoryTest.java index 03e96f05baf..e282ca92018 100644 --- a/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsBlockingQueueFactoryTest.java +++ b/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsBlockingQueueFactoryTest.java @@ -16,11 +16,15 @@ */ package org.apache.logging.log4j.jctools; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.lang.reflect.Field; import java.util.List; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.LoggingException; import org.apache.logging.log4j.core.LoggerContext; @@ -37,7 +41,7 @@ private static void exceptionTest(final LoggerContext context) throws Interrupte final Exception parent = new IllegalStateException("Test"); final Throwable child = new LoggingException("This is a test", parent); logger.error("This is a test", child); - final ListAppender appender = context.getConfiguration().getAppender("List"); + final ListAppender appender = context.getConfiguration().getAppender("LIST"); final List messages; try { messages = appender.getMessages(1, 2, TimeUnit.SECONDS); @@ -53,7 +57,7 @@ private static void rewriteTest(final LoggerContext context) throws InterruptedE final ExtendedLogger logger = context.getLogger(AsyncAppender.class); logger.error("This is a test"); logger.warn("Hello world!"); - final ListAppender appender = context.getConfiguration().getAppender("List"); + final ListAppender appender = context.getConfiguration().getAppender("LIST"); final List messages; try { messages = appender.getMessages(2, 2, TimeUnit.SECONDS); @@ -62,14 +66,25 @@ private static void rewriteTest(final LoggerContext context) throws InterruptedE } assertNotNull(messages); assertEquals(2, messages.size()); - final String messagePrefix = JCToolsBlockingQueueFactoryTest.class.getName() + " rewriteTest "; - assertEquals(messagePrefix + "This is a test", messages.get(0)); - assertEquals(messagePrefix + "Hello world!", messages.get(1)); + assertEquals("This is a test", messages.get(0)); + assertEquals("Hello world!", messages.get(1)); + } + + private static void assertJCToolsIsUsed(final LoggerContext context) { + final AsyncAppender appender = context.getConfiguration().getAppender("ASYNC"); + assertThat(appender).isNotNull(); + final BlockingQueue queue = (BlockingQueue) assertDoesNotThrow(() -> { + Field queueField = AsyncAppender.class.getDeclaredField("queue"); + queueField.setAccessible(true); + return queueField.get(appender); + }); + assertThat(queue).isInstanceOf(JCToolsBlockingQueueFactory.MpscBlockingQueue.class); } @Test @LoggerContextSource("JCToolsBlockingQueueFactoryTest.xml") public void testJcToolsBlockingQueue(final LoggerContext context) throws InterruptedException { + assertJCToolsIsUsed(context); rewriteTest(context); exceptionTest(context); } diff --git a/log4j-jctools/src/test/resources/JCToolsBlockingQueueFactoryTest.xml b/log4j-jctools/src/test/resources/JCToolsBlockingQueueFactoryTest.xml index 1b6ff95acb0..46ccb8e60f2 100644 --- a/log4j-jctools/src/test/resources/JCToolsBlockingQueueFactoryTest.xml +++ b/log4j-jctools/src/test/resources/JCToolsBlockingQueueFactoryTest.xml @@ -15,25 +15,23 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - + - - - - - + + - - + + - - - + + - diff --git a/log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider b/log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider deleted file mode 100644 index 25c8b921494..00000000000 --- a/log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider +++ /dev/null @@ -1,6 +0,0 @@ -# `META-INF/services/*` files are automatically generated by `bnd-maven-plugin`. -# We use `bnd:jar` goal, which only places them into the JAR. -# As a result, tests of the same artifact (running against `target/` contents, not the JAR) don't have them. -# To mitigate this, we manually create this file here only for tests. -# `logging-parent` version `10.5.0` will switch from `bnd:jar` to `bnd:bnd-process`, then this hack won't be needed. -org.apache.logging.log4j.jctools.JCToolsRecyclerFactoryProvider diff --git a/log4j-kit/pom.xml b/log4j-kit/pom.xml index c888f21c7a7..7833f16922e 100644 --- a/log4j-kit/pom.xml +++ b/log4j-kit/pom.xml @@ -35,6 +35,7 @@ org.jspecify.annotations.*;resolution:=optional + org.jspecify;transitive:=false diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerProperties.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerProperties.java index 71dd1b2f190..736fd337965 100644 --- a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerProperties.java +++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerProperties.java @@ -16,6 +16,8 @@ */ package org.apache.logging.log4j.kit.recycler; +import static org.apache.logging.log4j.kit.recycler.internal.CapacityUtil.DEFAULT_CAPACITY; + import org.apache.logging.log4j.kit.env.Log4jProperty; import org.apache.logging.log4j.status.StatusLogger; import org.jspecify.annotations.NullMarked; @@ -30,11 +32,6 @@ @NullMarked @Log4jProperty(name = "recycler") public record RecyclerProperties(@Nullable String factory, @Nullable Integer capacity) { - /** - * The default recycler capacity: {@code max(2C+1, 8)}, {@code C} denoting the number of available processors - */ - private static final int DEFAULT_CAPACITY = - Math.max(2 * Runtime.getRuntime().availableProcessors() + 1, 8); public RecyclerProperties { capacity = validateCapacity(capacity); @@ -50,4 +47,10 @@ private static Integer validateCapacity(final @Nullable Integer capacity) { } return DEFAULT_CAPACITY; } + + @Override + public Integer capacity() { + assert capacity != null; + return capacity; + } } diff --git a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/CapacityUtil.java b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/CapacityUtil.java new file mode 100644 index 00000000000..ee3c68e8c73 --- /dev/null +++ b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/CapacityUtil.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.kit.recycler.internal; + +public class CapacityUtil { + + private static final int BITS_PER_INT = 32; + + private static int ceilingNextPowerOfTwo(final int x) { + return 1 << (BITS_PER_INT - Integer.numberOfLeadingZeros(x - 1)); + } + + /** + * The default recycler capacity: {@code max(2C, 8)}, {@code C} denoting the number of available processors, + * rounded to the next power of 2. + */ + public static final int DEFAULT_CAPACITY = + Math.max(ceilingNextPowerOfTwo(2 * Runtime.getRuntime().availableProcessors()), 8); +} diff --git a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryRegistryTest.java b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryRegistryTest.java index cdc857e734e..a71228b6812 100644 --- a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryRegistryTest.java +++ b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryRegistryTest.java @@ -16,6 +16,7 @@ */ package org.apache.logging.log4j.kit.recycler.internal; +import static org.apache.logging.log4j.kit.recycler.internal.CapacityUtil.DEFAULT_CAPACITY; import static org.assertj.core.api.Assertions.assertThat; import org.apache.logging.log4j.kit.recycler.RecyclerFactory; @@ -28,9 +29,6 @@ public class RecyclerFactoryRegistryTest { - private static final int DEFAULT_CAPACITY = - Math.max(2 * Runtime.getRuntime().availableProcessors() + 1, 8); - @Test void DummyRecyclerFactory_should_work() { final RecyclerFactory factory = RecyclerFactoryTestUtil.createForEnvironment("dummy", null); diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index ca79e7308de..7a6c68166b5 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -103,7 +103,6 @@ 2.16.1 3.17.0 1.3.4 - 1.2.21 4.0.0 7.17.23 0.9.0 @@ -342,12 +341,6 @@ ${flapdoodle-reverse.version} - - com.conversantmedia - disruptor - ${conversant.disruptor.version} - - com.lmax disruptor diff --git a/log4j-perf-test/pom.xml b/log4j-perf-test/pom.xml index 1be3e92ca34..fe3e6f83319 100644 --- a/log4j-perf-test/pom.xml +++ b/log4j-perf-test/pom.xml @@ -53,6 +53,10 @@ org.apache.logging.log4j log4j-async-logger + + org.apache.logging.log4j + log4j-conversant + org.apache.logging.log4j log4j-core @@ -77,14 +81,6 @@ org.apache.logging.log4j log4j-plugins - - com.conversantmedia - disruptor - - - com.lmax - disruptor - com.h2database h2 diff --git a/pom.xml b/pom.xml index a4b354f9b2c..8015d115758 100644 --- a/pom.xml +++ b/pom.xml @@ -236,6 +236,7 @@ log4j-config-jackson log4j-config-properties log4j-config-yaml + log4j-conversant log4j-core log4j-core-test log4j-csv @@ -407,6 +408,12 @@ ${project.version} + + org.apache.logging.log4j + log4j-conversant + ${project.version} + + org.apache.logging.log4j log4j-core diff --git a/src/changelog/.3.x.x/2914_move_conversant_queue.xml b/src/changelog/.3.x.x/2914_move_conversant_queue.xml new file mode 100644 index 00000000000..adac79f607c --- /dev/null +++ b/src/changelog/.3.x.x/2914_move_conversant_queue.xml @@ -0,0 +1,8 @@ + + + + Move `DisruptorBlockingQueue` plugin to new `log4j-conversant` module. + diff --git a/src/site/antora/modules/ROOT/pages/components.adoc b/src/site/antora/modules/ROOT/pages/components.adoc index 830ecc40b5a..97c1f8ccbce 100644 --- a/src/site/antora/modules/ROOT/pages/components.adoc +++ b/src/site/antora/modules/ROOT/pages/components.adoc @@ -103,6 +103,23 @@ See xref:manual/configuration.adoc#configuration-factories[predefined `Configura include::partial$components/log4j-config-yaml.adoc[] +[#log4j-conversant] +== `log4j-conversant` + +|=== +| JPMS module +| `org.apache.logging.log4j.conversant` +|=== + +The `log4j-conversant` artifact contains a +xref:manual/appenders/delegating.adoc#BlockingQueueFactory[blocking queue factory] +that uses +https://github.com/conversant/disruptor[Conversant Disruptor BlockingQueue]. + +See xref:manual/appenders/delegating.adoc#DisruptorBlockingQueueFactory[Conversant Disruptor Blocking Queue] for more details. + +include::partial$components/log4j-conversant.adoc[] + [#log4j-core] == `log4j-core` diff --git a/src/site/antora/modules/ROOT/pages/manual/appenders/delegating.adoc b/src/site/antora/modules/ROOT/pages/manual/appenders/delegating.adoc index a717e5c6ce7..d2375bc628b 100644 --- a/src/site/antora/modules/ROOT/pages/manual/appenders/delegating.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/appenders/delegating.adoc @@ -265,31 +265,10 @@ The `SpinPolicy` to apply, when adding elements to the queue. .Additional dependencies are required to use `DisruptorBlockingQueue` [%collapsible] ===== -[tabs] -==== -Maven:: -+ -[source,xml,subs="+attributes"] ----- - - com.conversantmedia - disruptor - {conversant-version} - runtime - ----- - -Gradle:: -+ -[source,groovy,subs="+attributes"] ----- -runtimeOnly 'com.conversantmedia:disruptor:{conversant-version}' ----- - -==== +include::partial$features/queue-conversant.adoc[] ===== + -xref:plugin-reference.adoc#org-apache-logging-log4j_log4j-core_org-apache-logging-log4j-core-async-DisruptorBlockingQueueFactory[{plugin-reference-marker} Plugin reference for `DisruptorBlockingQueue`] +xref:plugin-reference.adoc#org-apache-logging-log4j_log4j-conversant_org-apache-logging-log4j-conversant-DisruptorBlockingQueueFactory[{plugin-reference-marker} Plugin reference for `DisruptorBlockingQueue`] [#JCToolsBlockingQueueFactory] `JCToolsBlockingQueue`:: diff --git a/src/site/antora/modules/ROOT/pages/manual/garbagefree.adoc b/src/site/antora/modules/ROOT/pages/manual/garbagefree.adoc index 8ec816c3d1d..2a513be60a4 100644 --- a/src/site/antora/modules/ROOT/pages/manual/garbagefree.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/garbagefree.adoc @@ -129,8 +129,7 @@ In case there is a problem, the <> is provided, the first provider whose name matching with this property value will be used. Otherwise, the first provider will be used. +. If <<#log4j.recycler.factory>> is provided, the first provider whose name matching with this property value will be used. +Otherwise, the first provider will be used. The predefined recycler factory providers are as follows: @@ -182,7 +182,7 @@ Intended as a test utility.) [cols="1h,5"] |=== | Module -| `log4j-kit` +| xref:components.adoc#log4j-kit[`log4j-kit`] | Name | `threadLocal` @@ -203,7 +203,7 @@ Note that it is effectively disabled for servlet environments. [cols="1h,5"] |=== | Module -| `log4j-kit` +| xref:components.adoc#log4j-kit[`log4j-kit`] | Name | `queue` @@ -221,7 +221,7 @@ If pool runs out of capacity (see <>), it will start cr [cols="1h,5"] |=== | Module -| `log4j-jctools` +| xref:components.adoc#log4j-jctools[`log4j-jctools`] | Name | `jctools-mpmc` @@ -233,6 +233,29 @@ If pool runs out of capacity (see <>), it will start cr Pools objects in a fixed-size JCTools MPMC queue. If pool runs out of capacity (see <>), it will start creating fresh instances for new requests. +[#recyclers-conversant] +==== Conversant Disruptor recycler + +[cols="1h,5"] +|=== +| Module +| xref:components.adoc#log4j-conversant[`log4j-conversant`] + +| Name +| `conversant-disruptor` + +| Order +| 600 +|=== + +Pools objects in a fixed-size ring buffer. +If pool runs out of capacity (see <>), a spin policy is applied (see <>). + +[NOTE] +==== +The capacity of this recycler **must** be at least 8 and a power of 2. +==== + [#core-limitations] === Limitations @@ -275,17 +298,12 @@ or [#codeImpact] === Avoiding autoboxing -We made an effort to make logging garbage-free without requiring code -changes in existing applications, but there is one area where this was -not possible. When logging primitive values (i.e. int, double, boolean, -etc.) the JVM autoboxes these primitive values to their Object wrapper -equivalents, creating garbage. - -Log4j provides an `Unbox` utility to prevent autoboxing of primitive -parameters. This utility contains a thread-local pool of reused -`StringBuilder`s. The `Unbox.box(primitive)` methods write directly into -a StringBuilder, and the resulting text will be copied into the final -log message text without creating temporary objects. +We made an effort to make logging garbage-free without requiring code changes in existing applications, but there is one area where this was not possible. +When logging primitive values (i.e. int, double, boolean, etc.) the JVM autoboxes these primitive values to their Object wrapper equivalents, creating garbage. + +Log4j provides an `Unbox` utility to prevent autoboxing of primitive parameters. +This utility contains a thread-local pool of reused +`StringBuilder`s. The `Unbox.box(primitive)` methods write directly into a StringBuilder, and the resulting text will be copied into the final log message text without creating temporary objects. [source,java] ---- diff --git a/src/site/antora/modules/ROOT/pages/manual/migration.adoc b/src/site/antora/modules/ROOT/pages/manual/migration.adoc index 5534f6d374e..6ccfa46d181 100644 --- a/src/site/antora/modules/ROOT/pages/manual/migration.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/migration.adoc @@ -53,7 +53,10 @@ The following `log4j-core` features were moved to their own modules or were remo |=== | Log4j 2 feature | Log4j 3 module -| xref:manual/appenders/delegating.adoc#BlockingQueueFactory[Asynchronous appender: JCTools-based queue] +| xref:manual/appenders/delegating.adoc#DisruptorBlockingQueueFactory[Asynchronous appender: Conversant Disruptor-based queue] +| xref:components.adoc#log4j-conversant[`log4j-conversant`] + +| xref:manual/appenders/delegating.adoc#JCToolsBlockingQueueFactory[Asynchronous appender: JCTools-based queue] | xref:components.adoc#log4j-jctools[`log4j-jctools`] | xref:manual/async.adoc[Asynchronous logger] diff --git a/src/site/antora/modules/ROOT/pages/plugin-reference.adoc b/src/site/antora/modules/ROOT/pages/plugin-reference.adoc index a002ed8c15d..fb5875b3e0e 100644 --- a/src/site/antora/modules/ROOT/pages/plugin-reference.adoc +++ b/src/site/antora/modules/ROOT/pages/plugin-reference.adoc @@ -269,7 +269,7 @@ The purpose of the sections below is to remove IDE errors while editing configur == AsyncWaitStrategyFactory [#org-apache-logging-log4j_log4j-core_org-apache-logging-log4j-core-async-BlockingQueueFactory] == `BlockingQueueFactory` -[#org-apache-logging-log4j_log4j-core_org-apache-logging-log4j-core-async-DisruptorBlockingQueueFactory] +[#org-apache-logging-log4j_log4j-conversant_org-apache-logging-log4j-conversant-DisruptorBlockingQueueFactory] == DisruptorBlockingQueue [#org-apache-logging-log4j_log4j-core_org-apache-logging-log4j-core-async-JCToolsBlockingQueueFactory] == JCToolsBlockingQueue diff --git a/src/site/antora/modules/ROOT/partials/components/log4j-conversant.adoc b/src/site/antora/modules/ROOT/partials/components/log4j-conversant.adoc new file mode 100644 index 00000000000..ba783ef06a8 --- /dev/null +++ b/src/site/antora/modules/ROOT/partials/components/log4j-conversant.adoc @@ -0,0 +1,41 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// + +[tabs] +==== +Maven:: ++ +We assume you use xref:components.adoc#log4j-bom[`log4j-bom`] for dependency management. ++ +[source,xml] +---- + + org.apache.logging.log4j + log4j-conversant + runtime + +---- + +Gradle:: ++ +We assume you use xref:components.adoc#log4j-bom[`log4j-bom`] for dependency management. ++ +[source,groovy] +---- +runtimeOnly 'org.apache.logging.log4j:log4j-conversant' +---- +==== \ No newline at end of file diff --git a/src/site/antora/modules/ROOT/partials/features/queue-conversant.adoc b/src/site/antora/modules/ROOT/partials/features/queue-conversant.adoc new file mode 100644 index 00000000000..2d8b17c405a --- /dev/null +++ b/src/site/antora/modules/ROOT/partials/features/queue-conversant.adoc @@ -0,0 +1,21 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// + +// This file exists in both the 2.x and 3.x branches. +// It contains the dependencies required to enable the blocking queue based on JCTools. + +include::partial$components/log4j-conversant.adoc[] \ No newline at end of file diff --git a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-async-logger.adoc b/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-async-logger.adoc index 870038fe481..66ff22aaedd 100644 --- a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-async-logger.adoc +++ b/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-async-logger.adoc @@ -82,8 +82,8 @@ See xref:manual/async.adoc#AllAsync[Making all loggers asynchronous] for more de | Default value | `true` |=== -Synchronizes access to the Disruptor ring buffer for blocking enqueue operations when the queue is full. -Users encountered excessive CPU utilization with Disruptor v3.4.2 when the application was logging more than the underlying appender could keep up with and the ring buffer became full, especially when the number of application threads vastly outnumbered the number of cores. +Synchronizes access to the LMAX Disruptor ring buffer for blocking enqueue operations when the queue is full. +Users encountered excessive CPU utilization with LMAX Disruptor v3.4.2 when the application was logging more than the underlying appender could keep up with and the ring buffer became full, especially when the number of application threads vastly outnumbered the number of cores. CPU utilization is significantly reduced by restricting access to the enqueue operation. Setting this value to `false` may lead to very high CPU utilization when the async logging queue is full. diff --git a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-garbage-collection.adoc b/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-garbage-collection.adoc index 182a2bab9ed..f2b2f5a1c64 100644 --- a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-garbage-collection.adoc +++ b/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-garbage-collection.adoc @@ -137,7 +137,7 @@ This property specifies the maximum number of primitive arguments to a log messa |=== | Env. variable | `LOG4J_RECYCLER_CAPACITY` | Type | `int` -| Default value | `2C+1` (`C` denoting the number of available processors) +| Default value | `2C` rounded to the next power of `2` (`C` denoting the number of available processors) |=== Denotes the buffer capacity for a recycler. @@ -159,3 +159,24 @@ If an invalid capacity is provided (i.e., if the capacity is zero or negative), If provided, available recycler factory providers will be sorted in order, and the first one whose name matching with this property value will be used. If missing or the selection fails, the default will be used. + +[id=log4j.recycler.conversant.spinPolicy] +== `log4j.recycler.conversant.spinPolicy` + +[cols="1h,5"] +|=== +| Env. variable | `LOG4J_RECYCLER_CONVERSANT_SPIN_POLICY` + +| Type +| https://javadoc.io/doc/com.conversantmedia/disruptor/latest/com.conversantmedia.disruptor/com/conversantmedia/util/concurrent/SpinPolicy.html[`SpinPolicy`] + +| Default value +| https://javadoc.io/doc/com.conversantmedia/disruptor/latest/com.conversantmedia.disruptor/com/conversantmedia/util/concurrent/SpinPolicy.html#WAITING[`WAITING`] +|=== + +Denotes the strategy adopted by the Conversant Disruptor, when the queue is fully used. +This configuration property is only used if the +xref:manual/garbagefree.adoc#recyclers-conversant[`conversant-disrutor` recycler] +is used. + +See https://javadoc.io/doc/com.conversantmedia/disruptor/latest/com.conversantmedia.disruptor/com/conversantmedia/util/concurrent/SpinPolicy.html[Conversant Disruptor Javadoc] for details. \ No newline at end of file