You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by vy...@apache.org on 2023/12/18 20:07:54 UTC
(logging-log4j2) branch main updated: Move JCTools-based queues to a new `log4j-jctools` module (#2104)
This is an automated email from the ASF dual-hosted git repository.
vy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
The following commit(s) were added to refs/heads/main by this push:
new 2e4e5e5102 Move JCTools-based queues to a new `log4j-jctools` module (#2104)
2e4e5e5102 is described below
commit 2e4e5e510243cab0d5ae56a26916df8000a2f575
Author: Volkan Yazıcı <vo...@yazi.ci>
AuthorDate: Mon Dec 18 21:07:42 2023 +0100
Move JCTools-based queues to a new `log4j-jctools` module (#2104)
---
.../log4j/internal/RecyclerFactoriesTest.java | 102 ------
.../log4j/internal/StringParameterParserTest.java | 373 ---------------------
.../log4j/internal/recycler/ArrayQueueTest.java | 101 ++++++
.../recycler/RecyclerFactoryRegistryTest.java | 85 +++++
.../internal/recycler/RecyclerFactoryTestUtil.java | 46 +++
.../ThreadLocalRecyclerFactoryProviderTest.java} | 46 ++-
log4j-api/pom.xml | 11 +-
.../logging/log4j/internal/DefaultLogBuilder.java | 4 +-
.../logging/log4j/internal/QueueFactories.java | 160 ---------
.../log4j/internal/QueueingRecyclerFactory.java | 92 -----
.../logging/log4j/internal/RecyclerFactories.java | 173 ----------
.../log4j/internal/StringParameterParser.java | 304 -----------------
.../log4j/internal/ThreadLocalRecyclerFactory.java | 100 ------
.../log4j/internal/recycler/ArrayQueue.java | 100 ++++++
.../recycler/DummyRecyclerFactoryProvider.java | 81 +++++
.../recycler/QueueingRecyclerFactoryProvider.java | 106 ++++++
.../ThreadLocalRecyclerFactoryProvider.java | 125 +++++++
.../logging/log4j/message/MessageFactory.java | 2 +-
.../log4j/message/ParameterizedMessage.java | 2 +-
.../log4j/message/ReusableMessageFactory.java | 4 +-
.../message/ReusableParameterizedMessage.java | 4 +-
.../apache/logging/log4j/spi/AbstractLogger.java | 2 +
.../logging/log4j/spi/DummyRecyclerFactory.java | 59 ----
.../apache/logging/log4j/spi/LoggingSystem.java | 6 +-
.../logging/log4j/spi/LoggingSystemProperty.java | 13 +-
.../logging/log4j/spi/PropertyComponent.java | 2 +
.../org/apache/logging/log4j/spi/QueueFactory.java | 32 --
.../apache/logging/log4j/spi/RecyclerFactory.java | 58 ----
.../log4j/spi/{ => recycler}/AbstractRecycler.java | 10 +-
.../logging/log4j/spi/{ => recycler}/Recycler.java | 10 +-
.../log4j/spi/{ => recycler}/RecyclerAware.java | 3 +-
.../log4j/spi/recycler/RecyclerFactory.java | 58 ++++
.../spi/recycler/RecyclerFactoryProvider.java | 59 ++++
.../spi/recycler/RecyclerFactoryRegistry.java | 119 +++++++
.../logging/log4j/spi/recycler/package-info.java | 26 ++
.../apache/logging/log4j/status/StatusLogger.java | 5 +-
.../logging/log4j/util/ServiceLoaderUtil.java | 11 +
log4j-core-its/pom.xml | 6 -
log4j-core-test/pom.xml | 6 -
.../log4j/core/appender/AsyncAppenderTest.java | 8 -
log4j-core/pom.xml | 7 -
.../logging/log4j/core/config/Configuration.java | 2 +-
.../log4j/core/filter/StructuredDataFilter.java | 4 +-
.../logging/log4j/core/impl/DefaultBundle.java | 1 +
.../logging/log4j/core/impl/MutableLogEvent.java | 2 +-
.../log4j/core/impl/ReusableLogEventFactory.java | 4 +-
.../log4j/core/layout/AbstractStringLayout.java | 4 +-
.../logging/log4j/core/layout/PatternLayout.java | 2 +-
.../log4j/core/layout/StringBuilderEncoder.java | 2 +-
.../apache/logging/log4j/core/util/JsonUtils.java | 2 +-
log4j-gctests/pom.xml | 6 -
{log4j-api => log4j-jctools}/pom.xml | 58 ++--
.../jctools}/JCToolsBlockingQueueFactory.java | 9 +-
.../jctools/JCToolsRecyclerFactoryProvider.java | 104 ++++++
.../jctools/JCToolsBlockingQueueFactoryTest.java | 76 +++++
.../JCToolsRecyclerFactoryProviderTest.java | 24 +-
.../resources/JCToolsBlockingQueueFactoryTest.xml | 2 +-
...ging.log4j.spi.recycler.RecyclerFactoryProvider | 6 +
log4j-layout-template-json-test/pom.xml | 6 -
...rFactoryCustomizedJsonTemplateLayoutLogging.xml | 30 --
log4j-layout-template-json/pom.xml | 5 -
.../layout/template/json/JsonTemplateLayout.java | 2 +-
.../template/json/resolver/CounterResolver.java | 2 +-
.../json/resolver/MessageParameterResolver.java | 2 +-
.../json/resolver/ReadOnlyStringMapResolver.java | 4 +-
.../json/resolver/StackTraceStringResolver.java | 4 +-
.../plugins/convert/TypeConverterFactory.java | 3 -
.../java/org/apache/logging/slf4j/SLF4JLogger.java | 2 +-
pom.xml | 7 +
src/site/asciidoc/manual/appenders.adoc | 1 +
src/site/asciidoc/runtime-dependencies.adoc | 4 -
71 files changed, 1270 insertions(+), 1631 deletions(-)
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/RecyclerFactoriesTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/RecyclerFactoriesTest.java
deleted file mode 100644
index 9673abe14f..0000000000
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/RecyclerFactoriesTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.internal;
-
-import java.util.ArrayDeque;
-import java.util.concurrent.ArrayBlockingQueue;
-import org.apache.logging.log4j.spi.DummyRecyclerFactory;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
-import org.assertj.core.api.Assertions;
-import org.assertj.core.api.InstanceOfAssertFactories;
-import org.junit.jupiter.api.Test;
-
-public class RecyclerFactoriesTest {
-
- @Test
- void DummyRecyclerFactory_should_work() {
- final RecyclerFactory actualDummyRecyclerFactory = RecyclerFactories.ofSpec("dummy");
- Assertions.assertThat(actualDummyRecyclerFactory).isSameAs(DummyRecyclerFactory.getInstance());
- }
-
- @Test
- void ThreadLocalRecyclerFactory_should_work() {
- final RecyclerFactory actualThreadLocalRecyclerFactory = RecyclerFactories.ofSpec("threadLocal");
- Assertions.assertThat(actualThreadLocalRecyclerFactory)
- .asInstanceOf(InstanceOfAssertFactories.type(ThreadLocalRecyclerFactory.class))
- .extracting(ThreadLocalRecyclerFactory::getCapacity)
- .isEqualTo(RecyclerFactories.CAPACITY);
- }
-
- @Test
- void ThreadLocalRecyclerFactory_should_work_with_capacity() {
- final int capacity = 13;
- final RecyclerFactory actualThreadLocalRecyclerFactory =
- RecyclerFactories.ofSpec("threadLocal:capacity=" + capacity);
- Assertions.assertThat(actualThreadLocalRecyclerFactory)
- .asInstanceOf(InstanceOfAssertFactories.type(ThreadLocalRecyclerFactory.class))
- .extracting(ThreadLocalRecyclerFactory::getCapacity)
- .isEqualTo(capacity);
- }
-
- @Test
- void QueueingRecyclerFactory_should_work() {
- final RecyclerFactory actualQueueingRecyclerFactory = RecyclerFactories.ofSpec("queue");
- Assertions.assertThat(actualQueueingRecyclerFactory)
- .asInstanceOf(InstanceOfAssertFactories.type(QueueingRecyclerFactory.class))
- .extracting(QueueingRecyclerFactory::getCapacity)
- .isEqualTo(RecyclerFactories.CAPACITY);
- }
-
- @Test
- void QueueingRecyclerFactory_should_work_with_supplier() {
- final RecyclerFactory recyclerFactory = RecyclerFactories.ofSpec("queue:supplier=java.util.ArrayDeque.new");
- Assertions.assertThat(recyclerFactory).isInstanceOf(QueueingRecyclerFactory.class);
- final QueueingRecyclerFactory queueingRecyclerFactory = (QueueingRecyclerFactory) recyclerFactory;
- final Recycler<Object> recycler = queueingRecyclerFactory.create(Object::new);
- Assertions.assertThat(recycler).isInstanceOf(QueueingRecyclerFactory.QueueingRecycler.class);
- final QueueingRecyclerFactory.QueueingRecycler<Object> queueingRecycler =
- (QueueingRecyclerFactory.QueueingRecycler<Object>) recycler;
- Assertions.assertThat(queueingRecycler.getQueue()).isInstanceOf(ArrayDeque.class);
- }
-
- @Test
- void QueueingRecyclerFactory_should_work_with_capacity() {
- final int capacity = 100;
- final RecyclerFactory actualQueueingRecyclerFactory = RecyclerFactories.ofSpec("queue:capacity=" + capacity);
- Assertions.assertThat(actualQueueingRecyclerFactory)
- .asInstanceOf(InstanceOfAssertFactories.type(QueueingRecyclerFactory.class))
- .extracting(QueueingRecyclerFactory::getCapacity)
- .isEqualTo(capacity);
- }
-
- @Test
- void QueueingRecyclerFactory_should_work_with_supplier_and_capacity() {
- final int capacity = 100;
- final RecyclerFactory recyclerFactory = RecyclerFactories.ofSpec(
- "queue:supplier=java.util.concurrent.ArrayBlockingQueue.new,capacity=" + capacity);
- Assertions.assertThat(recyclerFactory).isInstanceOf(QueueingRecyclerFactory.class);
- final QueueingRecyclerFactory queueingRecyclerFactory = (QueueingRecyclerFactory) recyclerFactory;
- final Recycler<Object> recycler = queueingRecyclerFactory.create(Object::new);
- Assertions.assertThat(recycler).isInstanceOf(QueueingRecyclerFactory.QueueingRecycler.class);
- final QueueingRecyclerFactory.QueueingRecycler<Object> queueingRecycler =
- (QueueingRecyclerFactory.QueueingRecycler<Object>) recycler;
- Assertions.assertThat(queueingRecycler.getQueue()).isInstanceOf(ArrayBlockingQueue.class);
- final ArrayBlockingQueue<Object> queue = (ArrayBlockingQueue<Object>) queueingRecycler.getQueue();
- Assertions.assertThat(queue.remainingCapacity()).isEqualTo(capacity);
- }
-}
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/StringParameterParserTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/StringParameterParserTest.java
deleted file mode 100644
index 512cc708c6..0000000000
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/StringParameterParserTest.java
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
- * 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.internal;
-
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
-import org.apache.logging.log4j.internal.StringParameterParser.DoubleQuotedStringValue;
-import org.apache.logging.log4j.internal.StringParameterParser.NullValue;
-import org.apache.logging.log4j.internal.StringParameterParser.StringValue;
-import org.apache.logging.log4j.internal.StringParameterParser.Value;
-import org.apache.logging.log4j.internal.StringParameterParser.Values;
-import org.assertj.core.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-@SuppressWarnings("DoubleBraceInitialization")
-class StringParameterParserTest {
-
- @Test
- void test_empty_string() {
- testSuccess("", Collections.emptyMap());
- }
-
- @Test
- void test_blank_string() {
- testSuccess("\t", Collections.emptyMap());
- }
-
- @Test
- void test_simple_pair() {
- testSuccess("a=b", Collections.singletonMap("a", Values.stringValue("b")));
- }
-
- @Test
- void test_simple_pair_with_whitespace_1() {
- testSuccess(" a=b", Collections.singletonMap("a", Values.stringValue("b")));
- }
-
- @Test
- void test_simple_pair_with_whitespace_2() {
- testSuccess(" a =b", Collections.singletonMap("a", Values.stringValue("b")));
- }
-
- @Test
- void test_simple_pair_with_whitespace_3() {
- testSuccess(" a = b", Collections.singletonMap("a", Values.stringValue("b")));
- }
-
- @Test
- void test_simple_pair_with_whitespace_4() {
- testSuccess(" a = b ", Collections.singletonMap("a", Values.stringValue("b")));
- }
-
- @Test
- void test_null_value_1() {
- testSuccess("a", Collections.singletonMap("a", Values.nullValue()));
- }
-
- @Test
- void test_null_value_2() {
- testSuccess("a,b=c,d=", new LinkedHashMap<>() {
- {
- put("a", Values.nullValue());
- put("b", Values.stringValue("c"));
- put("d", Values.nullValue());
- }
- });
- }
-
- @Test
- void test_null_value_3() {
- testSuccess("a,b=c,d", new LinkedHashMap<>() {
- {
- put("a", Values.nullValue());
- put("b", Values.stringValue("c"));
- put("d", Values.nullValue());
- }
- });
- }
-
- @Test
- void test_null_value_4() {
- testSuccess("a,b=\"c,=\\\"\",d=,e=f", new LinkedHashMap<>() {
- {
- put("a", Values.nullValue());
- put("b", Values.doubleQuotedStringValue("c,=\""));
- put("d", Values.nullValue());
- put("e", Values.stringValue("f"));
- }
- });
- }
-
- @Test
- void test_two_pairs() {
- testSuccess("a=b,c=d", new LinkedHashMap<>() {
- {
- put("a", Values.stringValue("b"));
- put("c", Values.stringValue("d"));
- }
- });
- }
-
- @Test
- void test_quoted_string_01() {
- testSuccess("a=\"b\"", Collections.singletonMap("a", Values.doubleQuotedStringValue("b")));
- }
-
- @Test
- void test_quoted_string_02() {
- testSuccess("a=\"b\",c=d", new LinkedHashMap<>() {
- {
- put("a", Values.doubleQuotedStringValue("b"));
- put("c", Values.stringValue("d"));
- }
- });
- }
-
- @Test
- void test_quoted_string_03() {
- testSuccess("a=b,c=\"d\"", new LinkedHashMap<>() {
- {
- put("a", Values.stringValue("b"));
- put("c", Values.doubleQuotedStringValue("d"));
- }
- });
- }
-
- @Test
- void test_quoted_string_04() {
- testSuccess("a=\"b\",c=\"d\"", new LinkedHashMap<>() {
- {
- put("a", Values.doubleQuotedStringValue("b"));
- put("c", Values.doubleQuotedStringValue("d"));
- }
- });
- }
-
- @Test
- void test_quoted_string_05() {
- testSuccess("a=\"\\\"b\"", Collections.singletonMap("a", Values.doubleQuotedStringValue("\"b")));
- }
-
- @Test
- void test_quoted_string_06() {
- testSuccess("a=\"\\\"b\\\"\"", Collections.singletonMap("a", Values.doubleQuotedStringValue("\"b\"")));
- }
-
- @Test
- void test_quoted_string_07() {
- testSuccess("a=\"\\\"b\",c=d", new LinkedHashMap<>() {
- {
- put("a", Values.doubleQuotedStringValue("\"b"));
- put("c", Values.stringValue("d"));
- }
- });
- }
-
- @Test
- void test_quoted_string_08() {
- testSuccess("a=\"\\\"b\\\"\",c=d", new LinkedHashMap<String, Value>() {
- {
- put("a", Values.doubleQuotedStringValue("\"b\""));
- put("c", Values.stringValue("d"));
- }
- });
- }
-
- @Test
- void test_quoted_string_09() {
- testSuccess("a=\"\\\"b,\",c=d", new LinkedHashMap<>() {
- {
- put("a", Values.doubleQuotedStringValue("\"b,"));
- put("c", Values.stringValue("d"));
- }
- });
- }
-
- @Test
- void test_quoted_string_10() {
- testSuccess("a=\"\\\"b\\\",\",c=d", new LinkedHashMap<>() {
- {
- put("a", Values.doubleQuotedStringValue("\"b\","));
- put("c", Values.stringValue("d"));
- }
- });
- }
-
- @Test
- void test_quoted_string_11() {
- testSuccess("a=\"\\\"b\",c=\"d\"", new LinkedHashMap<>() {
- {
- put("a", Values.doubleQuotedStringValue("\"b"));
- put("c", Values.doubleQuotedStringValue("d"));
- }
- });
- }
-
- @Test
- void test_quoted_string_12() {
- testSuccess("a=\"\\\"b\\\"\",c=\"d\"", new LinkedHashMap<>() {
- {
- put("a", Values.doubleQuotedStringValue("\"b\""));
- put("c", Values.doubleQuotedStringValue("d"));
- }
- });
- }
-
- @Test
- void test_quoted_string_13() {
- testSuccess("a=\"\\\"b,\",c=\"\\\"d\"", new LinkedHashMap<String, Value>() {
- {
- put("a", Values.doubleQuotedStringValue("\"b,"));
- put("c", Values.doubleQuotedStringValue("\"d"));
- }
- });
- }
-
- @Test
- void test_quoted_string_14() {
- testSuccess("a=\"\\\"b\\\",\",c=\"\\\"d\\\"\"", new LinkedHashMap<>() {
- {
- put("a", Values.doubleQuotedStringValue("\"b\","));
- put("c", Values.doubleQuotedStringValue("\"d\""));
- }
- });
- }
-
- @Test
- void test_quoted_string_15() {
- testSuccess("a=\"\\\"b\",c=\",d\"", new LinkedHashMap<>() {
- {
- put("a", Values.doubleQuotedStringValue("\"b"));
- put("c", Values.doubleQuotedStringValue(",d"));
- }
- });
- }
-
- @Test
- void test_quoted_string_16() {
- testSuccess("a=\"\\\"b\\\"\",c=\",d\"", new LinkedHashMap<>() {
- {
- put("a", Values.doubleQuotedStringValue("\"b\""));
- put("c", Values.doubleQuotedStringValue(",d"));
- }
- });
- }
-
- @Test
- void test_quoted_string_17() {
- testSuccess("a=\"\\\"b,\",c=\"\\\"d,\"", new LinkedHashMap<>() {
- {
- put("a", Values.doubleQuotedStringValue("\"b,"));
- put("c", Values.doubleQuotedStringValue("\"d,"));
- }
- });
- }
-
- @Test
- void test_quoted_string_18() {
- testSuccess("a=\"\\\"b\\\",\",c=\"\\\"d\\\",\"", new LinkedHashMap<>() {
- {
- put("a", Values.doubleQuotedStringValue("\"b\","));
- put("c", Values.doubleQuotedStringValue("\"d\","));
- }
- });
- }
-
- private static void testSuccess(final String input, final Map<String, Value> expectedMap) {
- final Map<String, Value> actualMap = StringParameterParser.parse(input);
- Assertions.assertThat(actualMap).as("input: %s", input).isEqualTo(expectedMap);
- }
-
- @Test
- void test_missing_key() {
- Assertions.assertThatThrownBy(() -> {
- final String input = ",a=b";
- StringParameterParser.parse(input);
- })
- .hasMessageStartingWith("failed to locate key at index 0");
- }
-
- @Test
- void test_conflicting_key() {
- Assertions.assertThatThrownBy(() -> {
- final String input = "a,a";
- StringParameterParser.parse(input);
- })
- .hasMessageStartingWith("conflicting key at index 2");
- }
-
- @Test
- void test_prematurely_ending_quoted_string_01() {
- Assertions.assertThatThrownBy(() -> {
- final String input = "a,b=\"";
- StringParameterParser.parse(input);
- })
- .hasMessageStartingWith("failed to locate the end of double-quoted content starting at index 4");
- }
-
- @Test
- void test_prematurely_ending_quoted_string_02() {
- Assertions.assertThatThrownBy(() -> {
- final String input = "a,b=\"c";
- StringParameterParser.parse(input);
- })
- .hasMessageStartingWith("failed to locate the end of double-quoted content starting at index 4");
- }
-
- @Test
- void test_prematurely_ending_quoted_string_03() {
- Assertions.assertThatThrownBy(() -> {
- final String input = "a,b=\",c";
- StringParameterParser.parse(input);
- })
- .hasMessageStartingWith("failed to locate the end of double-quoted content starting at index 4");
- }
-
- @Test
- void test_prematurely_ending_quoted_string_04() {
- Assertions.assertThatThrownBy(() -> {
- final String input = "a,b=\",c\" x";
- StringParameterParser.parse(input);
- })
- .hasMessageStartingWith("was expecting comma at index 9");
- }
-
- @Test
- void test_NullValue_toString() {
- final Map<String, Value> map = StringParameterParser.parse("a");
- final NullValue value = (NullValue) map.get("a");
- Assertions.assertThat(value.toString()).isEqualTo("null");
- }
-
- @Test
- void test_StringValue_toString() {
- final Map<String, Value> map = StringParameterParser.parse("a=b");
- final StringValue value = (StringValue) map.get("a");
- Assertions.assertThat(value.toString()).isEqualTo("b");
- }
-
- @Test
- void test_DoubleQuotedStringValue_toString() {
- final Map<String, Value> map = StringParameterParser.parse("a=\"\\\"b\"");
- final DoubleQuotedStringValue value = (DoubleQuotedStringValue) map.get("a");
- Assertions.assertThat(value.toString()).isEqualTo("\"b");
- }
-
- @Test
- void test_allowedKeys() {
- Assertions.assertThatThrownBy(() -> {
- final String input = "a,b";
- final Set<String> allowedKeys = new LinkedHashSet<>(Collections.singletonList("a"));
- StringParameterParser.parse(input, allowedKeys);
- })
- .hasMessageStartingWith("unknown key \"b\" is found in input: a,b");
- }
-}
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ArrayQueueTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ArrayQueueTest.java
new file mode 100644
index 0000000000..2ded8ff7a8
--- /dev/null
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ArrayQueueTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.internal.recycler;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.Queue;
+import java.util.Random;
+import java.util.concurrent.ArrayBlockingQueue;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class ArrayQueueTest {
+
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 0})
+ void invalid_capacity_should_not_be_allowed(final int invalidCapacity) {
+ assertThatThrownBy(() -> new ArrayQueue<>(invalidCapacity))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("invalid capacity: " + invalidCapacity);
+ }
+
+ @Test
+ void should_work_with_capacity_1() {
+
+ // Verify initials
+ final Queue<String> queue = new ArrayQueue<>(1);
+ assertThat(queue.size()).isEqualTo(0);
+ assertThat(queue.peek()).isNull();
+ assertThat(queue.poll()).isNull();
+ assertThat(queue).isEmpty();
+
+ // Verify enqueue & deque
+ assertThat(queue.offer("foo")).isTrue();
+ assertThat(queue.offer("bar")).isFalse();
+ assertThat(queue.size()).isEqualTo(1);
+ assertThat(queue).containsOnly("foo");
+ assertThat(queue.peek()).isEqualTo("foo");
+ assertThat(queue.poll()).isEqualTo("foo");
+
+ // Verify final state
+ assertThat(queue.size()).isEqualTo(0);
+ assertThat(queue.peek()).isNull();
+ assertThat(queue.poll()).isNull();
+ assertThat(queue).isEmpty();
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "1,0.3", "1,0.5", "1,0.8", "2,0.3", "2,0.5", "2,0.8", "3,0.3", "3,0.5", "3,0.8", "4,0.3", "4,0.5", "4,0.8"
+ })
+ void ops_should_match_with_std_lib(final int capacity, final double pollRatio) {
+
+ // Set the stage
+ final Random random = new Random(0);
+ final int opCount = random.nextInt(100);
+ final Queue<String> queueRef = new ArrayBlockingQueue<>(capacity);
+ final Queue<String> queueTarget = new ArrayQueue<>(capacity);
+
+ for (int opIndex = 0; opIndex < opCount; opIndex++) {
+
+ // Verify entry
+ assertThat(queueTarget.size()).isEqualTo(queueRef.size());
+ assertThat(queueTarget.peek()).isEqualTo(queueRef.peek());
+ assertThat(queueTarget).containsExactlyElementsOf(queueRef);
+
+ // Is this a `poll()`?
+ if (pollRatio >= random.nextDouble()) {
+ assertThat(queueTarget.poll()).isEqualTo(queueRef.poll());
+ }
+
+ // Then this is an `offer()`
+ else {
+ final String item = "op@" + opIndex;
+ assertThat(queueTarget.offer(item)).isEqualTo(queueRef.offer(item));
+ }
+
+ // Verify exit
+ assertThat(queueTarget.size()).isEqualTo(queueRef.size());
+ assertThat(queueTarget.peek()).isEqualTo(queueRef.peek());
+ assertThat(queueTarget).containsExactlyElementsOf(queueRef);
+ }
+ }
+}
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoryRegistryTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoryRegistryTest.java
new file mode 100644
index 0000000000..f6684f63cc
--- /dev/null
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoryRegistryTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.internal.recycler;
+
+import static org.apache.logging.log4j.internal.recycler.RecyclerFactoryTestUtil.createForEnvironment;
+import static org.apache.logging.log4j.spi.recycler.Recycler.DEFAULT_CAPACITY;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.logging.log4j.internal.recycler.DummyRecyclerFactoryProvider.DummyRecyclerFactory;
+import org.apache.logging.log4j.internal.recycler.QueueingRecyclerFactoryProvider.QueueingRecyclerFactory;
+import org.apache.logging.log4j.internal.recycler.ThreadLocalRecyclerFactoryProvider.ThreadLocalRecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
+import org.assertj.core.api.InstanceOfAssertFactories;
+import org.junit.jupiter.api.Test;
+
+public class RecyclerFactoryRegistryTest {
+
+ @Test
+ void DummyRecyclerFactory_should_work() {
+ final RecyclerFactory factory = createForEnvironment(null, "dummy", null);
+ assertThat(factory).isInstanceOf(DummyRecyclerFactory.class);
+ }
+
+ @Test
+ void ThreadLocalRecyclerFactory_should_work() {
+ final RecyclerFactory factory = createForEnvironment(null, "threadLocal", null);
+ assertThat(factory)
+ .asInstanceOf(InstanceOfAssertFactories.type(ThreadLocalRecyclerFactory.class))
+ .extracting(factory_ -> factory_.capacity)
+ .isEqualTo(DEFAULT_CAPACITY);
+ }
+
+ @Test
+ void ThreadLocalRecyclerFactory_should_work_with_capacity() {
+ final int capacity = 13;
+ final RecyclerFactory factory = createForEnvironment(null, "threadLocal", capacity);
+ assertThat(factory)
+ .asInstanceOf(InstanceOfAssertFactories.type(ThreadLocalRecyclerFactory.class))
+ .extracting(factory_ -> factory_.capacity)
+ .isEqualTo(capacity);
+ }
+
+ @Test
+ void QueueingRecyclerFactory_should_work() {
+ final RecyclerFactory factory = createForEnvironment(null, "queue", null);
+ assertThat(factory)
+ .asInstanceOf(InstanceOfAssertFactories.type(QueueingRecyclerFactory.class))
+ .extracting(factory_ -> factory_.capacity)
+ .isEqualTo(DEFAULT_CAPACITY);
+ }
+
+ @Test
+ void QueueingRecyclerFactory_should_work_with_capacity() {
+ final int capacity = 100;
+ final RecyclerFactory factory = createForEnvironment(null, "queue", capacity);
+ assertThat(factory)
+ .asInstanceOf(InstanceOfAssertFactories.type(QueueingRecyclerFactory.class))
+ .extracting(factory_ -> factory_.capacity)
+ .isEqualTo(capacity);
+ }
+
+ @Test
+ void verify_order() {
+ final RecyclerFactoryProvider dummyProvider = new DummyRecyclerFactoryProvider();
+ final RecyclerFactoryProvider threadLocalProvider = new ThreadLocalRecyclerFactoryProvider();
+ final RecyclerFactoryProvider queueProvider = new QueueingRecyclerFactoryProvider();
+ assertThat(dummyProvider.getOrder()).isGreaterThan(queueProvider.getOrder());
+ assertThat(queueProvider.getOrder()).isGreaterThan(threadLocalProvider.getOrder());
+ }
+}
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoryTestUtil.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoryTestUtil.java
new file mode 100644
index 0000000000..a6c8f99a2c
--- /dev/null
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoryTestUtil.java
@@ -0,0 +1,46 @@
+/*
+ * 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.internal.recycler;
+
+import edu.umd.cs.findbugs.annotations.Nullable;
+import java.util.Properties;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryRegistry;
+import org.apache.logging.log4j.util.PropertiesUtil;
+import org.apache.logging.log4j.util.PropertyEnvironment;
+
+final class RecyclerFactoryTestUtil {
+
+ private RecyclerFactoryTestUtil() {}
+
+ @Nullable
+ static RecyclerFactory createForEnvironment(
+ @Nullable Boolean threadLocalsEnabled, @Nullable final String factory, @Nullable final Integer capacity) {
+ final Properties properties = new Properties();
+ if (threadLocalsEnabled != null) {
+ properties.setProperty("log4j2.*.ThreadLocals.enable", "" + threadLocalsEnabled);
+ }
+ if (factory != null) {
+ properties.setProperty("log4j2.*.Recycler.factory", factory);
+ }
+ if (capacity != null) {
+ properties.setProperty("log4j2.*.Recycler.capacity", "" + capacity);
+ }
+ final PropertyEnvironment env = new PropertiesUtil(properties);
+ return RecyclerFactoryRegistry.findRecyclerFactory(env);
+ }
+}
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/ThreadLocalRecyclerFactoryTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProviderTest.java
similarity index 60%
rename from log4j-api-test/src/test/java/org/apache/logging/log4j/internal/ThreadLocalRecyclerFactoryTest.java
rename to log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProviderTest.java
index c822e2218f..147a84e52c 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/ThreadLocalRecyclerFactoryTest.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProviderTest.java
@@ -14,34 +14,48 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.logging.log4j.internal;
+package org.apache.logging.log4j.internal.recycler;
+import static org.apache.logging.log4j.internal.recycler.RecyclerFactoryTestUtil.createForEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.List;
import java.util.Queue;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.internal.recycler.ThreadLocalRecyclerFactoryProvider.ThreadLocalRecyclerFactory;
+import org.apache.logging.log4j.internal.recycler.ThreadLocalRecyclerFactoryProvider.ThreadLocalRecyclerFactory.ThreadLocalRecycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junitpioneer.jupiter.params.IntRangeSource;
-class ThreadLocalRecyclerFactoryTest {
+class ThreadLocalRecyclerFactoryProviderTest {
- private static final int CAPACITY = 8;
+ private static final int CAPACITY = 13;
private static class RecyclableObject {}
- private Recycler<RecyclableObject> recycler;
+ private ThreadLocalRecycler<RecyclableObject> recycler;
private Queue<RecyclableObject> recyclerQueue;
@BeforeEach
void setUp() {
- recycler = new ThreadLocalRecyclerFactory(CAPACITY).create(RecyclableObject::new);
- recyclerQueue = ((ThreadLocalRecyclerFactory.ThreadLocalRecycler<RecyclableObject>) recycler).getQueue();
+ final RecyclerFactory recyclerFactory = createForEnvironment(null, "threadLocal", CAPACITY);
+ assertThat(recyclerFactory).isInstanceOf(ThreadLocalRecyclerFactory.class);
+ assert recyclerFactory != null;
+ recycler = (ThreadLocalRecycler<RecyclableObject>) recyclerFactory.create(RecyclableObject::new);
+ recyclerQueue = recycler.queueRef.get();
+ }
+
+ @Test
+ void should_not_be_configured_when_TLs_are_disabled() {
+ assertThatThrownBy(() -> createForEnvironment(false, "threadLocal", null))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageStartingWith("failed to configure recycler");
}
@ParameterizedTest
@@ -79,18 +93,22 @@ class ThreadLocalRecyclerFactoryTest {
assertThat(recyclerQueue).isEmpty();
- // simulate a massively callstack with tons of logging
- final List<RecyclableObject> acquiredObjects =
- IntStream.range(0, 1024).mapToObj(i -> recycler.acquire()).collect(Collectors.toList());
+ // Simulate a callstack with excessive logging
+ final int acquisitionCount = Math.addExact(CAPACITY, 1024);
+ final List<RecyclableObject> acquiredObjects = IntStream.range(0, acquisitionCount)
+ .mapToObj(i -> recycler.acquire())
+ .toList();
- // still nothing returned to pool
+ // Verify collected instances are all new
+ assertThat(acquiredObjects).doesNotHaveDuplicates();
+
+ // Verify the pool is still empty
assertThat(recyclerQueue).isEmpty();
- // don't want any duplicate instances
- assertThat(acquiredObjects).containsOnlyOnceElementsOf(acquiredObjects);
+ // Release all acquired instances
acquiredObjects.forEach(recycler::release);
- // upon return, we should only have `CAPACITY` retained for future use
+ // Verify the queue size is capped
assertThat(recyclerQueue).hasSize(CAPACITY);
}
}
diff --git a/log4j-api/pom.xml b/log4j-api/pom.xml
index 2ea9e66998..bb22c40e74 100644
--- a/log4j-api/pom.xml
+++ b/log4j-api/pom.xml
@@ -36,9 +36,7 @@
<bnd-module-name>org.apache.logging.log4j</bnd-module-name>
<bnd-extra-package-options>
<!-- No JSR 305 at runtime -->
- !javax.annotation.*,
- <!-- Optional dependencies -->
- org.jctools.*;resolution:=optional
+ !javax.annotation.*
</bnd-extra-package-options>
<bnd-extra-module-options>
<!-- No JSR 305 at runtime -->
@@ -48,11 +46,4 @@
</bnd-extra-module-options>
</properties>
- <dependencies>
- <dependency>
- <groupId>org.jctools</groupId>
- <artifactId>jctools-core</artifactId>
- <optional>true</optional>
- </dependency>
- </dependencies>
</project>
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java
index e53fd1f589..cffd2ce56f 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java
@@ -24,8 +24,8 @@ import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.SimpleMessage;
import org.apache.logging.log4j.spi.ExtendedLogger;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerAware;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerAware;
import org.apache.logging.log4j.util.*;
/**
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/QueueFactories.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/QueueFactories.java
deleted file mode 100644
index 3db9e5d6e9..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/QueueFactories.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * 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.internal;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-import java.util.Queue;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.function.Supplier;
-import org.apache.logging.log4j.spi.QueueFactory;
-import org.apache.logging.log4j.util.Cast;
-import org.apache.logging.log4j.util.InternalApi;
-import org.apache.logging.log4j.util.LoaderUtil;
-import org.jctools.queues.MpmcArrayQueue;
-import org.jctools.queues.SpscArrayQueue;
-
-/**
- * Provides {@link QueueFactory} instances for different use cases.
- * <p>
- * Implementations provided by <a href="https://jctools.github.io/JCTools/">JCTools</a> will be preferred, if available at runtime.
- * Otherwise, {@link ArrayBlockingQueue} will be used.
- * </p>
- *
- * @since 3.0.0
- */
-@InternalApi
-public enum QueueFactories implements QueueFactory {
-
- /**
- * Provides a bounded queue for single-producer/single-consumer usage.
- */
- SPSC(() -> SpscArrayQueue::new),
-
- /**
- * Provides a bounded queue for multi-producer/multi-consumer usage.
- */
- MPMC(() -> MpmcArrayQueue::new);
-
- private final QueueFactory queueFactory;
-
- QueueFactories(final Supplier<QueueFactory> queueFactoryProvider) {
- this.queueFactory = getOrReplaceQueueFactory(queueFactoryProvider);
- }
-
- private static QueueFactory getOrReplaceQueueFactory(final Supplier<QueueFactory> queueFactoryProvider) {
- try {
- final QueueFactory queueFactory = queueFactoryProvider.get();
- // Test with a large enough capacity to avoid any `IllegalArgumentExceptions` from trivial queues
- queueFactory.create(16);
- return queueFactory;
- } catch (final LinkageError ignored) {
- return ArrayBlockingQueueFactory.INSTANCE;
- }
- }
-
- @Override
- public <E> Queue<E> create(final int capacity) {
- return queueFactory.create(capacity);
- }
-
- private static final class ArrayBlockingQueueFactory implements QueueFactory {
-
- private static final ArrayBlockingQueueFactory INSTANCE = new ArrayBlockingQueueFactory();
-
- private ArrayBlockingQueueFactory() {}
-
- @Override
- public <E> Queue<E> create(final int capacity) {
- return new ArrayBlockingQueue<>(capacity);
- }
- }
-
- /**
- * Creates a {@link QueueFactory} using the provided supplier.
- * <p>
- * A supplier path must be formatted as follows:
- * <ul>
- * <li>{@code <fully-qualified-class-name>.new} – the class constructor accepting a single {@code int} argument (denoting the capacity) will be used (e.g., {@code org.jctools.queues.MpmcArrayQueue.new})</li>
- * <li>{@code <fully-qualified-class-name>.<static-factory-method>} – the static factory method accepting a single {@code int} argument (denoting the capacity) will be used (e.g., {@code com.acme.Queues.createBoundedQueue})</li>
- * </ul>
- * </p>
- *
- * @param supplierPath a queue supplier path (e.g., {@code org.jctools.queues.MpmcArrayQueue.new}, {@code com.acme.Queues.createBoundedQueue})
- * @return a new {@link QueueFactory} instance
- */
- public static QueueFactory ofSupplier(final String supplierPath) {
- final int supplierPathSplitterIndex = supplierPath.lastIndexOf('.');
- if (supplierPathSplitterIndex < 0) {
- final String message = String.format("invalid queue factory supplier path: `%s`", supplierPath);
- throw new IllegalArgumentException(message);
- }
- final String supplierClassName = supplierPath.substring(0, supplierPathSplitterIndex);
- final String supplierMethodName = supplierPath.substring(supplierPathSplitterIndex + 1);
- try {
- final Class<?> supplierClass = LoaderUtil.loadClass(supplierClassName);
- if ("new".equals(supplierMethodName)) {
- final Constructor<?> supplierCtor = supplierClass.getDeclaredConstructor(int.class);
- return new ConstructorProvidedQueueFactory(supplierCtor);
- } else {
- final Method supplierMethod = supplierClass.getMethod(supplierMethodName, int.class);
- return new StaticMethodProvidedQueueFactory(supplierMethod);
- }
- } catch (final ReflectiveOperationException | LinkageError | SecurityException error) {
- final String message =
- String.format("failed to create the queue factory using the supplier path `%s`", supplierPath);
- throw new RuntimeException(message, error);
- }
- }
-
- private static final class ConstructorProvidedQueueFactory implements QueueFactory {
-
- private final Constructor<?> constructor;
-
- private ConstructorProvidedQueueFactory(final Constructor<?> constructor) {
- this.constructor = constructor;
- }
-
- @Override
- public <E> Queue<E> create(final int capacity) {
- final Constructor<Queue<E>> typedConstructor = Cast.cast(constructor);
- try {
- return typedConstructor.newInstance(capacity);
- } catch (final ReflectiveOperationException error) {
- throw new RuntimeException("queue construction failure", error);
- }
- }
- }
-
- private static final class StaticMethodProvidedQueueFactory implements QueueFactory {
-
- private final Method method;
-
- private StaticMethodProvidedQueueFactory(final Method method) {
- this.method = method;
- }
-
- @Override
- public <E> Queue<E> create(final int capacity) {
- try {
- return Cast.cast(method.invoke(null, capacity));
- } catch (final ReflectiveOperationException error) {
- throw new RuntimeException("queue construction failure", error);
- }
- }
- }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/QueueingRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/QueueingRecyclerFactory.java
deleted file mode 100644
index b5b6c70f72..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/QueueingRecyclerFactory.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.internal;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.Queue;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-import org.apache.logging.log4j.spi.AbstractRecycler;
-import org.apache.logging.log4j.spi.QueueFactory;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
-
-/**
- * A {@link RecyclerFactory} pooling objects in a queue created using the provided {@link QueueFactory}.
- */
-final class QueueingRecyclerFactory implements RecyclerFactory {
-
- private final QueueFactory queueFactory;
-
- private final int capacity;
-
- QueueingRecyclerFactory(final QueueFactory queueFactory, final int capacity) {
- if (capacity < 1) {
- throw new IllegalArgumentException("was expecting `capacity > 0`, found: " + capacity);
- }
- this.queueFactory = requireNonNull(queueFactory, "queueFactory");
- this.capacity = capacity;
- }
-
- /**
- * @return the maximum number of objects retained per thread in recyclers created
- */
- int getCapacity() {
- return capacity;
- }
-
- @Override
- public <V> Recycler<V> create(final Supplier<V> supplier, final Consumer<V> cleaner) {
- requireNonNull(supplier, "supplier");
- requireNonNull(cleaner, "cleaner");
- final Queue<V> queue = queueFactory.create(capacity);
- return new QueueingRecycler<>(supplier, cleaner, queue);
- }
-
- // Visible for tests
- static class QueueingRecycler<V> extends AbstractRecycler<V> {
-
- private final Consumer<V> cleaner;
-
- private final Queue<V> queue;
-
- private QueueingRecycler(final Supplier<V> supplier, final Consumer<V> cleaner, final Queue<V> queue) {
- super(supplier);
- this.cleaner = cleaner;
- this.queue = queue;
- }
-
- // Visible for tests
- Queue<V> getQueue() {
- return 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-api/src/main/java/org/apache/logging/log4j/internal/RecyclerFactories.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/RecyclerFactories.java
deleted file mode 100644
index aea850f199..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/RecyclerFactories.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * 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.internal;
-
-import static java.util.Objects.requireNonNull;
-import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled;
-
-import java.util.Map;
-import java.util.Set;
-import org.apache.logging.log4j.spi.DummyRecyclerFactory;
-import org.apache.logging.log4j.spi.QueueFactory;
-import org.apache.logging.log4j.spi.RecyclerFactory;
-import org.apache.logging.log4j.util.InternalApi;
-
-/**
- * Stores the default {@link RecyclerFactory} instance.
- */
-@InternalApi
-public final class RecyclerFactories {
-
- /**
- * The default recycler capacity.
- */
- public static final int CAPACITY = Math.max(2 * Runtime.getRuntime().availableProcessors() + 1, 8);
-
- /**
- * The default recycler instance.
- */
- public static final RecyclerFactory INSTANCE = isThreadLocalsEnabled()
- ? new ThreadLocalRecyclerFactory(CAPACITY)
- : new QueueingRecyclerFactory(QueueFactories.MPMC, CAPACITY);
-
- private RecyclerFactories() {}
-
- /**
- * Creates a {@link RecyclerFactory} instance using the provided specification.
- * <p>
- * The recycler factory specification string must be formatted as follows:
- * </p>
- * <pre>{@code
- * recyclerFactorySpec = dummySpec
- * | threadLocalRecyclerFactorySpec
- * | queueingRecyclerFactorySpec
- *
- * dummySpec = "dummy"
- *
- * threadLocalRecyclerFactorySpec = "threadLocal" , [ ":" , capacityArg ]
- * capacityArg = "capacity=" , integer
- *
- * queueingRecyclerFactorySpec = "queue" , [ ":" , queueingRecyclerFactoryArgs ]
- * queueingRecyclerFactoryArgs = queueingRecyclerFactoryArg , [ "," , queueingRecyclerFactoryArg ]*
- * queueingRecyclerFactoryArg = capacityArg
- * | queueSupplierArg
- * queueSupplierArg = ( classPath , ".new" )
- * | ( classPath , "." , methodName )
- * }</pre>
- * <p>
- * If not specified, {@code capacity} will be set to {@code max(8, 2*C+1)}, where {@code C} denotes the value returned by {@link Runtime#availableProcessors()}.
- * </p>
- * <p>
- * You can find some examples below.
- * </p>
- * <ul>
- * <li><code>{@code dummy}</code></li>
- * <li><code>{@code threadLocal}</code></li>
- * <li><code>{@code threadLocal:capacity=13}</code></li>
- * <li><code>{@code queue}</code></li>
- * <li><code>{@code queue:supplier=java.util.ArrayDeque.new}</code></li>
- * <li><code>{@code queue:capacity=100}</code></li>
- * <li><code>{@code queue:supplier=com.acme.AwesomeQueue.create,capacity=42}</code></li>
- * </ul>
- * @param recyclerFactorySpec the recycler factory specification string
- * @return a recycler factory instance
- */
- public static RecyclerFactory ofSpec(final String recyclerFactorySpec) {
-
- // Check arguments
- requireNonNull(recyclerFactorySpec, "recyclerFactorySpec");
-
- // Is a dummy factory requested?
- if (recyclerFactorySpec.equals("dummy")) {
- return DummyRecyclerFactory.getInstance();
- }
-
- // Is a TLA factory requested?
- else if (recyclerFactorySpec.startsWith("threadLocal")) {
- return readThreadLocalRecyclerFactory(recyclerFactorySpec);
- }
-
- // Is a queueing factory requested?
- else if (recyclerFactorySpec.startsWith("queue")) {
- return readQueueingRecyclerFactory(recyclerFactorySpec);
- }
-
- // Bogus input, bail out.
- else {
- throw new IllegalArgumentException("invalid recycler factory: " + recyclerFactorySpec);
- }
- }
-
- private static RecyclerFactory readThreadLocalRecyclerFactory(final String recyclerFactorySpec) {
-
- // Parse the spec
- final String queueFactorySpec = recyclerFactorySpec.substring(
- "threadLocal".length() + (recyclerFactorySpec.startsWith("threadLocal:") ? 1 : 0));
- final Map<String, StringParameterParser.Value> parsedValues =
- StringParameterParser.parse(queueFactorySpec, Set.of("capacity"));
-
- // Read the capacity
- final int capacity = readQueueCapacity(queueFactorySpec, parsedValues);
-
- // Execute the read spec
- return new ThreadLocalRecyclerFactory(capacity);
- }
-
- private static RecyclerFactory readQueueingRecyclerFactory(final String recyclerFactorySpec) {
-
- // Parse the spec
- final String queueFactorySpec =
- recyclerFactorySpec.substring("queue".length() + (recyclerFactorySpec.startsWith("queue:") ? 1 : 0));
- final Map<String, StringParameterParser.Value> parsedValues =
- StringParameterParser.parse(queueFactorySpec, Set.of("supplier", "capacity"));
-
- // Read the capacity
- final int capacity = readQueueCapacity(queueFactorySpec, parsedValues);
-
- // Read the supplier path
- final StringParameterParser.Value supplierValue = parsedValues.get("supplier");
- final String supplierPath = supplierValue == null || supplierValue instanceof StringParameterParser.NullValue
- ? null
- : supplierValue.toString();
-
- // Execute the read spec
- final QueueFactory queueFactory =
- supplierPath != null ? QueueFactories.ofSupplier(supplierPath) : QueueFactories.MPMC;
- return new QueueingRecyclerFactory(queueFactory, capacity);
- }
-
- private static int readQueueCapacity(
- final String factorySpec, final Map<String, StringParameterParser.Value> parsedValues) {
- final StringParameterParser.Value capacityValue = parsedValues.get("capacity");
- if (capacityValue == null || capacityValue instanceof StringParameterParser.NullValue) {
- return CAPACITY;
- } else {
- final int capacity;
- try {
- capacity = Integer.parseInt(capacityValue.toString());
- } catch (final NumberFormatException error) {
- throw new IllegalArgumentException(
- "failed reading `capacity` in recycler factory: " + factorySpec, error);
- }
- if (capacity < 1) {
- throw new IllegalArgumentException(
- "was expecting `capacity > 0` in the recycler factory: " + factorySpec);
- }
- return capacity;
- }
- }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/StringParameterParser.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/StringParameterParser.java
deleted file mode 100644
index 9d1127ae28..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/StringParameterParser.java
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * 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.internal;
-
-import java.util.*;
-import java.util.concurrent.Callable;
-import org.apache.logging.log4j.util.InternalApi;
-import org.apache.logging.log4j.util.Strings;
-
-/**
- * Utility class for parsing string-formatted parameters, e.g., {@code queue:supplier=com.acme.FastestQueue.new,capacity=42}.
- * <p>
- * See the associated test class for possible combinations and double-, single-quote handling.
- * </p>
- */
-@InternalApi
-public final class StringParameterParser {
-
- private StringParameterParser() {}
-
- public static final class Values {
-
- private Values() {}
-
- static NullValue nullValue() {
- return NullValue.INSTANCE;
- }
-
- static StringValue stringValue(final String string) {
- return new StringValue(string);
- }
-
- static DoubleQuotedStringValue doubleQuotedStringValue(final String doubleQuotedString) {
- return new DoubleQuotedStringValue(doubleQuotedString);
- }
- }
-
- public interface Value {}
-
- public static final class NullValue implements Value {
-
- private static final NullValue INSTANCE = new NullValue();
-
- private NullValue() {}
-
- @Override
- public String toString() {
- return "null";
- }
- }
-
- public static final class StringValue implements Value {
-
- private final String string;
-
- private StringValue(final String string) {
- this.string = string;
- }
-
- public String getString() {
- return string;
- }
-
- @Override
- public boolean equals(final Object object) {
- if (this == object) {
- return true;
- }
- if (object == null || getClass() != object.getClass()) {
- return false;
- }
- final StringValue that = (StringValue) object;
- return string.equals(that.string);
- }
-
- @Override
- public int hashCode() {
- return 31 + Objects.hashCode(string);
- }
-
- @Override
- public String toString() {
- return string;
- }
- }
-
- public static final class DoubleQuotedStringValue implements Value {
-
- private final String doubleQuotedString;
-
- private DoubleQuotedStringValue(final String doubleQuotedString) {
- this.doubleQuotedString = doubleQuotedString;
- }
-
- public String getDoubleQuotedString() {
- return doubleQuotedString;
- }
-
- @Override
- public boolean equals(final Object object) {
- if (this == object) {
- return true;
- }
- if (object == null || getClass() != object.getClass()) {
- return false;
- }
- final DoubleQuotedStringValue that = (DoubleQuotedStringValue) object;
- return doubleQuotedString.equals(that.doubleQuotedString);
- }
-
- @Override
- public int hashCode() {
- return 31 + Objects.hashCode(doubleQuotedString);
- }
-
- @Override
- public String toString() {
- return doubleQuotedString.replaceAll("\\\\\"", "\"");
- }
- }
-
- private enum State {
- READING_KEY,
- READING_VALUE
- }
-
- private static final class Parser implements Callable<Map<String, Value>> {
-
- private final String input;
-
- private final Map<String, Value> map;
-
- private State state;
-
- private int i;
-
- private String key;
-
- private Parser(final String input) {
- this.input = Objects.requireNonNull(input, "input");
- this.map = new LinkedHashMap<>();
- this.state = State.READING_KEY;
- this.i = 0;
- this.key = null;
- }
-
- @Override
- public Map<String, Value> call() {
- while (true) {
- skipWhitespace();
- if (i >= input.length()) {
- break;
- }
- switch (state) {
- case READING_KEY:
- readKey();
- break;
- case READING_VALUE:
- readValue();
- break;
- default:
- throw new IllegalStateException("unknown state: " + state);
- }
- }
- if (state == State.READING_VALUE) {
- map.put(key, Values.nullValue());
- }
- return map;
- }
-
- private void readKey() {
- final int eq = input.indexOf('=', i);
- final int co = input.indexOf(',', i);
- final int j;
- final int nextI;
- if (eq < 0 && co < 0) {
- // Neither '=', nor ',' was found.
- j = nextI = input.length();
- } else if (eq < 0) {
- // Found ','.
- j = nextI = co;
- } else if (co < 0) {
- // Found '='.
- j = eq;
- nextI = eq + 1;
- } else if (eq < co) {
- // Found '=...,'.
- j = eq;
- nextI = eq + 1;
- } else {
- // Found ',...='.
- j = co;
- nextI = co;
- }
- key = input.substring(i, j).trim();
- if (Strings.isEmpty(key)) {
- final String message = String.format("failed to locate key at index %d: %s", i, input);
- throw new IllegalArgumentException(message);
- }
- if (map.containsKey(key)) {
- final String message = String.format("conflicting key at index %d: %s", i, input);
- throw new IllegalArgumentException(message);
- }
- state = State.READING_VALUE;
- i = nextI;
- }
-
- private void readValue() {
- final boolean doubleQuoted = input.charAt(i) == '"';
- if (doubleQuoted) {
- readDoubleQuotedStringValue();
- } else {
- readStringValue();
- }
- key = null;
- state = State.READING_KEY;
- }
-
- private void readDoubleQuotedStringValue() {
- int j = i + 1;
- while (j < input.length()) {
- if (input.charAt(j) == '"' && input.charAt(j - 1) != '\\') {
- break;
- } else {
- j++;
- }
- }
- if (j >= input.length()) {
- final String message = String.format(
- "failed to locate the end of double-quoted content starting at index %d: %s", i, input);
- throw new IllegalArgumentException(message);
- }
- final String content = input.substring(i + 1, j).replaceAll("\\\\\"", "\"");
- final Value value = Values.doubleQuotedStringValue(content);
- map.put(key, value);
- i = j + 1;
- skipWhitespace();
- if (i < input.length()) {
- if (input.charAt(i) != ',') {
- final String message = String.format("was expecting comma at index %d: %s", i, input);
- throw new IllegalArgumentException(message);
- }
- i++;
- }
- }
-
- private void skipWhitespace() {
- while (i < input.length()) {
- final char c = input.charAt(i);
- if (!Character.isWhitespace(c)) {
- break;
- } else {
- i++;
- }
- }
- }
-
- private void readStringValue() {
- int j = input.indexOf(',', i /* + 1*/);
- if (j < 0) {
- j = input.length();
- }
- final String content = input.substring(i, j);
- final String trimmedContent = content.trim();
- final Value value = trimmedContent.isEmpty() ? Values.nullValue() : Values.stringValue(trimmedContent);
- map.put(key, value);
- i += content.length() + 1;
- }
- }
-
- public static Map<String, Value> parse(final String input) {
- return parse(input, null);
- }
-
- public static Map<String, Value> parse(final String input, final Set<String> allowedKeys) {
- if (Strings.isBlank(input)) {
- return Collections.emptyMap();
- }
- final Map<String, Value> map = new Parser(input).call();
- final Set<String> actualKeys = map.keySet();
- for (final String actualKey : actualKeys) {
- final boolean allowed = allowedKeys == null || allowedKeys.contains(actualKey);
- if (!allowed) {
- final String message = String.format("unknown key \"%s\" is found in input: %s", actualKey, input);
- throw new IllegalArgumentException(message);
- }
- }
- return map;
- }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/ThreadLocalRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/ThreadLocalRecyclerFactory.java
deleted file mode 100644
index 56e8930547..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/ThreadLocalRecyclerFactory.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.internal;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.Queue;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-import org.apache.logging.log4j.spi.AbstractRecycler;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
-
-/**
- * A {@link RecyclerFactory} pooling objects in a queue stored in a {@link ThreadLocal}.
- * <p>
- * This strategy may not be appropriate in workloads where units of work are independent of operating system threads such as reactive streams, coroutines, or virtual threads.
- * For such use cases, see {@link QueueingRecyclerFactory}.
- * </p>
- *
- * @since 3.0.0
- */
-final class ThreadLocalRecyclerFactory implements RecyclerFactory {
-
- /**
- * Maximum number of objects retained per thread.
- * <p>
- * This allows to acquire objects in recursive method calls and maintain minimal overhead in the scenarios where the active instance count goes far beyond this for a brief moment.
- * </p>
- */
- private final int capacity;
-
- ThreadLocalRecyclerFactory(int capacity) {
- if (capacity < 1) {
- throw new IllegalArgumentException("was expecting a `capacity` greater than 1, found: " + capacity);
- }
- this.capacity = capacity;
- }
-
- /**
- * @return maximum number of objects retained per thread in recyclers created
- */
- int getCapacity() {
- return capacity;
- }
-
- @Override
- public <V> Recycler<V> create(final Supplier<V> supplier, final Consumer<V> cleaner) {
- requireNonNull(supplier, "supplier");
- requireNonNull(cleaner, "cleaner");
- return new ThreadLocalRecycler<>(supplier, cleaner, capacity);
- }
-
- // Visible for testing
- static class ThreadLocalRecycler<V> extends AbstractRecycler<V> {
-
- private final Consumer<V> cleaner;
-
- private final ThreadLocal<Queue<V>> holder;
-
- private ThreadLocalRecycler(final Supplier<V> supplier, final Consumer<V> cleaner, final int capacity) {
- super(supplier);
- this.holder = ThreadLocal.withInitial(() -> QueueFactories.SPSC.create(capacity));
- this.cleaner = cleaner;
- }
-
- @Override
- public V acquire() {
- final Queue<V> queue = holder.get();
- final V value = queue.poll();
- return value != null ? value : createInstance();
- }
-
- @Override
- public void release(final V value) {
- requireNonNull(value, "value");
- cleaner.accept(value);
- holder.get().offer(value);
- }
-
- // Visible for testing
- Queue<V> getQueue() {
- return holder.get();
- }
- }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ArrayQueue.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ArrayQueue.java
new file mode 100644
index 0000000000..90c573b364
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ArrayQueue.java
@@ -0,0 +1,100 @@
+/*
+ * 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.internal.recycler;
+
+import java.util.AbstractQueue;
+import java.util.Iterator;
+import java.util.stream.IntStream;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.apache.logging.log4j.util.InternalApi;
+
+/**
+ * An array-backed, fixed-length, not-thread-safe {@link java.util.Queue} implementation.
+ *
+ * @param <E> the element type
+ */
+@InternalApi
+@NotThreadSafe
+final class ArrayQueue<E> extends AbstractQueue<E> {
+
+ private final E[] buffer;
+
+ private int head;
+
+ private int tail;
+
+ private int size;
+
+ @SuppressWarnings("unchecked")
+ ArrayQueue(final int capacity) {
+ if (capacity < 1) {
+ throw new IllegalArgumentException("invalid capacity: " + capacity);
+ }
+ buffer = (E[]) new Object[capacity];
+ head = 0;
+ tail = -1;
+ size = 0;
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ int[] i = {head};
+ return IntStream.range(0, size)
+ .mapToObj(ignored -> {
+ final E item = buffer[i[0]];
+ i[0] = (i[0] + 1) % buffer.length;
+ return item;
+ })
+ .iterator();
+ }
+
+ @Override
+ public boolean offer(final E item) {
+ if (size == buffer.length) {
+ return false;
+ }
+ tail = (tail + 1) % buffer.length;
+ buffer[tail] = item;
+ size++;
+ return true;
+ }
+
+ @Override
+ public E poll() {
+ if (isEmpty()) {
+ return null;
+ }
+ final E item = buffer[head];
+ buffer[head] = null; // Clear refs for GC
+ head = (head + 1) % buffer.length;
+ size--;
+ return item;
+ }
+
+ @Override
+ public E peek() {
+ if (isEmpty()) {
+ return null;
+ }
+ return buffer[head];
+ }
+
+ @Override
+ public int size() {
+ return size;
+ }
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/DummyRecyclerFactoryProvider.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/DummyRecyclerFactoryProvider.java
new file mode 100644
index 0000000000..f0a531b657
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/DummyRecyclerFactoryProvider.java
@@ -0,0 +1,81 @@
+/*
+ * 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.internal.recycler;
+
+import static java.util.Objects.requireNonNull;
+
+import aQute.bnd.annotation.spi.ServiceProvider;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apache.logging.log4j.spi.recycler.AbstractRecycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.util.PropertyEnvironment;
+
+/**
+ * A {@link Recycler} factory provider such that the recycler does not recycle anything; all instances are freshly created.
+ *
+ * @since 3.0.0
+ */
+@ServiceProvider(RecyclerFactoryProvider.class)
+public final class DummyRecyclerFactoryProvider implements RecyclerFactoryProvider {
+
+ @Override
+ public int getOrder() {
+ return 900;
+ }
+
+ @Override
+ public String getName() {
+ return "dummy";
+ }
+
+ @Override
+ public RecyclerFactory createForEnvironment(final PropertyEnvironment environment) {
+ return DummyRecyclerFactory.INSTANCE;
+ }
+
+ // Visible for testing
+ static final class DummyRecyclerFactory implements RecyclerFactory {
+
+ private static final DummyRecyclerFactory INSTANCE = new DummyRecyclerFactory();
+
+ private DummyRecyclerFactory() {}
+
+ @Override
+ public <V> Recycler<V> create(final Supplier<V> supplier, final Consumer<V> cleaner) {
+ requireNonNull(supplier, "supplier");
+ return new DummyRecycler<>(supplier);
+ }
+
+ private static final class DummyRecycler<V> extends AbstractRecycler<V> {
+
+ private DummyRecycler(final Supplier<V> supplier) {
+ super(supplier);
+ }
+
+ @Override
+ public V acquire() {
+ return createInstance();
+ }
+
+ @Override
+ public void release(final V value) {}
+ }
+ }
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/QueueingRecyclerFactoryProvider.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/QueueingRecyclerFactoryProvider.java
new file mode 100644
index 0000000000..655e9e4a9e
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/QueueingRecyclerFactoryProvider.java
@@ -0,0 +1,106 @@
+/*
+ * 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.internal.recycler;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.logging.log4j.spi.recycler.Recycler.DEFAULT_CAPACITY;
+
+import aQute.bnd.annotation.spi.ServiceProvider;
+import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apache.logging.log4j.spi.LoggingSystemProperty;
+import org.apache.logging.log4j.spi.recycler.AbstractRecycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.util.PropertyEnvironment;
+
+/**
+ * A {@link Recycler} factory provider such that the recycler pools objects in a fixed-size queue.
+ */
+@ServiceProvider(RecyclerFactoryProvider.class)
+public final class QueueingRecyclerFactoryProvider implements RecyclerFactoryProvider {
+
+ @Override
+ public int getOrder() {
+ return 800;
+ }
+
+ @Override
+ public String getName() {
+ return "queue";
+ }
+
+ @Override
+ public RecyclerFactory createForEnvironment(final PropertyEnvironment environment) {
+ requireNonNull(environment, "environment");
+ final int capacity = environment.getIntegerProperty(LoggingSystemProperty.RECYCLER_CAPACITY, DEFAULT_CAPACITY);
+ if (capacity < 1) {
+ throw new IllegalArgumentException("was expecting a `capacity` greater than 1, found: " + capacity);
+ }
+ return new QueueingRecyclerFactory(capacity);
+ }
+
+ // Visible for testing
+ static final class QueueingRecyclerFactory implements RecyclerFactory {
+
+ // Visible for testing
+ final int capacity;
+
+ private QueueingRecyclerFactory(int capacity) {
+ this.capacity = capacity;
+ }
+
+ @Override
+ public <V> Recycler<V> create(final Supplier<V> supplier, final Consumer<V> cleaner) {
+ requireNonNull(supplier, "supplier");
+ requireNonNull(cleaner, "cleaner");
+ final Queue<V> queue = new ArrayBlockingQueue<>(capacity);
+ return new QueueingRecycler<>(supplier, cleaner, queue);
+ }
+
+ // Visible for testing
+ static final class QueueingRecycler<V> extends AbstractRecycler<V> {
+
+ private final Consumer<V> cleaner;
+
+ // Visible for testing
+ final Queue<V> queue;
+
+ private QueueingRecycler(final Supplier<V> supplier, final Consumer<V> cleaner, final Queue<V> 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-api/src/main/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProvider.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProvider.java
new file mode 100644
index 0000000000..6cdbba0863
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProvider.java
@@ -0,0 +1,125 @@
+/*
+ * 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.internal.recycler;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.logging.log4j.spi.recycler.Recycler.DEFAULT_CAPACITY;
+
+import aQute.bnd.annotation.spi.ServiceProvider;
+import edu.umd.cs.findbugs.annotations.Nullable;
+import java.util.Queue;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apache.logging.log4j.spi.LoggingSystemProperty;
+import org.apache.logging.log4j.spi.recycler.AbstractRecycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.util.PropertyEnvironment;
+
+/**
+ * A {@link Recycler} factory provider such that the recycler pools objects in a fixed-size queue stored in a {@link ThreadLocal}.
+ * <p>
+ * This strategy may not be appropriate in workloads where units of work are independent of operating system threads such as reactive streams, coroutines, or virtual threads.
+ * For such use cases, see {@link QueueingRecyclerFactoryProvider}.
+ * </p>
+ *
+ * @since 3.0.0
+ */
+@ServiceProvider(RecyclerFactoryProvider.class)
+public final class ThreadLocalRecyclerFactoryProvider implements RecyclerFactoryProvider {
+
+ @Override
+ public int getOrder() {
+ return 700;
+ }
+
+ @Override
+ public String getName() {
+ return "threadLocal";
+ }
+
+ @Nullable
+ @Override
+ public RecyclerFactory createForEnvironment(PropertyEnvironment environment) {
+ requireNonNull(environment, "environment");
+ final boolean threadLocalEnabled =
+ environment.getBooleanProperty(LoggingSystemProperty.THREAD_LOCALS_ENABLE, true);
+ if (!threadLocalEnabled) {
+ return null;
+ }
+ final int capacity = environment.getIntegerProperty(LoggingSystemProperty.RECYCLER_CAPACITY, DEFAULT_CAPACITY);
+ if (capacity < 1) {
+ throw new IllegalArgumentException("was expecting a `capacity` greater than 1, found: " + capacity);
+ }
+ return new ThreadLocalRecyclerFactory(capacity);
+ }
+
+ // Visible for testing
+ static final class ThreadLocalRecyclerFactory implements RecyclerFactory {
+
+ /**
+ * Maximum number of objects retained per thread.
+ * <p>
+ * This allows to acquire objects in recursive method calls and maintain minimal overhead in the scenarios where the active instance count goes far beyond this for a brief moment.
+ * </p>
+ */
+ // Visible for testing
+ final int capacity;
+
+ private ThreadLocalRecyclerFactory(int capacity) {
+ this.capacity = capacity;
+ }
+
+ @Override
+ public <V> Recycler<V> create(final Supplier<V> supplier, final Consumer<V> cleaner) {
+ requireNonNull(supplier, "supplier");
+ requireNonNull(cleaner, "cleaner");
+ return new ThreadLocalRecycler<>(supplier, cleaner, capacity);
+ }
+
+ // Visible for testing
+ static final class ThreadLocalRecycler<V> extends AbstractRecycler<V> {
+
+ private final Consumer<V> cleaner;
+
+ // Visible for testing
+ final ThreadLocal<Queue<V>> queueRef;
+
+ private ThreadLocalRecycler(final Supplier<V> supplier, final Consumer<V> cleaner, final int capacity) {
+ super(supplier);
+ this.queueRef = ThreadLocal.withInitial(() -> new ArrayQueue<>(capacity));
+ this.cleaner = cleaner;
+ }
+
+ @Override
+ public V acquire() {
+ final Queue<V> queue = queueRef.get();
+ final V value = queue.poll();
+ return value != null ? value : createInstance();
+ }
+
+ @Override
+ public void release(final V value) {
+ requireNonNull(value, "value");
+ cleaner.accept(value);
+ final Queue<V> queue = queueRef.get();
+ queue.offer(value);
+ }
+ }
+ }
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java
index 50f457140e..21176cd0c5 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java
@@ -16,7 +16,7 @@
*/
package org.apache.logging.log4j.message;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
/**
* Creates messages. Implementations can provide different message format syntaxes.
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java
index f980631224..aae12e6f37 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java
@@ -19,7 +19,7 @@ package org.apache.logging.log4j.message;
import java.util.Arrays;
import java.util.Objects;
import org.apache.logging.log4j.spi.LoggingSystem;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
import org.apache.logging.log4j.util.StringBuilderFormattable;
import org.apache.logging.log4j.util.StringBuilders;
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
index a2523c2ae4..7abd8ac064 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
@@ -17,8 +17,8 @@
package org.apache.logging.log4j.message;
import org.apache.logging.log4j.spi.LoggingSystem;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
index 326da8dabe..928e201f1b 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
@@ -18,8 +18,8 @@ package org.apache.logging.log4j.message;
import java.util.Arrays;
import org.apache.logging.log4j.spi.LoggingSystem;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
import org.apache.logging.log4j.util.Constants;
import org.apache.logging.log4j.util.PerformanceSensitive;
import org.apache.logging.log4j.util.StringBuilders;
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
index 2b5606714c..5dd788c06b 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
@@ -27,6 +27,8 @@ import org.apache.logging.log4j.message.FlowMessageFactory;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.message.StringFormattedMessage;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.Cast;
import org.apache.logging.log4j.util.LambdaUtil;
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java
deleted file mode 100644
index 0076cd49e6..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DummyRecyclerFactory.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.spi;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-/**
- * Recycler strategy which doesn't recycle anything; all instances are freshly created.
- *
- * @since 3.0.0
- */
-public final class DummyRecyclerFactory implements RecyclerFactory {
-
- private static final DummyRecyclerFactory INSTANCE = new DummyRecyclerFactory();
-
- private DummyRecyclerFactory() {}
-
- public static DummyRecyclerFactory getInstance() {
- return INSTANCE;
- }
-
- @Override
- public <V> Recycler<V> create(final Supplier<V> supplier, final Consumer<V> cleaner) {
- requireNonNull(supplier, "supplier");
- return new DummyRecycler<>(supplier);
- }
-
- private static class DummyRecycler<V> extends AbstractRecycler<V> {
-
- private DummyRecycler(final Supplier<V> supplier) {
- super(supplier);
- }
-
- @Override
- public V acquire() {
- return createInstance();
- }
-
- @Override
- public void release(final V value) {}
- }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java
index 08daa2e05e..46cf2ae91f 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java
@@ -41,13 +41,14 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.ThreadContext;
-import org.apache.logging.log4j.internal.RecyclerFactories;
import org.apache.logging.log4j.message.DefaultFlowMessageFactory;
import org.apache.logging.log4j.message.FlowMessageFactory;
import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.message.ParameterizedMessageFactory;
import org.apache.logging.log4j.message.ReusableMessageFactory;
import org.apache.logging.log4j.simple.SimpleLoggerContextFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryRegistry;
import org.apache.logging.log4j.util.Constants;
import org.apache.logging.log4j.util.Lazy;
import org.apache.logging.log4j.util.LoaderUtil;
@@ -105,7 +106,8 @@ public class LoggingSystem {
environmentLazy.map(environment -> () -> getProvider().createContextMap(environment));
private final Lazy<Supplier<ThreadContextStack>> threadContextStackFactoryLazy =
environmentLazy.map(environment -> () -> getProvider().createContextStack(environment));
- private final Lazy<RecyclerFactory> recyclerFactoryLazy = Lazy.lazy(() -> RecyclerFactories.INSTANCE);
+ private final Lazy<RecyclerFactory> recyclerFactoryLazy =
+ environmentLazy.map(RecyclerFactoryRegistry::findRecyclerFactory);
public LoggingSystem() {}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProperty.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProperty.java
index b9d814cb15..63183ae763 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProperty.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProperty.java
@@ -190,7 +190,15 @@ public enum LoggingSystemProperty implements PropertyKey {
* (from either {@code javax} or {@code jakarta}) is checked to see if this is a webapp.
*/
// Web.isWebApp : calculate | true | false
- IS_WEBAPP(PropertyComponent.WEB, Constant.IS_WEB_APP);
+ IS_WEBAPP(PropertyComponent.WEB, Constant.IS_WEB_APP),
+ /**
+ * Property to override the default recycler factory.
+ */
+ RECYCLER_FACTORY(PropertyComponent.RECYCLER, Constant.RECYCLER_FACTORY),
+ /**
+ * Property to override the default recycler capacity.
+ */
+ RECYCLER_CAPACITY(PropertyComponent.RECYCLER, Constant.RECYCLER_CAPACITY);
public static final String SIMPLE_LOGGER_LOG_LEVEL = "SimpleLogger.%s.level";
public static final String SYSTEM_PROPERTY_PREFIX = "log4j2.*.";
@@ -476,5 +484,8 @@ public enum LoggingSystemProperty implements PropertyKey {
static final String IS_WEB_APP = "isWebApp";
public static final String WEB =
LoggingSystemProperty.SYSTEM_PROPERTY_PREFIX + PropertyComponent.Constant.WEB + DELIM + IS_WEB_APP;
+
+ static final String RECYCLER_FACTORY = "factory";
+ static final String RECYCLER_CAPACITY = "capacity";
}
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/PropertyComponent.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/PropertyComponent.java
index 8a748bc84a..5dd99ef77a 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/PropertyComponent.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/PropertyComponent.java
@@ -35,6 +35,7 @@ public enum PropertyComponent {
LOGGER(Constant.LOGGER),
LOGGER_CONTEXT(Constant.LOGGER_CONTEXT),
MESSAGE(Constant.MESSAGE),
+ RECYCLER(Constant.RECYCLER),
SCRIPT(Constant.SCRIPT),
SIMPLE_LOGGER(Constant.SIMPLE_LOGGER),
STATUS_LOGGER(Constant.STATUS_LOGGER),
@@ -74,6 +75,7 @@ public enum PropertyComponent {
public static final String LOGGER = "Logger";
public static final String LOGGER_CONTEXT = "LoggerContext";
public static final String MESSAGE = "Message";
+ public static final String RECYCLER = "Recycler";
public static final String SCRIPT = "Script";
public static final String SIMPLE_LOGGER = "SimpleLogger";
public static final String STATUS_LOGGER = "StatusLogger";
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueFactory.java
deleted file mode 100644
index 6c41f637fd..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/QueueFactory.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.spi;
-
-import java.util.Queue;
-import org.apache.logging.log4j.internal.QueueFactories;
-
-/**
- * A {@link Queue} factory contract.
- *
- * @see QueueFactories
- * @since 3.0.0
- */
-@FunctionalInterface
-public interface QueueFactory {
-
- <E> Queue<E> create(final int capacity);
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java
deleted file mode 100644
index 96c9f010e5..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerFactory.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.spi;
-
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-/**
- * Factory for {@link Recycler} strategies. Depending on workloads, different instance recycling strategies may be
- * most performant. For example, traditional multithreaded workloads may benefit from using thread-local instance
- * recycling while different models of concurrency or versions of the JVM may benefit from using an object pooling
- * strategy instead.
- *
- * @since 3.0.0
- */
-@FunctionalInterface
-public interface RecyclerFactory {
-
- /**
- * Creates a new recycler using the given supplier function for initial instances. These instances have
- * no cleaner function and are assumed to always be reusable.
- *
- * @param supplier function to provide new instances of a recyclable object
- * @param <V> the recyclable type
- * @return a new recycler for V-type instances
- */
- default <V> Recycler<V> create(final Supplier<V> supplier) {
- return create(supplier, ignored -> {});
- }
-
- /**
- * Creates a new recycler using the given functions for providing fresh instances and for cleaning up
- * existing instances for reuse. For example, a StringBuilder recycler would provide two functions:
- * a supplier function for constructing a new StringBuilder with a preselected initial capacity and
- * another function for trimming the StringBuilder to some preselected maximum capacity and setting
- * its length back to 0 as if it were a fresh StringBuilder.
- *
- * @param supplier function to provide new instances of a recyclable object
- * @param cleaner function to reset a recyclable object to a fresh state
- * @param <V> the recyclable type
- * @return a new recycler for V-type instances
- */
- <V> Recycler<V> create(Supplier<V> supplier, Consumer<V> cleaner);
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractRecycler.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/AbstractRecycler.java
similarity index 80%
rename from log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractRecycler.java
rename to log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/AbstractRecycler.java
index 6b74698a9e..84b5924f25 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractRecycler.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/AbstractRecycler.java
@@ -14,7 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.logging.log4j.spi;
+package org.apache.logging.log4j.spi.recycler;
+
+import static java.util.Objects.requireNonNull;
import java.util.function.Supplier;
@@ -22,15 +24,15 @@ public abstract class AbstractRecycler<V> implements Recycler<V> {
private final Supplier<V> supplier;
- public AbstractRecycler(final Supplier<V> supplier) {
- this.supplier = supplier;
+ protected AbstractRecycler(final Supplier<V> supplier) {
+ this.supplier = requireNonNull(supplier, "supplier");
}
protected final V createInstance() {
final V instance = supplier.get();
if (instance instanceof RecyclerAware) {
@SuppressWarnings("unchecked")
- RecyclerAware<V> recyclerAware = (RecyclerAware<V>) instance;
+ final RecyclerAware<V> recyclerAware = (RecyclerAware<V>) instance;
recyclerAware.setRecycler(this);
}
return instance;
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Recycler.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/Recycler.java
similarity index 77%
rename from log4j-api/src/main/java/org/apache/logging/log4j/spi/Recycler.java
rename to log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/Recycler.java
index 50cd53a471..4a9db5b0b0 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Recycler.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/Recycler.java
@@ -14,16 +14,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.logging.log4j.spi;
+package org.apache.logging.log4j.spi.recycler;
/**
- * Strategy for recycling objects. This is primarily useful for heavyweight objects and buffers.
+ * Contract for recycling strategies.
+ * This is the primary building block for logging components striving for garbage-free operation.
*
* @param <V> the recyclable type
* @since 3.0.0
*/
public interface Recycler<V> {
+ /**
+ * The default recycler capacity: {@code max(2C+1, 8)}, {@code C} denoting the number of available processors
+ */
+ int DEFAULT_CAPACITY = Math.max(2 * Runtime.getRuntime().availableProcessors() + 1, 8);
+
/**
* Acquires an instance of V. This may either be a fresh instance of V or a recycled instance of V.
* Recycled instances will be modified by their cleanup function before being returned.
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerAware.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerAware.java
similarity index 95%
rename from log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerAware.java
rename to log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerAware.java
index 42e4d72163..4483ed3a02 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/RecyclerAware.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerAware.java
@@ -14,7 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.logging.log4j.spi;
+package org.apache.logging.log4j.spi.recycler;
+
/**
* Interface implemented by classes that need to interact with the {@link Recycler} that created them.
*/
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactory.java
new file mode 100644
index 0000000000..bb935b1958
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactory.java
@@ -0,0 +1,58 @@
+/*
+ * 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.spi.recycler;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Contract for {@link Recycler} factories.
+ *
+ * @since 3.0.0
+ */
+public interface RecyclerFactory {
+
+ /**
+ * Creates a new recycler using the given supplier function for initial instances.
+ *
+ * @param supplier a function to provide initial instances
+ * @param <V> the recyclable type
+ * @return a new recycler
+ */
+ default <V> Recycler<V> create(final Supplier<V> supplier) {
+ return create(supplier, ignored -> {});
+ }
+
+ /**
+ * Creates a new recycler using the given supplier and cleaner functions.
+ * <p>
+ * The provided supplier needs to make sure that generated instances are always clean.
+ * </p>
+ * <p>
+ * Recycled instances are always guaranteed to be clean.
+ * The cleaning of an instance can take place either just before acquisition or prior to admitting it back into the reusable instances pool.
+ * The moment when the cleaning will be carried out is implementation dependent.
+ * Though a released instance should ideally be cleaned immediately to avoid keeping references to unused objects.
+ * </p>
+ *
+ * @param supplier a function to provide initial (and clean!) instances
+ * @param cleaner function to reset an instance before reuse
+ * @param <V> the recyclable type
+ * @return a new recycler
+ */
+ <V> Recycler<V> create(Supplier<V> supplier, Consumer<V> cleaner);
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactoryProvider.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactoryProvider.java
new file mode 100644
index 0000000000..d049e2d01e
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactoryProvider.java
@@ -0,0 +1,59 @@
+/*
+ * 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.spi.recycler;
+
+import edu.umd.cs.findbugs.annotations.Nullable;
+import org.apache.logging.log4j.util.PropertyEnvironment;
+
+/**
+ * Contract for providing {@link RecyclerFactory} instances.
+ *
+ * @since 3.0.0
+ */
+public interface RecyclerFactoryProvider {
+
+ /**
+ * Denotes the value to be used while sorting recycler factory providers to determine the precedence order.
+ * Values will be sorted naturally, that is, lower values will imply higher precedence.
+ *
+ * @return the value to be used while sorting
+ */
+ default int getOrder() {
+ return 100;
+ }
+
+ /**
+ * The name of this recycler factory provider.
+ * Recycler factory providers are required to have unique names.
+ *
+ * @return the name of this recycler factory provider
+ */
+ String getName();
+
+ /**
+ * Creates a recycler factory for the provided environment.
+ * <p>
+ * The return value can be null indicating that the recycler factory is not available for the provided environment.
+ * For instance, the provider of a {@link ThreadLocal}-based recycler factory can return null if the environment is of a web application.
+ * </p>
+ *
+ * @param environment an environment
+ * @return either a recycler factory instance, or null, if the associated recycler factory is not available for the given environment
+ */
+ @Nullable
+ RecyclerFactory createForEnvironment(PropertyEnvironment environment);
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactoryRegistry.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactoryRegistry.java
new file mode 100644
index 0000000000..ba17fc98e9
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/RecyclerFactoryRegistry.java
@@ -0,0 +1,119 @@
+/*
+ * 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.spi.recycler;
+
+import static java.util.Objects.requireNonNull;
+
+import aQute.bnd.annotation.spi.ServiceConsumer;
+import edu.umd.cs.findbugs.annotations.Nullable;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.logging.log4j.spi.LoggingSystemProperty;
+import org.apache.logging.log4j.util.PropertyEnvironment;
+import org.apache.logging.log4j.util.ServiceLoaderUtil;
+
+/**
+ * Registry of available {@link RecyclerFactoryProvider}s.
+ */
+@ServiceConsumer(RecyclerFactoryProvider.class)
+public final class RecyclerFactoryRegistry {
+
+ private static final Map<String, RecyclerFactoryProvider> PROVIDER_BY_NAME = loadProviders();
+
+ private static Map<String, RecyclerFactoryProvider> loadProviders() {
+ return ServiceLoaderUtil.safeStream(RecyclerFactoryProvider.class, null)
+ .sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder))
+ .collect(Collectors.toMap(
+ RecyclerFactoryProvider::getName,
+ Function.identity(),
+ (provider1, provider2) -> {
+ final String message = String.format(
+ "recycler factory providers `%s` (order=%d) and `%s` (order=%d) have conflicting names: `%s`",
+ provider1.getClass().getCanonicalName(),
+ provider1.getOrder(),
+ provider2.getClass().getCanonicalName(),
+ provider2.getOrder(),
+ provider2.getName());
+ throw new IllegalStateException(message);
+ },
+ LinkedHashMap::new));
+ }
+
+ private RecyclerFactoryRegistry() {}
+
+ public static Collection<RecyclerFactoryProvider> getRecyclerFactoryProviders() {
+ return PROVIDER_BY_NAME.values();
+ }
+
+ public static RecyclerFactory findRecyclerFactory(final PropertyEnvironment environment) {
+
+ // Check arguments
+ requireNonNull(environment, "environment");
+
+ // Short-circuit if there are no recycler factories available
+ if (PROVIDER_BY_NAME.isEmpty()) {
+ throw new IllegalStateException("couldn't find any recycler factories");
+ }
+
+ // If there is a particular recycler factory requested, get that done.
+ @Nullable final String name = environment.getStringProperty(LoggingSystemProperty.RECYCLER_FACTORY);
+ if (name != null) {
+ return findRecyclerFactoryByName(environment, name);
+ }
+
+ // Otherwise, find one available
+ return PROVIDER_BY_NAME.values().stream()
+ .sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder))
+ .map(provider -> provider.createForEnvironment(environment))
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElseThrow(() -> {
+ final String names = PROVIDER_BY_NAME.keySet().stream()
+ .map(name_ -> '`' + name_ + '`')
+ .collect(Collectors.joining(", "));
+ final String message = String.format(
+ "Couldn't find a recycler factory provider available for the provided environment. Names of the available recycler factory providers: %s",
+ names);
+ return new IllegalArgumentException(message);
+ });
+ }
+
+ private static RecyclerFactory findRecyclerFactoryByName(final PropertyEnvironment environment, final String name) {
+ @Nullable final RecyclerFactoryProvider provider = PROVIDER_BY_NAME.get(name);
+ if (provider == null) {
+ final String names = PROVIDER_BY_NAME.keySet().stream()
+ .map(name_ -> '`' + name_ + '`')
+ .collect(Collectors.joining(", "));
+ final String message = String.format(
+ "Couldn't find a recycler factory provider of name `%s`. Names of the available recycler factory providers: %s",
+ name, names);
+ throw new IllegalArgumentException(message);
+ }
+ @Nullable final RecyclerFactory factory = provider.createForEnvironment(environment);
+ if (factory == null) {
+ final String message = String.format(
+ "failed to configure recycler factory of name `%s` for the provided environment", name);
+ throw new IllegalArgumentException(message);
+ }
+ return factory;
+ }
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/package-info.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/package-info.java
new file mode 100644
index 0000000000..f45bb6e5af
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/recycler/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+/**
+ * Internal interfaces and classes to be used by authors of logging implementations or for internal use by
+ * API classes.
+ */
+@Export
+@Version("3.0.0")
+package org.apache.logging.log4j.spi.recycler;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java
index 7f0247ccef..f198bbee3e 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java
@@ -21,12 +21,12 @@ import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
-import org.apache.logging.log4j.internal.QueueFactories;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory;
import org.apache.logging.log4j.simple.SimpleLogger;
@@ -63,7 +63,7 @@ public final class StatusLogger extends AbstractLogger {
private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
- private final Queue<StatusData> messages;
+ private final Queue<StatusData> messages = new ConcurrentLinkedQueue<>();
private int listenersLevel;
@@ -97,7 +97,6 @@ public final class StatusLogger extends AbstractLogger {
this.logger = logger;
this.configuration = configuration;
this.listenersLevel = configuration.getDefaultLevel().intLevel();
- messages = QueueFactories.MPMC.create(configuration.getMaxEntries());
}
/**
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
index b9be78fcd1..7fe952cbd0 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
@@ -16,6 +16,9 @@
*/
package org.apache.logging.log4j.util;
+import static java.util.Objects.requireNonNull;
+
+import edu.umd.cs.findbugs.annotations.Nullable;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ServiceConfigurationError;
@@ -36,7 +39,15 @@ public final class ServiceLoaderUtil {
private ServiceLoaderUtil() {}
+ public static <S> Stream<S> safeStream(final Class<S> type, @Nullable final ClassLoader classLoader) {
+ requireNonNull(type, "type");
+ final ClassLoader effectiveLoader = classLoader != null ? classLoader : type.getClassLoader();
+ final ServiceLoader<S> serviceLoader = ServiceLoader.load(type, effectiveLoader);
+ return safeStream(serviceLoader);
+ }
+
public static <S> Stream<S> safeStream(final ServiceLoader<S> serviceLoader) {
+ requireNonNull(serviceLoader, "serviceLoader");
final Set<Class<?>> classes = new HashSet<>();
return StreamSupport.stream(new ServiceLoaderSpliterator<>(serviceLoader), false)
// only the first occurrence of a class
diff --git a/log4j-core-its/pom.xml b/log4j-core-its/pom.xml
index c1dee9b916..2b38ca60b5 100644
--- a/log4j-core-its/pom.xml
+++ b/log4j-core-its/pom.xml
@@ -72,12 +72,6 @@
<artifactId>jackson-dataformat-yaml</artifactId>
<optional>true</optional>
</dependency>
- <!-- Alternative implementation of BlockingQueue using JCTools for AsyncAppender -->
- <dependency>
- <groupId>org.jctools</groupId>
- <artifactId>jctools-core</artifactId>
- <optional>true</optional>
- </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
diff --git a/log4j-core-test/pom.xml b/log4j-core-test/pom.xml
index ec7e071ee0..bd3a339e2a 100644
--- a/log4j-core-test/pom.xml
+++ b/log4j-core-test/pom.xml
@@ -155,12 +155,6 @@
<artifactId>jansi</artifactId>
<optional>true</optional>
</dependency>
- <!-- Alternative implementation of BlockingQueue using JCTools for AsyncAppender -->
- <dependency>
- <groupId>org.jctools</groupId>
- <artifactId>jctools-core</artifactId>
- <optional>true</optional>
- </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/AsyncAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/AsyncAppenderTest.java
index b1aacc3e41..9631d9a2e0 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/AsyncAppenderTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/AsyncAppenderTest.java
@@ -118,14 +118,6 @@ public class AsyncAppenderTest {
assertEquals("STDOUT", appender.getErrorRef());
}
- @Test
- @Tag("jctools")
- @LoggerContextSource("BlockingQueueFactory-JCToolsBlockingQueue.xml")
- public void testJcToolsBlockingQueue(final LoggerContext context) throws InterruptedException {
- rewriteTest(context);
- exceptionTest(context);
- }
-
@Test
@LoggerContextSource("BlockingQueueFactory-LinkedTransferQueue.xml")
public void testLinkedTransferQueue(final LoggerContext context) throws InterruptedException {
diff --git a/log4j-core/pom.xml b/log4j-core/pom.xml
index fcdfffd644..bc33f87910 100644
--- a/log4j-core/pom.xml
+++ b/log4j-core/pom.xml
@@ -40,7 +40,6 @@
com.lmax.disruptor.*;resolution:=optional,
org.apache.commons.compress.*;resolution:=optional,
org.fusesource.jansi;resolution:=optional,
- org.jctools.*;resolution:=optional,
<!-- Optional Java modules -->
<!-- java.management -->
java.lang.management;resolution:=optional,
@@ -134,12 +133,6 @@
<artifactId>jansi</artifactId>
<optional>true</optional>
</dependency>
- <!-- Alternative implementation of BlockingQueue using JCTools for AsyncAppender -->
- <dependency>
- <groupId>org.jctools</groupId>
- <artifactId>jctools-core</artifactId>
- <optional>true</optional>
- </dependency>
</dependencies>
<build>
<plugins>
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
index d5a2c6f168..0d93461512 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
@@ -39,7 +39,7 @@ import org.apache.logging.log4j.core.util.NetUtils;
import org.apache.logging.log4j.core.util.WatchManager;
import org.apache.logging.log4j.plugins.Node;
import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
import org.apache.logging.log4j.util.PropertyEnvironment;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
index 408d0845d3..2a21b56b22 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
@@ -35,8 +35,8 @@ import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.plugins.PluginAttribute;
import org.apache.logging.log4j.plugins.PluginElement;
import org.apache.logging.log4j.plugins.PluginFactory;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
import org.apache.logging.log4j.util.PerformanceSensitive;
import org.apache.logging.log4j.util.StringBuilders;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java
index d4ae74c892..7d9091071b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java
@@ -45,6 +45,7 @@ import org.apache.logging.log4j.plugins.SingletonFactory;
import org.apache.logging.log4j.plugins.condition.ConditionalOnMissingBinding;
import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory;
import org.apache.logging.log4j.spi.*;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
/**
* Provides instance binding defaults.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
index 4e64b8c2cc..44e442eb52 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
@@ -34,7 +34,7 @@ import org.apache.logging.log4j.message.ParameterVisitable;
import org.apache.logging.log4j.message.ReusableMessage;
import org.apache.logging.log4j.message.SimpleMessage;
import org.apache.logging.log4j.message.TimestampMessage;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
import org.apache.logging.log4j.util.StackLocatorUtil;
import org.apache.logging.log4j.util.StringBuilders;
import org.apache.logging.log4j.util.StringMap;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
index 1ac3f5dd89..6f40131165 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
@@ -29,8 +29,8 @@ import org.apache.logging.log4j.core.time.Clock;
import org.apache.logging.log4j.core.time.NanoClock;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.plugins.Inject;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
/**
* Garbage-free LogEventFactory that recycles mutable {@link LogEvent} instances.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
index dceca48d88..569e3873f0 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
@@ -24,8 +24,8 @@ import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.impl.Log4jPropertyKey;
import org.apache.logging.log4j.core.impl.LogEventFactory;
import org.apache.logging.log4j.plugins.PluginElement;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.apache.logging.log4j.util.StringBuilders;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
index f2fe28baa5..09354f7902 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
@@ -36,7 +36,7 @@ import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
import org.apache.logging.log4j.plugins.PluginElement;
import org.apache.logging.log4j.plugins.PluginFactory;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.apache.logging.log4j.util.PropertyEnvironment;
import org.apache.logging.log4j.util.Strings;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java
index e61083552d..360dd56e06 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java
@@ -23,7 +23,7 @@ import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.util.Objects;
import org.apache.logging.log4j.core.util.Constants;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
/**
* {@link Encoder} for {@link StringBuilder}s.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java
index e2a3c74213..a60d6e4c9c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.util;
import org.apache.logging.log4j.spi.LoggingSystem;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
import org.apache.logging.log4j.util.Lazy;
/**
diff --git a/log4j-gctests/pom.xml b/log4j-gctests/pom.xml
index a461b5fff7..988808c891 100644
--- a/log4j-gctests/pom.xml
+++ b/log4j-gctests/pom.xml
@@ -99,12 +99,6 @@
<artifactId>jansi</artifactId>
<optional>true</optional>
</dependency>
- <!-- Alternative implementation of BlockingQueue using JCTools for AsyncAppender -->
- <dependency>
- <groupId>org.jctools</groupId>
- <artifactId>jctools-core</artifactId>
- <optional>true</optional>
- </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
diff --git a/log4j-api/pom.xml b/log4j-jctools/pom.xml
similarity index 59%
copy from log4j-api/pom.xml
copy to log4j-jctools/pom.xml
index 2ea9e66998..e5e8c01b76 100644
--- a/log4j-api/pom.xml
+++ b/log4j-jctools/pom.xml
@@ -15,44 +15,62 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
<modelVersion>4.0.0</modelVersion>
+
<parent>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j</artifactId>
<version>${revision}</version>
<relativePath>../log4j-parent</relativePath>
</parent>
- <artifactId>log4j-api</artifactId>
- <packaging>jar</packaging>
- <name>Apache Log4j API</name>
- <description>The Apache Log4j API</description>
+
+ <artifactId>log4j-jctools</artifactId>
+ <name>Apache Log4j JCTools-based supplements</name>
+ <description>Provides JCTools-based data structure implementations for the Apache Log4j.</description>
+
<properties>
- <maven.javadoc.skip>false</maven.javadoc.skip>
+
+ <log4jParentDir>${basedir}/..</log4jParentDir>
<!--
~ OSGi and JPMS options
-->
- <bnd-module-name>org.apache.logging.log4j</bnd-module-name>
- <bnd-extra-package-options>
- <!-- No JSR 305 at runtime -->
- !javax.annotation.*,
- <!-- Optional dependencies -->
- org.jctools.*;resolution:=optional
- </bnd-extra-package-options>
- <bnd-extra-module-options>
- <!-- No JSR 305 at runtime -->
- jsr305;ignore=true,
- <!-- Used in StringBuilders through reflection -->
- java.sql;static=true
- </bnd-extra-module-options>
+ <Fragment-Host>org.apache.logging.log4j.core</Fragment-Host>
</properties>
+
<dependencies>
+
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ </dependency>
+
<dependency>
<groupId>org.jctools</groupId>
<artifactId>jctools-core</artifactId>
- <optional>true</optional>
</dependency>
+
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+
</dependencies>
+
</project>
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/JCToolsBlockingQueueFactory.java b/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsBlockingQueueFactory.java
similarity index 95%
rename from log4j-core/src/main/java/org/apache/logging/log4j/core/async/JCToolsBlockingQueueFactory.java
rename to log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsBlockingQueueFactory.java
index 188d49bb5b..d35801cdd6 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/JCToolsBlockingQueueFactory.java
+++ b/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsBlockingQueueFactory.java
@@ -14,12 +14,13 @@
* 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.jctools;
import java.util.Collection;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
+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;
@@ -27,13 +28,13 @@ import org.apache.logging.log4j.plugins.PluginFactory;
import org.jctools.queues.MpscArrayQueue;
/**
- * Factory for creating instances of BlockingQueues backed by JCTools {@link MpscArrayQueue}.
+ * A {@link BlockingQueueFactory} based on <a href="https://jctools.github.io/JCTools/">JCTools</a>.
*
- * @since 2.7
+ * @since 3.0.0
*/
@Configurable(elementType = BlockingQueueFactory.ELEMENT_TYPE, printObject = true)
@Plugin("JCToolsBlockingQueue")
-public class JCToolsBlockingQueueFactory implements BlockingQueueFactory {
+public final class JCToolsBlockingQueueFactory implements BlockingQueueFactory {
private final WaitStrategy waitStrategy;
diff --git a/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java b/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java
new file mode 100644
index 0000000000..321b7e9983
--- /dev/null
+++ b/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java
@@ -0,0 +1,104 @@
+/*
+ * 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.jctools;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.logging.log4j.spi.recycler.Recycler.DEFAULT_CAPACITY;
+
+import aQute.bnd.annotation.spi.ServiceProvider;
+import java.util.Queue;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apache.logging.log4j.spi.LoggingSystemProperty;
+import org.apache.logging.log4j.spi.recycler.AbstractRecycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.util.PropertyEnvironment;
+import org.jctools.queues.MpmcArrayQueue;
+
+/**
+ * A {@link Recycler} factory provider implementation based on <a href="https://jctools.github.io/JCTools/">JCTools</a>.
+ *
+ * @since 3.0.0
+ */
+@ServiceProvider(RecyclerFactoryProvider.class)
+public final class JCToolsRecyclerFactoryProvider implements RecyclerFactoryProvider {
+
+ @Override
+ public int getOrder() {
+ return 600;
+ }
+
+ @Override
+ public String getName() {
+ return "jctools-mpmc";
+ }
+
+ @Override
+ public RecyclerFactory createForEnvironment(final PropertyEnvironment environment) {
+ requireNonNull(environment, "environment");
+ final int capacity = environment.getIntegerProperty(LoggingSystemProperty.RECYCLER_CAPACITY, DEFAULT_CAPACITY);
+ if (capacity < 1) {
+ throw new IllegalArgumentException("was expecting a `capacity` greater than 1, found: " + capacity);
+ }
+ return new JCToolsMpmcRecyclerFactory(capacity);
+ }
+
+ private static final class JCToolsMpmcRecyclerFactory implements RecyclerFactory {
+
+ private final int capacity;
+
+ private JCToolsMpmcRecyclerFactory(final int capacity) {
+ this.capacity = capacity;
+ }
+
+ @Override
+ public <V> Recycler<V> create(Supplier<V> supplier, Consumer<V> cleaner) {
+ requireNonNull(supplier, "supplier");
+ requireNonNull(cleaner, "cleaner");
+ final MpmcArrayQueue<V> queue = new MpmcArrayQueue<>(capacity);
+ return new JCToolsMpmcRecycler<>(supplier, cleaner, queue);
+ }
+
+ private static final class JCToolsMpmcRecycler<V> extends AbstractRecycler<V> {
+
+ private final Consumer<V> cleaner;
+
+ private final Queue<V> queue;
+
+ private JCToolsMpmcRecycler(final Supplier<V> supplier, final Consumer<V> cleaner, final Queue<V> 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-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsBlockingQueueFactoryTest.java b/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsBlockingQueueFactoryTest.java
new file mode 100644
index 0000000000..03e96f05ba
--- /dev/null
+++ b/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsBlockingQueueFactoryTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.jctools;
+
+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.util.List;
+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 JCToolsBlockingQueueFactoryTest {
+
+ 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<String> 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<String> messages;
+ try {
+ messages = appender.getMessages(2, 2, TimeUnit.SECONDS);
+ } finally {
+ appender.clear();
+ }
+ 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));
+ }
+
+ @Test
+ @LoggerContextSource("JCToolsBlockingQueueFactoryTest.xml")
+ public void testJcToolsBlockingQueue(final LoggerContext context) throws InterruptedException {
+ rewriteTest(context);
+ exceptionTest(context);
+ }
+}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunJCTools.java b/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
similarity index 50%
rename from log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunJCTools.java
rename to log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
index 58622a938a..51e9d9e272 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunJCTools.java
+++ b/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
@@ -14,16 +14,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.logging.log4j.core.async.perftest;
+package org.apache.logging.log4j.jctools;
-import java.util.concurrent.BlockingQueue;
-import org.apache.logging.log4j.core.async.JCToolsBlockingQueueFactory;
-import org.apache.logging.log4j.core.async.JCToolsBlockingQueueFactory.WaitStrategy;
+import static org.assertj.core.api.Assertions.assertThat;
-public class RunJCTools extends AbstractRunQueue {
+import java.util.Comparator;
+import java.util.List;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactoryRegistry;
+import org.junit.jupiter.api.Test;
- @Override
- BlockingQueue<String> createQueue(final int capacity) {
- return JCToolsBlockingQueueFactory.createFactory(WaitStrategy.SPIN).create(capacity);
+class JCToolsRecyclerFactoryProviderTest {
+
+ @Test
+ void verify_is_the_first() {
+ final List<Class<?>> providerClasses = RecyclerFactoryRegistry.getRecyclerFactoryProviders().stream()
+ .sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder))
+ .<Class<?>>map(RecyclerFactoryProvider::getClass)
+ .toList();
+ assertThat(providerClasses).startsWith(JCToolsRecyclerFactoryProvider.class);
}
}
diff --git a/log4j-core-test/src/test/resources/BlockingQueueFactory-JCToolsBlockingQueue.xml b/log4j-jctools/src/test/resources/JCToolsBlockingQueueFactoryTest.xml
similarity index 95%
rename from log4j-core-test/src/test/resources/BlockingQueueFactory-JCToolsBlockingQueue.xml
rename to log4j-jctools/src/test/resources/JCToolsBlockingQueueFactoryTest.xml
index c53500891b..1b6ff95acb 100644
--- a/log4j-core-test/src/test/resources/BlockingQueueFactory-JCToolsBlockingQueue.xml
+++ b/log4j-jctools/src/test/resources/JCToolsBlockingQueueFactoryTest.xml
@@ -15,7 +15,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<Configuration status="OFF" name="JCToolsBlockingQueueFactory">
+<Configuration status="OFF" name="JCToolsBlockingQueueFactoryTest">
<Appenders>
<Console name="STDOUT">
diff --git a/log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider b/log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider
new file mode 100644
index 0000000000..25c8b92149
--- /dev/null
+++ b/log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider
@@ -0,0 +1,6 @@
+# `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-layout-template-json-test/pom.xml b/log4j-layout-template-json-test/pom.xml
index 7a24826ad0..f0945107da 100644
--- a/log4j-layout-template-json-test/pom.xml
+++ b/log4j-layout-template-json-test/pom.xml
@@ -78,12 +78,6 @@
<artifactId>java-allocation-instrumenter</artifactId>
<scope>test</scope>
</dependency>
- <!-- needed for `RecyclerFactoriesTest` -->
- <dependency>
- <groupId>org.jctools</groupId>
- <artifactId>jctools-core</artifactId>
- <scope>test</scope>
- </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
diff --git a/log4j-layout-template-json-test/src/test/resources/recyclerFactoryCustomizedJsonTemplateLayoutLogging.xml b/log4j-layout-template-json-test/src/test/resources/recyclerFactoryCustomizedJsonTemplateLayoutLogging.xml
deleted file mode 100644
index 63936384cc..0000000000
--- a/log4j-layout-template-json-test/src/test/resources/recyclerFactoryCustomizedJsonTemplateLayoutLogging.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- ~ 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.
- -->
-<Configuration status="OFF">
- <Appenders>
- <List name="List" raw="true">
- <JsonTemplateLayout
- recyclerFactory="queue:supplier=org.jctools.queues.MpmcArrayQueue.new,capacity=512"/>
- </List>
- </Appenders>
- <Loggers>
- <Root level="TRACE">
- <AppenderRef ref="List"/>
- </Root>
- </Loggers>
-</Configuration>
diff --git a/log4j-layout-template-json/pom.xml b/log4j-layout-template-json/pom.xml
index 651512771e..6279900cd6 100644
--- a/log4j-layout-template-json/pom.xml
+++ b/log4j-layout-template-json/pom.xml
@@ -52,11 +52,6 @@
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-plugins</artifactId>
</dependency>
- <dependency>
- <groupId>org.jctools</groupId>
- <artifactId>jctools-core</artifactId>
- <optional>true</optional>
- </dependency>
</dependencies>
<build>
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
index 30a668b311..f8d92fd4d5 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
@@ -34,7 +34,7 @@ import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
import org.apache.logging.log4j.layout.template.json.util.Uris;
import org.apache.logging.log4j.plugins.*;
import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
import org.apache.logging.log4j.util.Strings;
@Configurable(elementType = Layout.ELEMENT_TYPE)
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
index eebc2e9f2b..77a80af09c 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
@@ -23,7 +23,7 @@ import java.util.concurrent.locks.LockSupport;
import java.util.function.Consumer;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
/**
* Resolves a number from an internal counter.
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
index 27db91b743..0f7fc723cc 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
@@ -21,7 +21,7 @@ import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.ParameterConsumer;
import org.apache.logging.log4j.message.ParameterVisitable;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
/**
* {@link Message} parameter (i.e., {@link Message#getParameters()}) resolver.
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
index f6da114660..64356bfe2e 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
@@ -22,8 +22,8 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
import org.apache.logging.log4j.util.ReadOnlyStringMap;
import org.apache.logging.log4j.util.TriConsumer;
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
index 451c190e97..8d505eff8f 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
@@ -25,8 +25,8 @@ import java.util.stream.Collectors;
import org.apache.logging.log4j.layout.template.json.util.CharSequencePointer;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
import org.apache.logging.log4j.layout.template.json.util.TruncatingBufferedPrintWriter;
-import org.apache.logging.log4j.spi.Recycler;
-import org.apache.logging.log4j.spi.RecyclerFactory;
+import org.apache.logging.log4j.spi.recycler.Recycler;
+import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
/**
* Exception stack trace to JSON string resolver used by {@link ExceptionResolver}.
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterFactory.java
index 403aeaa09d..58ad4360fd 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterFactory.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterFactory.java
@@ -23,11 +23,9 @@ import java.util.Map;
import java.util.UnknownFormatConversionException;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.internal.RecyclerFactories;
import org.apache.logging.log4j.plugins.Inject;
import org.apache.logging.log4j.plugins.Singleton;
import org.apache.logging.log4j.plugins.util.TypeUtil;
-import org.apache.logging.log4j.spi.RecyclerFactory;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.Cast;
import org.apache.logging.log4j.util.EnglishEnums;
@@ -60,7 +58,6 @@ public class TypeConverterFactory {
registerTypeAlias(Integer.class, Integer.TYPE);
registerTypeConverter(Long.class, Long::valueOf);
registerTypeAlias(Long.class, Long.TYPE);
- registerTypeConverter(RecyclerFactory.class, RecyclerFactories::ofSpec);
registerTypeConverter(Short.class, Short::valueOf);
registerTypeAlias(Short.class, Short.TYPE);
registerTypeConverter(String.class, s -> s);
diff --git a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java
index 64be7b447c..919cf2dee5 100644
--- a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java
+++ b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java
@@ -24,7 +24,7 @@ import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.spi.AbstractLogger;
import org.apache.logging.log4j.spi.LoggingSystem;
-import org.apache.logging.log4j.spi.Recycler;
+import org.apache.logging.log4j.spi.recycler.Recycler;
import org.slf4j.LoggerFactory;
import org.slf4j.MarkerFactory;
import org.slf4j.spi.LocationAwareLogger;
diff --git a/pom.xml b/pom.xml
index 3dcdfa03ce..92041a027c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -245,6 +245,7 @@
<module>log4j-gctests</module>
<module>log4j-iostreams</module>
<module>log4j-jcl</module>
+ <module>log4j-jctools</module>
<module>log4j-jdbc</module>
<module>log4j-jdbc-dbcp2</module>
<module>log4j-jndi</module>
@@ -410,6 +411,12 @@
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-jctools</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jdbc</artifactId>
diff --git a/src/site/asciidoc/manual/appenders.adoc b/src/site/asciidoc/manual/appenders.adoc
index c2127cf344..5256b8adab 100644
--- a/src/site/asciidoc/manual/appenders.adoc
+++ b/src/site/asciidoc/manual/appenders.adoc
@@ -192,6 +192,7 @@ attribute, `spinPolicy`, which corresponds to the `SpinPolicy` enum.
|JCToolsBlockingQueue |This uses
https://jctools.github.io/JCTools/[JCTools], specifically the MPSC
bounded lock-free queue.
+This implementation is provided by the `log4j-jctools` artifact.
|LinkedTransferQueue |This uses the new in Java 7 implementation
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/LinkedTransferQueue.html[`LinkedTransferQueue`].
diff --git a/src/site/asciidoc/runtime-dependencies.adoc b/src/site/asciidoc/runtime-dependencies.adoc
index e46761edc7..e98983a2a6 100644
--- a/src/site/asciidoc/runtime-dependencies.adoc
+++ b/src/site/asciidoc/runtime-dependencies.adoc
@@ -182,8 +182,6 @@ The module info definition may be modified in the future to export these only to
!
!requires static com.lmax.disruptor
!
-!requires static org.jctools.core
-!
!requires static org.osgi.framework
!
!requires static com.conversantmedia.disruptor
@@ -312,8 +310,6 @@ The module info definition may be modified in the future to export these only to
!
!requires org.apache.logging.log4j.core
!
-!requires org.jctools.core
-!
!===
|log4j-plugin-processor