Author: rpopma
Date: Sun Jan 12 16:14:09 2014
New Revision: 1557551
URL: http://svn.apache.org/r1557551
Log:
LOG4J2-479: ThreadContext now uses plain ThreadLocal by default, can be
configured to use InheritableThreadLocal
Added:
logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java
(with props)
Modified:
logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java
logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java
logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java
logging/log4j/log4j2/trunk/src/changes/changes.xml
logging/log4j/log4j2/trunk/src/site/xdoc/manual/thread-context.xml
Modified:
logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java?rev=1557551&r1=1557550&r2=1557551&view=diff
==============================================================================
---
logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java
(original)
+++
logging/log4j/log4j2/trunk/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java
Sun Jan 12 16:14:09 2014
@@ -20,6 +20,8 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import org.apache.logging.log4j.util.PropertiesUtil;
+
/**
* The actual ThreadContext Map. A new ThreadContext Map is created each time
it is updated and the Map stored
* is always immutable. This means the Map can be passed to other threads
without concern that it will be updated.
@@ -27,20 +29,37 @@ import java.util.Map;
* the performance should be much better than if the Map was copied for each
event.
*/
public class DefaultThreadContextMap implements ThreadContextMap {
+ /**
+ * Property name ({@value}) for selecting {@code InheritableThreadLocal}
(value "true")
+ * or plain {@code ThreadLocal} (value is not "true") in the
implementation.
+ */
+ public static final String INHERITABLE_MAP =
"isThreadContextMapInheritable";
private final boolean useMap;
-
- private final ThreadLocal<Map<String, String>> localMap =
- new InheritableThreadLocal<Map<String, String>>() {
- @Override
- protected Map<String, String> childValue(final Map<String, String>
parentValue) {
- return parentValue == null || !useMap ? null :
- Collections.unmodifiableMap(new HashMap<String,
String>(parentValue));
- }
- };
+ private final ThreadLocal<Map<String, String>> localMap;
public DefaultThreadContextMap(final boolean useMap) {
this.useMap = useMap;
+ this.localMap = createThreadLocalMap(useMap);
+ }
+
+ // LOG4J2-479: by default, use a plain ThreadLocal, only use
InheritableThreadLocal if configured.
+ // (This method is package protected for JUnit tests.)
+ static ThreadLocal<Map<String, String>> createThreadLocalMap(final boolean
isMapEnabled) {
+ final PropertiesUtil managerProps = PropertiesUtil.getProperties();
+ final boolean inheritable =
managerProps.getBooleanProperty(INHERITABLE_MAP);
+ if (inheritable) {
+ return new InheritableThreadLocal<Map<String, String>>() {
+ @Override
+ protected Map<String, String> childValue(final Map<String,
String> parentValue) {
+ return (parentValue != null && isMapEnabled) //
+ ? Collections.unmodifiableMap(new HashMap<String,
String>(parentValue)) //
+ : null;
+ }
+ };
+ }
+ // if not inheritable, return plain ThreadLocal with null as initial
value
+ return new ThreadLocal<Map<String, String>>();
}
/**
Added:
logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java?rev=1557551&view=auto
==============================================================================
---
logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java
(added)
+++
logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java
Sun Jan 12 16:14:09 2014
@@ -0,0 +1,212 @@
+/*
+ * 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;
+
+import org.apache.logging.log4j.spi.DefaultThreadContextMap;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class ThreadContextInheritanceTest {
+ @BeforeClass
+ public static void setupClass() {
+ System.setProperty(DefaultThreadContextMap.INHERITABLE_MAP, "true");
+ }
+
+ @AfterClass
+ public static void tearDownClass() {
+ System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP);
+ }
+
+ @Test
+ public void testPush() {
+ ThreadContext.push("Hello");
+ ThreadContext.push("{} is {}",
ThreadContextInheritanceTest.class.getSimpleName(),
+ "running");
+ assertEquals("Incorrect parameterized stack value",
+ ThreadContext.pop(), "ThreadContextInheritanceTest is
running");
+ assertEquals("Incorrect simple stack value", ThreadContext.pop(),
+ "Hello");
+ }
+
+ @Test
+ public void testInheritanceSwitchedOn() throws Exception {
+ System.setProperty(DefaultThreadContextMap.INHERITABLE_MAP, "true");
+ try {
+ ThreadContext.clear();
+ ThreadContext.put("Greeting", "Hello");
+ StringBuilder sb = new StringBuilder();
+ TestThread thread = new TestThread(sb);
+ thread.start();
+ thread.join();
+ String str = sb.toString();
+ assertTrue("Unexpected ThreadContext value. Expected Hello. Actual
"
+ + str, "Hello".equals(str));
+ sb = new StringBuilder();
+ thread = new TestThread(sb);
+ thread.start();
+ thread.join();
+ str = sb.toString();
+ assertTrue("Unexpected ThreadContext value. Expected Hello. Actual
"
+ + str, "Hello".equals(str));
+ } finally {
+ System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP);
+ }
+ }
+
+ @Test
+ public void perfTest() throws Exception {
+ ThreadContext.clear();
+ final Timer complete = new Timer("ThreadContextTest");
+ complete.start();
+ ThreadContext.put("Var1", "value 1");
+ ThreadContext.put("Var2", "value 2");
+ ThreadContext.put("Var3", "value 3");
+ ThreadContext.put("Var4", "value 4");
+ ThreadContext.put("Var5", "value 5");
+ ThreadContext.put("Var6", "value 6");
+ ThreadContext.put("Var7", "value 7");
+ ThreadContext.put("Var8", "value 8");
+ ThreadContext.put("Var9", "value 9");
+ ThreadContext.put("Var10", "value 10");
+ final int loopCount = 1000000;
+ final Timer timer = new Timer("ThreadContextCopy", loopCount);
+ timer.start();
+ for (int i = 0; i < loopCount; ++i) {
+ final Map<String, String> map =
ThreadContext.getImmutableContext();
+ assertNotNull(map);
+ }
+ timer.stop();
+ complete.stop();
+ System.out.println(timer.toString());
+ System.out.println(complete.toString());
+ }
+
+ @Test
+ public void testGetContextReturnsEmptyMapIfEmpty() {
+ ThreadContext.clear();
+ assertTrue(ThreadContext.getContext().isEmpty());
+ }
+
+ @Test
+ public void testGetContextReturnsMutableCopy() {
+ ThreadContext.clear();
+ final Map<String, String> map1 = ThreadContext.getContext();
+ assertTrue(map1.isEmpty());
+ map1.put("K", "val"); // no error
+ assertEquals("val", map1.get("K"));
+
+ // adding to copy does not affect thread context map
+ assertTrue(ThreadContext.getContext().isEmpty());
+
+ ThreadContext.put("key", "val2");
+ final Map<String, String> map2 = ThreadContext.getContext();
+ assertEquals(1, map2.size());
+ assertEquals("val2", map2.get("key"));
+ map2.put("K", "val"); // no error
+ assertEquals("val", map2.get("K"));
+
+ // first copy is not affected
+ assertNotSame(map1, map2);
+ assertEquals(1, map1.size());
+ }
+
+ @Test
+ public void testGetImmutableContextReturnsEmptyMapIfEmpty() {
+ ThreadContext.clear();
+ assertTrue(ThreadContext.getImmutableContext().isEmpty());
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testGetImmutableContextReturnsImmutableMapIfNonEmpty() {
+ ThreadContext.clear();
+ ThreadContext.put("key", "val");
+ final Map<String, String> immutable =
ThreadContext.getImmutableContext();
+ immutable.put("otherkey", "otherval");
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testGetImmutableContextReturnsImmutableMapIfEmpty() {
+ ThreadContext.clear();
+ final Map<String, String> immutable =
ThreadContext.getImmutableContext();
+ immutable.put("otherkey", "otherval");
+ }
+
+ @Test
+ public void testGetImmutableStackReturnsEmptyStackIfEmpty() {
+ ThreadContext.clearStack();
+ assertTrue(ThreadContext.getImmutableStack().asList().isEmpty());
+ }
+
+ @Test
+ public void testPut() {
+ ThreadContext.clear();
+ assertNull(ThreadContext.get("testKey"));
+ ThreadContext.put("testKey", "testValue");
+ assertEquals("testValue", ThreadContext.get("testKey"));
+ }
+
+ @Test
+ public void testRemove() {
+ ThreadContext.clear();
+ assertNull(ThreadContext.get("testKey"));
+ ThreadContext.put("testKey", "testValue");
+ assertEquals("testValue", ThreadContext.get("testKey"));
+
+ ThreadContext.remove("testKey");
+ assertNull(ThreadContext.get("testKey"));
+ assertTrue(ThreadContext.isEmpty());
+ }
+
+ @Test
+ public void testContainsKey() {
+ ThreadContext.clear();
+ assertFalse(ThreadContext.containsKey("testKey"));
+ ThreadContext.put("testKey", "testValue");
+ assertTrue(ThreadContext.containsKey("testKey"));
+
+ ThreadContext.remove("testKey");
+ assertFalse(ThreadContext.containsKey("testKey"));
+ }
+
+ private class TestThread extends Thread {
+
+ private final StringBuilder sb;
+
+ public TestThread(final StringBuilder sb) {
+ this.sb = sb;
+ }
+
+ @Override
+ public void run() {
+ final String greeting = ThreadContext.get("Greeting");
+ if (greeting == null) {
+ sb.append("null");
+ } else {
+ sb.append(greeting);
+ }
+ ThreadContext.clear();
+ }
+ }
+}
Propchange:
logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified:
logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java?rev=1557551&r1=1557550&r2=1557551&view=diff
==============================================================================
---
logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java
(original)
+++
logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java
Sun Jan 12 16:14:09 2014
@@ -16,11 +16,11 @@
*/
package org.apache.logging.log4j;
-import org.junit.Test;
+import static org.junit.Assert.*;
import java.util.Map;
-import static org.junit.Assert.*;
+import org.junit.Test;
/**
*
@@ -39,7 +39,7 @@ public class ThreadContextTest {
}
@Test
- public void testInheritance() throws Exception {
+ public void testInheritanceSwitchedOffByDefault() throws Exception {
ThreadContext.clear();
ThreadContext.put("Greeting", "Hello");
StringBuilder sb = new StringBuilder();
@@ -47,15 +47,15 @@ public class ThreadContextTest {
thread.start();
thread.join();
String str = sb.toString();
- assertTrue("Unexpected ThreadContext value. Expected Hello. Actual "
- + str, "Hello".equals(str));
+ assertTrue("Unexpected ThreadContext value. Expected null. Actual "
+ + str, "null".equals(str));
sb = new StringBuilder();
thread = new TestThread(sb);
thread.start();
thread.join();
str = sb.toString();
- assertTrue("Unexpected ThreadContext value. Expected Hello. Actual "
- + str, "Hello".equals(str));
+ assertTrue("Unexpected ThreadContext value. Expected null. Actual "
+ + str, "null".equals(str));
}
@Test
Modified:
logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java?rev=1557551&r1=1557550&r2=1557551&view=diff
==============================================================================
---
logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java
(original)
+++
logging/log4j/log4j2/trunk/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java
Sun Jan 12 16:14:09 2014
@@ -168,4 +168,22 @@ public class DefaultThreadContextMapTest
map.put("key2", "value2");
assertEquals("{key2=value2}", map.toString());
}
+
+ @Test
+ public void testThreadLocalNotInheritableByDefault() {
+ System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP);
+ ThreadLocal<Map<String, String>> threadLocal =
DefaultThreadContextMap.createThreadLocalMap(true);
+ assertFalse(threadLocal instanceof InheritableThreadLocal<?>);
+ }
+
+ @Test
+ public void testThreadLocalInheritableIfConfigured() {
+ System.setProperty(DefaultThreadContextMap.INHERITABLE_MAP, "true");
+ try {
+ ThreadLocal<Map<String, String>> threadLocal =
DefaultThreadContextMap.createThreadLocalMap(true);
+ assertTrue(threadLocal instanceof InheritableThreadLocal<?>);
+ } finally {
+ System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP);
+ }
+ }
}
Modified: logging/log4j/log4j2/trunk/src/changes/changes.xml
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/src/changes/changes.xml?rev=1557551&r1=1557550&r2=1557551&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/src/changes/changes.xml (original)
+++ logging/log4j/log4j2/trunk/src/changes/changes.xml Sun Jan 12 16:14:09 2014
@@ -21,10 +21,14 @@
</properties>
<body>
<release version="2.0-RC1" date="2013-MM-DD" description="Bug fixes and
enhancements">
+ <action issue="LOG4J2-479" dev="rpopma" type="add" due-to="MK">
+ ThreadContext now uses plain ThreadLocal by default, unless system
property
+ <tt>isThreadContextMapInheritable</tt> has value <tt>"true"</tt>.
+ </action>
<action issue="LOG4J2-398" dev="rgoers" type="fix">
Configure properties and setup Interpolator before processing rest of
configuration.
</action>
- <action issue="LOG4J2-481" dev="rgoers" type="update" due-to="Matt
Sicker">
+ <action issue="LOG4J2-481" dev="rgoers" type="add" due-to="Matt Sicker">
Add Stream interface to Loggers.
</action>
<action issue="LOG4J2-490" dev="rgoers" type="update" due-to="Matt
Sicker">
Modified: logging/log4j/log4j2/trunk/src/site/xdoc/manual/thread-context.xml
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/src/site/xdoc/manual/thread-context.xml?rev=1557551&r1=1557550&r2=1557551&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/src/site/xdoc/manual/thread-context.xml
(original)
+++ logging/log4j/log4j2/trunk/src/site/xdoc/manual/thread-context.xml Sun Jan
12 16:14:09 2014
@@ -95,9 +95,12 @@ logger.debug("Message 2");
.
.
ThreadContext.clear();</pre>
- <p>The Stack and the Map are managed per thread and is based on
- <a
href="http://docs.oracle.com/javase/6/docs/api/java/lang/InheritableThreadLocal.html">InheritableThreadLocal</a>.
- Thus, in many cases the contents of the Stack and Map will be
passed to child threads. However, as
+ <p>The Stack and the Map are managed per thread and are based on
+ <a
href="http://docs.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html">ThreadLocal</a>
+ by default. The Map can be configured to use an
+ <a
href="http://docs.oracle.com/javase/6/docs/api/java/lang/InheritableThreadLocal.html">InheritableThreadLocal</a>
+ by setting system property <tt>isThreadContextMapInheritable</tt>
to <tt>"true"</tt>.
+ When configured this way, the contents of the Map will be passed
to child threads. However, as
discussed in the
<a
href="http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/Executors.html#privilegedThreadFactory()">Executors</a>
class and in other cases where thread pooling is utilized, the
ThreadContext may not always be