You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by pk...@apache.org on 2024/06/24 14:35:02 UTC
(logging-log4j2) 03/05: Move `GarbageFreeSortedArrayThreadContextMap` to `log4j-core`
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 <pi...@karwasz.org>
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;