This is an automated email from the ASF dual-hosted git repository. pkarwasz pushed a commit to branch feature/move-thread-context in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 366fa976ba0df7fe611c1bcbe5fd9763b22a61f4 Author: Piotr P. Karwasz <[email protected]> AuthorDate: Mon Jun 24 15:49:14 2024 +0200 Move `GarbageFreeSortedArrayThreadContextMap` to `log4j-core` --- .../log4j/test/spi/ThreadContextMapSuite.java | 139 ++++++++++++++++++++ .../log4j/spi/DefaultThreadContextMapTest.java | 146 ++++++--------------- .../logging/log4j/spi/ThreadContextMapTest.java | 70 ---------- .../org/apache/logging/log4j/spi/Provider.java | 8 +- .../async/AbstractAsyncThreadContextTestBase.java | 3 +- .../async/AsyncThreadContextGarbageFreeTest.java | 4 +- ...GarbageFreeSortedArrayThreadContextMapTest.java | 116 ++++++++++++++++ .../core/impl/ThreadContextDataInjectorTest.java | 2 +- .../GarbageFreeSortedArrayThreadContextMap.java | 7 +- .../logging/log4j/core/impl/Log4jProvider.java | 66 ++-------- .../GarbageFreeOpenHashMapThreadContextMap.java | 1 + 11 files changed, 314 insertions(+), 248 deletions(-) diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/spi/ThreadContextMapSuite.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/spi/ThreadContextMapSuite.java new file mode 100644 index 0000000000..23bb42d483 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/spi/ThreadContextMapSuite.java @@ -0,0 +1,139 @@ +/* + * 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.test.spi; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.apache.logging.log4j.spi.ThreadContextMap; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +/** + * Provides test cases to apply to all implementations of {@link ThreadContextMap}. + * @since 2.24.0 + */ +@Execution(ExecutionMode.CONCURRENT) +public abstract class ThreadContextMapSuite { + + private static final String KEY = "key"; + + /** + * Checks if the context map does not propagate to other threads by default. + */ + protected static void threadLocalNotInheritableByDefault(final ThreadContextMap contextMap) { + contextMap.put(KEY, "threadLocalNotInheritableByDefault"); + verifyThreadContextValueFromANewThread(contextMap, null); + } + + /** + * Checks if the context map can be configured to propagate to other threads. + */ + protected static void threadLocalInheritableIfConfigured(final ThreadContextMap contextMap) { + contextMap.put(KEY, "threadLocalInheritableIfConfigured"); + verifyThreadContextValueFromANewThread(contextMap, "threadLocalInheritableIfConfigured"); + } + + /** + * Checks basic put/remove pattern. + */ + protected static void singleValue(final ThreadContextMap contextMap) { + assertThat(contextMap.isEmpty()).as("Map is empty").isTrue(); + contextMap.put(KEY, "testPut"); + assertThat(contextMap.isEmpty()).as("Map is not empty").isFalse(); + assertThat(contextMap.containsKey(KEY)).as("Map key exists").isTrue(); + assertThat(contextMap.get(KEY)).as("Map contains expected value").isEqualTo("testPut"); + contextMap.remove(KEY); + assertThat(contextMap.isEmpty()).as("Map is empty").isTrue(); + } + + /** + * Checks mutable copy + */ + protected static void getCopyReturnsMutableCopy(final ThreadContextMap contextMap) { + contextMap.put(KEY, "testGetCopyReturnsMutableCopy"); + + final Map<String, String> copy = contextMap.getCopy(); + assertThat(copy).as("Copy contains same value").containsExactly(entry(KEY, "testGetCopyReturnsMutableCopy")); + + copy.put(KEY, "testGetCopyReturnsMutableCopy2"); + assertThat(contextMap.get(KEY)) + .as("Original map is not affected by changes in the copy") + .isEqualTo("testGetCopyReturnsMutableCopy"); + + contextMap.clear(); + assertThat(contextMap.isEmpty()).as("Original map is empty").isTrue(); + assertThat(copy) + .as("Copy is not affected by changes in the map.") + .containsExactly(entry(KEY, "testGetCopyReturnsMutableCopy2")); + } + + /** + * The immutable copy must be {@code null} if the map is empty. + */ + protected static void getImmutableMapReturnsNullIfEmpty(final ThreadContextMap contextMap) { + assertThat(contextMap.isEmpty()).as("Original map is empty").isTrue(); + assertThat(contextMap.getImmutableMapOrNull()) + .as("Immutable copy is null") + .isNull(); + } + + /** + * The result of {@link ThreadContextMap#getImmutableMapOrNull()} must be immutable. + */ + protected static void getImmutableMapReturnsImmutableMapIfNonEmpty(final ThreadContextMap contextMap) { + contextMap.put(KEY, "getImmutableMapReturnsImmutableMapIfNonEmpty"); + + final Map<String, String> immutable = contextMap.getImmutableMapOrNull(); + assertThat(immutable) + .as("Immutable copy contains same value") + .containsExactly(entry(KEY, "getImmutableMapReturnsImmutableMapIfNonEmpty")); + + assertThrows( + UnsupportedOperationException.class, () -> immutable.put(KEY, "getImmutableMapReturnsNullIfEmpty2")); + } + + /** + * The immutable copy is not affected by changes to the original map. + */ + protected static void getImmutableMapCopyNotAffectedByContextMapChanges(final ThreadContextMap contextMap) { + contextMap.put(KEY, "getImmutableMapCopyNotAffectedByContextMapChanges"); + + final Map<String, String> immutable = contextMap.getImmutableMapOrNull(); + contextMap.put(KEY, "getImmutableMapCopyNotAffectedByContextMapChanges2"); + assertThat(immutable) + .as("Immutable copy contains the original value") + .containsExactly(entry(KEY, "getImmutableMapCopyNotAffectedByContextMapChanges")); + } + + private static void verifyThreadContextValueFromANewThread( + final ThreadContextMap contextMap, final String expected) { + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + try { + assertThat(executorService.submit(() -> contextMap.get(KEY))) + .succeedsWithin(Duration.ofSeconds(1)) + .isEqualTo(expected); + } finally { + executorService.shutdown(); + } + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java index 243ce0e9fc..4e3356aad3 100644 --- a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java @@ -16,55 +16,43 @@ */ package org.apache.logging.log4j.spi; -import static org.junit.Assert.assertNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.HashMap; import java.util.Map; +import java.util.Properties; import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.apache.logging.log4j.test.spi.ThreadContextMapSuite; +import org.apache.logging.log4j.util.PropertiesUtil; import org.junit.jupiter.api.Test; /** * Tests the {@code DefaultThreadContextMap} class. */ @UsingThreadContextMap -public class DefaultThreadContextMapTest { +class DefaultThreadContextMapTest extends ThreadContextMapSuite { - @Test - public void testEqualsVsSameKind() { - final DefaultThreadContextMap map1 = createMap(); - final DefaultThreadContextMap map2 = createMap(); - assertEquals(map1, map1); - assertEquals(map2, map2); - assertEquals(map1, map2); - assertEquals(map2, map1); + private ThreadContextMap createThreadContextMap() { + return new DefaultThreadContextMap(); } - @Test - public void testHashCodeVsSameKind() { - final DefaultThreadContextMap map1 = createMap(); - final DefaultThreadContextMap map2 = createMap(); - assertEquals(map1.hashCode(), map2.hashCode()); + private ThreadContextMap createInheritableThreadContextMap() { + final Properties props = new Properties(); + props.setProperty("log4j2.isThreadContextMapInheritable", "true"); + final PropertiesUtil util = new PropertiesUtil(props); + return new DefaultThreadContextMap(util); } @Test - public void testPut() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); - assertTrue(map.isEmpty()); - assertFalse(map.containsKey("key")); - map.put("key", "value"); - - assertFalse(map.isEmpty()); - assertTrue(map.containsKey("key")); - assertEquals("value", map.get("key")); + void singleValue() { + singleValue(createThreadContextMap()); } @Test - public void testPutAll() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); + void testPutAll() { + final DefaultThreadContextMap map = new DefaultThreadContextMap(); assertTrue(map.isEmpty()); assertFalse(map.containsKey("key")); final int mapSize = 10; @@ -80,24 +68,8 @@ public class DefaultThreadContextMapTest { } } - /** - * Test method for - * {@link org.apache.logging.log4j.spi.DefaultThreadContextMap#remove(java.lang.String)} - * . - */ - @Test - public void testRemove() { - final DefaultThreadContextMap map = createMap(); - assertEquals("value", map.get("key")); - assertEquals("value2", map.get("key2")); - - map.remove("key"); - assertFalse(map.containsKey("key")); - assertEquals("value2", map.get("key2")); - } - @Test - public void testClear() { + void testClear() { final DefaultThreadContextMap map = createMap(); map.clear(); @@ -106,11 +78,8 @@ public class DefaultThreadContextMapTest { assertFalse(map.containsKey("key2")); } - /** - * @return - */ private DefaultThreadContextMap createMap() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); + final DefaultThreadContextMap map = new DefaultThreadContextMap(); assertTrue(map.isEmpty()); map.put("key", "value"); map.put("key2", "value2"); @@ -120,79 +89,28 @@ public class DefaultThreadContextMapTest { } @Test - public void testGetCopyReturnsMutableMap() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); - assertTrue(map.isEmpty()); - final Map<String, String> copy = map.getCopy(); - assertTrue(copy.isEmpty()); - - copy.put("key", "value"); // mutable - assertEquals("value", copy.get("key")); - - // thread context map not affected - assertTrue(map.isEmpty()); + void getCopyReturnsMutableCopy() { + getCopyReturnsMutableCopy(createThreadContextMap()); } @Test - public void testGetCopyReturnsMutableCopy() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); - map.put("key1", "value1"); - assertFalse(map.isEmpty()); - final Map<String, String> copy = map.getCopy(); - assertEquals("value1", copy.get("key1")); // copy has values too - - copy.put("key", "value"); // copy is mutable - assertEquals("value", copy.get("key")); - - // thread context map not affected - assertFalse(map.containsKey("key")); - - // clearing context map does not affect copy - map.clear(); - assertTrue(map.isEmpty()); - - assertFalse(copy.isEmpty()); + void getImmutableMapReturnsNullIfEmpty() { + getImmutableMapReturnsNullIfEmpty(createThreadContextMap()); } @Test - public void testGetImmutableMapReturnsNullIfEmpty() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); - assertTrue(map.isEmpty()); - assertNull(map.getImmutableMapOrNull()); + void getImmutableMapReturnsImmutableMapIfNonEmpty() { + getImmutableMapReturnsImmutableMapIfNonEmpty(createThreadContextMap()); } @Test - public void testGetImmutableMapReturnsImmutableMapIfNonEmpty() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); - map.put("key1", "value1"); - assertFalse(map.isEmpty()); - - final Map<String, String> immutable = map.getImmutableMapOrNull(); - assertEquals("value1", immutable.get("key1")); // copy has values too - - // immutable - assertThrows(UnsupportedOperationException.class, () -> immutable.put("key", "value")); + void getImmutableMapCopyNotAffectedByContextMapChanges() { + getImmutableMapCopyNotAffectedByContextMapChanges(createThreadContextMap()); } @Test - public void testGetImmutableMapCopyNotAffectdByContextMapChanges() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); - map.put("key1", "value1"); - assertFalse(map.isEmpty()); - - final Map<String, String> immutable = map.getImmutableMapOrNull(); - assertEquals("value1", immutable.get("key1")); // copy has values too - - // clearing context map does not affect copy - map.clear(); - assertTrue(map.isEmpty()); - - assertFalse(immutable.isEmpty()); - } - - @Test - public void testToStringShowsMapContext() { - final DefaultThreadContextMap map = new DefaultThreadContextMap(true); + void testToStringShowsMapContext() { + final DefaultThreadContextMap map = new DefaultThreadContextMap(); assertEquals("{}", map.toString()); map.put("key1", "value1"); @@ -202,4 +120,14 @@ public class DefaultThreadContextMapTest { map.put("key2", "value2"); assertEquals("{key2=value2}", map.toString()); } + + @Test + void threadLocalNotInheritableByDefault() { + threadLocalNotInheritableByDefault(createThreadContextMap()); + } + + @Test + void threadLocalInheritableIfConfigured() { + threadLocalInheritableIfConfigured(createInheritableThreadContextMap()); + } } diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadContextMapTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadContextMapTest.java deleted file mode 100644 index 2eb63dedf0..0000000000 --- a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadContextMapTest.java +++ /dev/null @@ -1,70 +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 org.assertj.core.api.Assertions.assertThat; - -import java.time.Duration; -import java.util.Properties; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.stream.Stream; -import org.apache.logging.log4j.util.PropertiesUtil; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -class ThreadContextMapTest { - - private static final String KEY = "key"; - - static Stream<ThreadContextMap> defaultMaps() { - return Stream.of(new DefaultThreadContextMap(), new GarbageFreeSortedArrayThreadContextMap()); - } - - static Stream<ThreadContextMap> inheritableMaps() { - final Properties props = new Properties(); - props.setProperty("log4j2.isThreadContextMapInheritable", "true"); - final PropertiesUtil util = new PropertiesUtil(props); - return Stream.of(new DefaultThreadContextMap(util), new GarbageFreeSortedArrayThreadContextMap(util)); - } - - @ParameterizedTest - @MethodSource("defaultMaps") - void threadLocalNotInheritableByDefault(final ThreadContextMap contextMap) { - contextMap.put(KEY, "threadLocalNotInheritableByDefault"); - verifyThreadContextValueFromANewThread(contextMap, null); - } - - @ParameterizedTest - @MethodSource("inheritableMaps") - void threadLocalInheritableIfConfigured(final ThreadContextMap contextMap) { - contextMap.put(KEY, "threadLocalInheritableIfConfigured"); - verifyThreadContextValueFromANewThread(contextMap, "threadLocalInheritableIfConfigured"); - } - - private static void verifyThreadContextValueFromANewThread( - final ThreadContextMap contextMap, final String expected) { - final ExecutorService executorService = Executors.newSingleThreadExecutor(); - try { - assertThat(executorService.submit(() -> contextMap.get(KEY))) - .succeedsWithin(Duration.ofSeconds(1)) - .isEqualTo(expected); - } finally { - executorService.shutdown(); - } - } -} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java index bad793160d..f29dfdbd41 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java @@ -291,12 +291,8 @@ public class Provider { */ public ThreadContextMap getThreadContextMapInstance() { final PropertiesUtil props = PropertiesUtil.getProperties(); - if (props.getBooleanProperty(DISABLE_CONTEXT_MAP) || props.getBooleanProperty(DISABLE_THREAD_CONTEXT)) { - return NoOpThreadContextMap.INSTANCE; - } - final String threadContextMap = getThreadContextMap(); - return "org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap".equals(threadContextMap) - ? new GarbageFreeSortedArrayThreadContextMap() + return props.getBooleanProperty(DISABLE_CONTEXT_MAP) || props.getBooleanProperty(DISABLE_THREAD_CONTEXT) + ? NoOpThreadContextMap.INSTANCE : new DefaultThreadContextMap(); } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AbstractAsyncThreadContextTestBase.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AbstractAsyncThreadContextTestBase.java index a1c3e7053c..572b2ca4d5 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AbstractAsyncThreadContextTestBase.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AbstractAsyncThreadContextTestBase.java @@ -92,7 +92,8 @@ public abstract class AbstractAsyncThreadContextTestBase { protected enum ContextImpl { WEBAPP("WebApp", "org.apache.logging.log4j.spi.DefaultThreadContextMap"), - GARBAGE_FREE("GarbageFree", "org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap"); + GARBAGE_FREE( + "GarbageFree", "org.apache.logging.log4j.core.context.internal.GarbageFreeSortedArrayThreadContextMap"); private final String threadContextMap; private final String implClass; diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextGarbageFreeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextGarbageFreeTest.java index 4ede816304..e029580b01 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextGarbageFreeTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextGarbageFreeTest.java @@ -27,14 +27,14 @@ import org.junit.jupiter.params.provider.EnumSource; // ThreadContext initialization will result in static final fields being set in various components. // To use a different ThreadContextMap, the test needs to be run in a new JVM. @Tag(Tags.ASYNC_LOGGERS) -public class AsyncThreadContextGarbageFreeTest extends AbstractAsyncThreadContextTestBase { +class AsyncThreadContextGarbageFreeTest extends AbstractAsyncThreadContextTestBase { @TempLoggingDir private static Path loggingPath; @ParameterizedTest @EnumSource - public void testAsyncLogWritesToLog(Mode asyncMode) throws Exception { + void testAsyncLogWritesToLog(Mode asyncMode) throws Exception { testAsyncLogWritesToLog(ContextImpl.GARBAGE_FREE, asyncMode, loggingPath); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/context/internal/GarbageFreeSortedArrayThreadContextMapTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/context/internal/GarbageFreeSortedArrayThreadContextMapTest.java new file mode 100644 index 0000000000..192c72e85c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/context/internal/GarbageFreeSortedArrayThreadContextMapTest.java @@ -0,0 +1,116 @@ +/* + * 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.core.context.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import org.apache.logging.log4j.spi.ThreadContextMap; +import org.apache.logging.log4j.test.spi.ThreadContextMapSuite; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.junit.jupiter.api.Test; + +public class GarbageFreeSortedArrayThreadContextMapTest extends ThreadContextMapSuite { + + private GarbageFreeSortedArrayThreadContextMap createThreadContextMap() { + return new GarbageFreeSortedArrayThreadContextMap(); + } + + private ThreadContextMap createInheritableThreadContextMap() { + final Properties props = new Properties(); + props.setProperty("log4j2.isThreadContextMapInheritable", "true"); + final PropertiesUtil util = new PropertiesUtil(props); + return new GarbageFreeSortedArrayThreadContextMap(util); + } + + @Test + void singleValue() { + singleValue(createThreadContextMap()); + } + + @Test + void testPutAll() { + final GarbageFreeSortedArrayThreadContextMap map = createThreadContextMap(); + assertTrue(map.isEmpty()); + assertFalse(map.containsKey("key")); + final int mapSize = 10; + final Map<String, String> newMap = new HashMap<>(mapSize); + for (int i = 1; i <= mapSize; i++) { + newMap.put("key" + i, "value" + i); + } + map.putAll(newMap); + assertFalse(map.isEmpty()); + for (int i = 1; i <= mapSize; i++) { + assertTrue(map.containsKey("key" + i)); + assertEquals("value" + i, map.get("key" + i)); + } + } + + @Test + void testClear() { + final GarbageFreeSortedArrayThreadContextMap map = createMap(); + + map.clear(); + assertTrue(map.isEmpty()); + assertFalse(map.containsKey("key")); + assertFalse(map.containsKey("key2")); + } + + private GarbageFreeSortedArrayThreadContextMap createMap() { + final GarbageFreeSortedArrayThreadContextMap map = createThreadContextMap(); + assertTrue(map.isEmpty()); + map.put("key", "value"); + map.put("key2", "value2"); + assertEquals("value", map.get("key")); + assertEquals("value2", map.get("key2")); + return map; + } + + @Test + void getCopyReturnsMutableCopy() { + getCopyReturnsMutableCopy(createThreadContextMap()); + } + + @Test + void getImmutableMapReturnsNullIfEmpty() { + getImmutableMapReturnsNullIfEmpty(createThreadContextMap()); + } + + @Test + void getImmutableMapReturnsImmutableMapIfNonEmpty() { + getImmutableMapReturnsImmutableMapIfNonEmpty(createThreadContextMap()); + } + + @Test + void getImmutableMapCopyNotAffectedByContextMapChanges() { + getImmutableMapCopyNotAffectedByContextMapChanges(createThreadContextMap()); + } + + @Test + void threadLocalNotInheritableByDefault() { + threadLocalNotInheritableByDefault(createThreadContextMap()); + } + + @Test + void threadLocalInheritableIfConfigured() { + threadLocalInheritableIfConfigured(createInheritableThreadContextMap()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java index 47034eb7bf..24e3e1c93f 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java @@ -50,7 +50,7 @@ public class ThreadContextDataInjectorTest { @Parameters(name = "{0}") public static Collection<String[]> threadContextMapClassNames() { return asList(new String[][] { - {"org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap"}, + {"org.apache.logging.log4j.core.context.internal.GarbageFreeSortedArrayThreadContextMap"}, {"org.apache.logging.log4j.spi.DefaultThreadContextMap"} }); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/context/internal/GarbageFreeSortedArrayThreadContextMap.java similarity index 95% rename from log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java rename to log4j-core/src/main/java/org/apache/logging/log4j/core/context/internal/GarbageFreeSortedArrayThreadContextMap.java index 1622a0c455..b9ee2be759 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/context/internal/GarbageFreeSortedArrayThreadContextMap.java @@ -14,12 +14,15 @@ * 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.core.context.internal; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import org.apache.logging.log4j.spi.ObjectThreadContextMap; +import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap; +import org.apache.logging.log4j.spi.ThreadContextMap; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.SortedArrayStringMap; @@ -34,7 +37,7 @@ import org.apache.logging.log4j.util.StringMap; * </p> * @since 2.7 */ -class GarbageFreeSortedArrayThreadContextMap implements ReadOnlyThreadContextMap, ObjectThreadContextMap { +public class GarbageFreeSortedArrayThreadContextMap implements ReadOnlyThreadContextMap, ObjectThreadContextMap { /** * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java index 222eafc858..10e06cb387 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java @@ -20,6 +20,7 @@ import aQute.bnd.annotation.Resolution; import aQute.bnd.annotation.spi.ServiceConsumer; import aQute.bnd.annotation.spi.ServiceProvider; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.context.internal.GarbageFreeSortedArrayThreadContextMap; import org.apache.logging.log4j.spi.DefaultThreadContextMap; import org.apache.logging.log4j.spi.LoggerContextFactory; import org.apache.logging.log4j.spi.NoOpThreadContextMap; @@ -27,12 +28,10 @@ import org.apache.logging.log4j.spi.Provider; import org.apache.logging.log4j.spi.ScopedContextProvider; import org.apache.logging.log4j.spi.ThreadContextMap; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.Constants; import org.apache.logging.log4j.util.Lazy; import org.apache.logging.log4j.util.LoaderUtil; import org.apache.logging.log4j.util.PropertiesUtil; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; /** * Binding for the Log4j API. @@ -63,15 +62,6 @@ public class Log4jProvider extends Provider { */ private static final String WEB_APP_CONTEXT_MAP = "WebApp"; - /** - * Constant used to select a copy-on-write implementation of {@link ThreadContextMap}. - * <p> - * <strong>Warning:</strong> the value of this constant does not point to a concrete class name. - * </p> - * @see #getThreadContextMap - */ - private static final String COPY_ON_WRITE_CONTEXT_MAP = "CopyOnWrite"; - /** * Constant used to select a garbage-free implementation of {@link ThreadContextMap}. * <p> @@ -94,9 +84,7 @@ public class Log4jProvider extends Provider { // Name of the context map implementations private static final String WEB_APP_CLASS_NAME = "org.apache.logging.log4j.spi.DefaultThreadContextMap"; private static final String GARBAGE_FREE_CLASS_NAME = - "org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap"; - private static final String COPY_ON_WRITE_CLASS_NAME = - "org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap"; + "org.apache.logging.log4j.core.context.internal.GarbageFreeSortedArrayThreadContextMap"; private static final Logger LOGGER = StatusLogger.getLogger(); @@ -126,13 +114,9 @@ public class Log4jProvider extends Provider { String threadContextMapClass = props.getStringProperty(THREAD_CONTEXT_MAP_PROPERTY); // Default based on properties if (threadContextMapClass == null) { - if (props.getBooleanProperty(GC_FREE_THREAD_CONTEXT_PROPERTY)) { - threadContextMapClass = GARBAGE_FREE_CONTEXT_MAP; - } else if (Constants.ENABLE_THREADLOCALS) { - threadContextMapClass = COPY_ON_WRITE_CONTEXT_MAP; - } else { - threadContextMapClass = WEB_APP_CONTEXT_MAP; - } + threadContextMapClass = props.getBooleanProperty(GC_FREE_THREAD_CONTEXT_PROPERTY) + ? GARBAGE_FREE_CONTEXT_MAP + : WEB_APP_CONTEXT_MAP; } /* * The constructors are called explicitly to improve GraalVM support. @@ -146,11 +130,11 @@ public class Log4jProvider extends Provider { case WEB_APP_CONTEXT_MAP: case WEB_APP_CLASS_NAME: return new DefaultThreadContextMap(); - case GARBAGE_FREE_CONTEXT_MAP: + // Old FQCN of the garbage-free context map case "org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap": - case COPY_ON_WRITE_CONTEXT_MAP: - case "org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap": - return super.getThreadContextMapInstance(); + case GARBAGE_FREE_CONTEXT_MAP: + case GARBAGE_FREE_CLASS_NAME: + return new GarbageFreeSortedArrayThreadContextMap(); default: try { return LoaderUtil.newCheckedInstanceOf(threadContextMapClass, ThreadContextMap.class); @@ -162,38 +146,6 @@ public class Log4jProvider extends Provider { return NoOpThreadContextMap.INSTANCE; } - @Override - public @Nullable String getThreadContextMap() { - // Properties - final PropertiesUtil props = PropertiesUtil.getProperties(); - if (props.getBooleanProperty(DISABLE_CONTEXT_MAP) || props.getBooleanProperty(DISABLE_THREAD_CONTEXT)) { - return NoOpThreadContextMap.class.getName(); - } - String threadContextMapClass = props.getStringProperty(THREAD_CONTEXT_MAP_PROPERTY); - // Default based on properties - if (threadContextMapClass == null) { - if (props.getBooleanProperty(GC_FREE_THREAD_CONTEXT_PROPERTY)) { - threadContextMapClass = GARBAGE_FREE_CONTEXT_MAP; - } else if (Constants.ENABLE_THREADLOCALS) { - threadContextMapClass = COPY_ON_WRITE_CONTEXT_MAP; - } else { - threadContextMapClass = WEB_APP_CONTEXT_MAP; - } - } - switch (threadContextMapClass) { - case NO_OP_CONTEXT_MAP: - return NoOpThreadContextMap.class.getName(); - case WEB_APP_CONTEXT_MAP: - return DefaultThreadContextMap.class.getName(); - case GARBAGE_FREE_CONTEXT_MAP: - return GARBAGE_FREE_CLASS_NAME; - case COPY_ON_WRITE_CONTEXT_MAP: - return COPY_ON_WRITE_CLASS_NAME; - default: - return threadContextMapClass; - } - } - // Used in tests void resetThreadContextMap() { threadContextMapLazy.set(null); diff --git a/log4j-perf-test/src/main/java/org/apache/logging/log4j/spi/GarbageFreeOpenHashMapThreadContextMap.java b/log4j-perf-test/src/main/java/org/apache/logging/log4j/spi/GarbageFreeOpenHashMapThreadContextMap.java index 45283a2fb0..a39ce88abc 100644 --- a/log4j-perf-test/src/main/java/org/apache/logging/log4j/spi/GarbageFreeOpenHashMapThreadContextMap.java +++ b/log4j-perf-test/src/main/java/org/apache/logging/log4j/spi/GarbageFreeOpenHashMapThreadContextMap.java @@ -16,6 +16,7 @@ */ package org.apache.logging.log4j.spi; +import org.apache.logging.log4j.core.context.internal.GarbageFreeSortedArrayThreadContextMap; import org.apache.logging.log4j.perf.nogc.OpenHashStringMap; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.ReadOnlyStringMap;
