This is an automated email from the ASF dual-hosted git repository. vy pushed a commit to branch api-queue in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit b37dcc14c3d6b674508c1f856f686818031f03d5 Author: Volkan Yazıcı <[email protected]> AuthorDate: Fri Dec 15 19:15:38 2023 +0100 Create `log4j-jctools` module with a JCTools-based recycler --- .../log4j/internal/RecyclerFactoriesTest.java | 102 ------ .../log4j/internal/StringParameterParserTest.java | 373 --------------------- .../internal/{ => recycler}/ArrayQueueTest.java | 2 +- .../recycler/RecyclerFactoriesTestUtil.java | 46 +++ .../recycler/RecyclerFactoryRegistryTest.java | 85 +++++ .../ThreadLocalRecyclerFactoryProviderTest.java} | 46 ++- .../logging/log4j/internal/DefaultLogBuilder.java | 4 +- .../logging/log4j/internal/QueueFactories.java | 143 -------- .../log4j/internal/QueueingRecyclerFactory.java | 92 ----- .../logging/log4j/internal/RecyclerFactories.java | 173 ---------- .../log4j/internal/StringParameterParser.java | 304 ----------------- .../log4j/internal/ThreadLocalRecyclerFactory.java | 101 ------ .../log4j/internal/{ => recycler}/ArrayQueue.java | 2 +- .../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 + .../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-api-queue-jctools => log4j-jctools}/pom.xml | 21 +- .../JCToolsMpmcRecyclerFactoryProvider.java | 106 ++++++ .../JCToolsMpmcRecyclerFactoryProviderTest.java | 32 +- ...ging.log4j.spi.recycler.RecyclerFactoryProvider | 6 + .../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 | 2 +- 57 files changed, 961 insertions(+), 1518 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/ArrayQueueTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ArrayQueueTest.java similarity index 98% rename from log4j-api-test/src/test/java/org/apache/logging/log4j/internal/ArrayQueueTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ArrayQueueTest.java index e063e7f81c..2ded8ff7a8 100644 --- a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/ArrayQueueTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ArrayQueueTest.java @@ -14,7 +14,7 @@ * 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.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoriesTestUtil.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoriesTestUtil.java new file mode 100644 index 0000000000..44422a3319 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoriesTestUtil.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 RecyclerFactoriesTestUtil { + + private RecyclerFactoriesTestUtil() {} + + @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/recycler/RecyclerFactoryRegistryTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoryRegistryTest.java new file mode 100644 index 0000000000..8793c1244e --- /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.RecyclerFactoriesTestUtil.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/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..7ccde99823 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.RecyclerFactoriesTestUtil.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/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 b0275c4506..0000000000 --- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/QueueFactories.java +++ /dev/null @@ -1,143 +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 org.apache.logging.log4j.util.LowLevelLogUtil.log; - -import aQute.bnd.annotation.spi.ServiceConsumer; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.List; -import java.util.Queue; -import java.util.ServiceLoader; -import java.util.concurrent.ArrayBlockingQueue; -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.apache.logging.log4j.util.ServiceLoaderUtil; - -/** - * Provides the default {@link QueueFactory} instance. - * - * @since 3.0.0 - */ -@InternalApi -@ServiceConsumer(QueueFactory.class) -public final class QueueFactories { - - /** - * The default {@link QueueFactory} instance. - */ - public static final QueueFactory INSTANCE = findInstance(); - - private static QueueFactory findInstance() { - final ServiceLoader<QueueFactory> serviceLoader = - ServiceLoader.load(QueueFactory.class, QueueFactory.class.getClassLoader()); - final List<QueueFactory> factories = - ServiceLoaderUtil.safeStream(serviceLoader).toList(); - if (factories.isEmpty()) { - return ArrayBlockingQueue::new; - } else { - final int factoryCount = factories.size(); - if (factoryCount > 1) { - log("Log4j was expecting a single `QueueFactory` provider, found:"); - for (int factoryIndex = 0; factoryIndex < factoryCount; factoryIndex++) { - log((factoryIndex + 1) + ". `" + factories.get(factoryIndex) + "`"); - } - log("Log4j will use the first `QueueFactory` provider as the default."); - } - return factories.get(0); - } - } - - private QueueFactories() {} - - /** - * 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 c7ec83a87a..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.INSTANCE, 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.INSTANCE; - 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 a05e9f3166..0000000000 --- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/ThreadLocalRecyclerFactory.java +++ /dev/null @@ -1,101 +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>> 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); - } - - // Visible for testing - Queue<V> getQueue() { - return queueRef.get(); - } - } -} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/ArrayQueue.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ArrayQueue.java similarity index 98% rename from log4j-api/src/main/java/org/apache/logging/log4j/internal/ArrayQueue.java rename to log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ArrayQueue.java index 238efa8796..90c573b364 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/ArrayQueue.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ArrayQueue.java @@ -14,7 +14,7 @@ * 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 java.util.AbstractQueue; import java.util.Iterator; 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..094ac880cd --- /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("2.20.1") +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 d5134593ef..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.INSTANCE.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/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-api-queue-jctools/pom.xml b/log4j-jctools/pom.xml similarity index 76% rename from log4j-api-queue-jctools/pom.xml rename to log4j-jctools/pom.xml index 18dd10d906..a66acc8017 100644 --- a/log4j-api-queue-jctools/pom.xml +++ b/log4j-jctools/pom.xml @@ -26,9 +26,9 @@ <relativePath>../log4j-parent</relativePath> </parent> - <artifactId>log4j-api-queue-jctools</artifactId> - <name>Apache Log4j API queue provider (JCTools)</name> - <description>Provides JCTools-based queue implementations for 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> @@ -37,7 +37,6 @@ <!-- ~ OSGi and JPMS options --> - <bnd-module-name>org.apache.logging.log4j.api.queue.jctools</bnd-module-name> <Fragment-Host>org.apache.logging.log4j.core</Fragment-Host> </properties> @@ -46,7 +45,7 @@ <dependency> <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-api</artifactId> + <artifactId>log4j-core</artifactId> </dependency> <dependency> @@ -54,6 +53,18 @@ <artifactId>jctools-core</artifactId> </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-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsMpmcRecyclerFactoryProvider.java b/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsMpmcRecyclerFactoryProvider.java new file mode 100644 index 0000000000..3a8fe11305 --- /dev/null +++ b/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsMpmcRecyclerFactoryProvider.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.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 multi-producer-multi-consumer, thread-safe {@link Recycler} factory provider implementation based on <a href="https://jctools.github.io/JCTools/">JCTools</a>. + */ +@ServiceProvider(RecyclerFactoryProvider.class) +public final class JCToolsMpmcRecyclerFactoryProvider 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); + } + + // Visible for testing + static final class JCToolsMpmcRecyclerFactory implements RecyclerFactory { + + // Visible for testing + 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); + } + + // Visible for testing + static final class JCToolsMpmcRecycler<V> extends AbstractRecycler<V> { + + private final Consumer<V> cleaner; + + // Visible for testing + 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-api-queue-jctools/src/main/java/org/apache/logging/log4j/api/queue/JCToolsQueueFactory.java b/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsMpmcRecyclerFactoryProviderTest.java similarity index 50% rename from log4j-api-queue-jctools/src/main/java/org/apache/logging/log4j/api/queue/JCToolsQueueFactory.java rename to log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsMpmcRecyclerFactoryProviderTest.java index 57a39e31f8..b936c14309 100644 --- a/log4j-api-queue-jctools/src/main/java/org/apache/logging/log4j/api/queue/JCToolsQueueFactory.java +++ b/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsMpmcRecyclerFactoryProviderTest.java @@ -14,24 +14,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.api.queue; +package org.apache.logging.log4j.jctools; -import aQute.bnd.annotation.spi.ServiceProvider; -import java.util.Queue; -import org.apache.logging.log4j.spi.QueueFactory; -import org.jctools.queues.MpmcArrayQueue; +import static org.assertj.core.api.Assertions.assertThat; -/** - * A multi-producer-multi-consumer, thread-safe {@link QueueFactory} implementation based on <a href="https://jctools.github.io/JCTools/">JCTools</a>. - */ -@ServiceProvider(QueueFactory.class) -public final class JCToolsQueueFactory implements QueueFactory { +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; + +class JCToolsMpmcRecyclerFactoryProviderTest { - @Override - public <E> Queue<E> create(final int capacity) { - if (capacity < 1) { - throw new IllegalArgumentException("invalid capacity: " + capacity); - } - return new MpmcArrayQueue<>(capacity); + @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(JCToolsMpmcRecyclerFactoryProvider.class); } } 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..36741a8bb5 --- /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.JCToolsMpmcRecyclerFactoryProvider 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 8862927529..2344d4c60f 100644 --- a/pom.xml +++ b/pom.xml @@ -234,7 +234,6 @@ Note that modules here must have a corresponding entry in `dependencyManagement > dependencies` block below! --> <module>log4j-1.2-api</module> <module>log4j-api</module> - <module>log4j-api-queue-jctools</module> <module>log4j-api-test</module> <module>log4j-appserver</module> <module>log4j-core</module> @@ -246,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>
