View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.util;
15  
16  import static org.junit.Assert.assertEquals;
17  import static org.junit.Assert.assertNotNull;
18  import static org.junit.Assert.assertNull;
19  import static org.junit.Assert.assertSame;
20  import static org.junit.Assert.assertTrue;
21  
22  import java.util.HashMap;
23  import java.util.Map;
24  import java.util.concurrent.CountDownLatch;
25  
26  import org.junit.Test;
27  import ch.qos.logback.core.testUtil.RandomUtil;
28  
29  public class LogbackMDCAdapterTest {
30  
31      final static String A_SUFFIX = "A_SUFFIX";
32      final static String B_SUFFIX = "B_SUFFIX";
33  
34      int diff = RandomUtil.getPositiveInt();
35  
36      private final LogbackMDCAdapter mdcAdapter = new LogbackMDCAdapter();
37  
38      /**
39       * Test that CopyOnInheritThreadLocal does not barf when the
40       * MDC hashmap is null
41       *
42       * @throws InterruptedException
43       */
44      @Test
45      public void LOGBACK_442() throws InterruptedException {
46          Map<String, String> parentHM = getMapFromMDCAdapter(mdcAdapter);
47          assertNull(parentHM);
48  
49          ChildThreadForMDCAdapter childThread = new ChildThreadForMDCAdapter(mdcAdapter);
50          childThread.start();
51          childThread.join();
52          assertTrue(childThread.successul);
53          assertNull(childThread.childHM);
54      }
55  
56      @Test
57      public void removeForNullKeyTest() {
58          mdcAdapter.remove(null);
59      }
60  
61      @Test
62      public void removeInexistentKey() {
63          mdcAdapter.remove("abcdlw0");
64      }
65  
66      @Test
67      public void sequenceWithGet() {
68          mdcAdapter.put("k0", "v0");
69          Map<String, String> map0 = mdcAdapter.copyOnThreadLocal.get();
70          mdcAdapter.get("k0");
71          mdcAdapter.put("k1", "v1"); // no map copy required
72  
73          // verify that map0 is the same instance and that value was updated
74          assertSame(map0, mdcAdapter.copyOnThreadLocal.get());
75      }
76  
77      @Test
78      public void sequenceWithGetPropertyMap() {
79          mdcAdapter.put("k0", "v0");
80          Map<String, String> map0 = mdcAdapter.getPropertyMap(); // point 0
81          mdcAdapter.put("k0", "v1"); // new map should be created
82          // verify that map0 is that in point 0
83          assertEquals("v0", map0.get("k0"));
84      }
85  
86      @Test
87      public void sequenceWithCopyContextMap() {
88          mdcAdapter.put("k0", "v0");
89          Map<String, String> map0 = mdcAdapter.copyOnThreadLocal.get();
90          mdcAdapter.getCopyOfContextMap();
91          mdcAdapter.put("k1", "v1"); // no map copy required
92  
93          // verify that map0 is the same instance and that value was updated
94          assertSame(map0, mdcAdapter.copyOnThreadLocal.get());
95      }
96  
97      // =================================================
98  
99      /**
100      * Test that LogbackMDCAdapter does not copy its hashmap when a child
101      * thread inherits it.
102      *
103      * @throws InterruptedException
104      */
105     @Test
106     public void noCopyOnInheritenceTest() throws InterruptedException {
107         CountDownLatch countDownLatch = new CountDownLatch(1);
108         String firstKey = "x" + diff;
109         String secondKey = "o" + diff;
110         mdcAdapter.put(firstKey, firstKey + A_SUFFIX);
111 
112         ChildThread childThread = new ChildThread(mdcAdapter, firstKey, secondKey, countDownLatch);
113         childThread.start();
114         countDownLatch.await();
115         mdcAdapter.put(firstKey, firstKey + B_SUFFIX);
116         childThread.join();
117 
118         assertNull(mdcAdapter.get(secondKey));
119         assertTrue(childThread.successful);
120 
121         Map<String, String> parentHM = getMapFromMDCAdapter(mdcAdapter);
122         assertTrue(parentHM != childThread.childHM);
123 
124         HashMap<String, String> parentHMWitness = new HashMap<String, String>();
125         parentHMWitness.put(firstKey, firstKey + B_SUFFIX);
126         assertEquals(parentHMWitness, parentHM);
127 
128         HashMap<String, String> childHMWitness = new HashMap<String, String>();
129         childHMWitness.put(secondKey, secondKey + A_SUFFIX);
130         assertEquals(childHMWitness, childThread.childHM);
131 
132     }
133 
134     // see also http://jira.qos.ch/browse/LBCLASSIC-253
135     @Test
136     public void clearOnChildThreadShouldNotAffectParent() throws InterruptedException {
137         String firstKey = "x" + diff;
138         String secondKey = "o" + diff;
139 
140         mdcAdapter.put(firstKey, firstKey + A_SUFFIX);
141         assertEquals(firstKey + A_SUFFIX, mdcAdapter.get(firstKey));
142 
143         Thread clearer = new ChildThread(mdcAdapter, firstKey, secondKey) {
144             @Override
145             public void run() {
146                 mdcAdapter.clear();
147                 assertNull(mdcAdapter.get(firstKey));
148             }
149         };
150 
151         clearer.start();
152         clearer.join();
153 
154         assertEquals(firstKey + A_SUFFIX, mdcAdapter.get(firstKey));
155     }
156 
157     // see http://jira.qos.ch/browse/LBCLASSIC-289
158     // this test used to fail without synchronization code in LogbackMDCAdapter
159     @Test
160     public void nearSimultaneousPutsShouldNotCauseConcurrentModificationException() throws InterruptedException {
161         // For the weirdest reason, modifications to mdcAdapter must be done
162         // before the definition anonymous ChildThread class below. Otherwise, the
163         // map in the child thread, the one contained in mdcAdapter.copyOnInheritThreadLocal,
164         // is null. How strange is that?
165 
166         // let the map have lots of elements so that copying it takes time
167         for (int i = 0; i < 2048; i++) {
168             mdcAdapter.put("k" + i, "v" + i);
169         }
170 
171         ChildThread childThread = new ChildThread(mdcAdapter, null, null) {
172             @Override
173             public void run() {
174                 for (int i = 0; i < 16; i++) {
175                     mdcAdapter.put("ck" + i, "cv" + i);
176                     Thread.yield();
177                 }
178                 successful = true;
179             }
180         };
181 
182         childThread.start();
183         Thread.sleep(1);
184         for (int i = 0; i < 16; i++) {
185             mdcAdapter.put("K" + i, "V" + i);
186         }
187         childThread.join();
188         assertTrue(childThread.successful);
189     }
190 
191     Map<String, String> getMapFromMDCAdapter(LogbackMDCAdapter lma) {
192         ThreadLocal<Map<String, String>> copyOnThreadLocal = lma.copyOnThreadLocal;
193         return copyOnThreadLocal.get();
194     }
195 
196     // ========================== various thread classes
197     class ChildThreadForMDCAdapter extends Thread {
198 
199         LogbackMDCAdapter logbackMDCAdapter;
200         boolean successul;
201         Map<String, String> childHM;
202 
203         ChildThreadForMDCAdapter(LogbackMDCAdapter logbackMDCAdapter) {
204             this.logbackMDCAdapter = logbackMDCAdapter;
205         }
206 
207         @Override
208         public void run() {
209             childHM = getMapFromMDCAdapter(logbackMDCAdapter);
210             logbackMDCAdapter.get("");
211             successul = true;
212         }
213     }
214 
215     class ChildThread extends Thread {
216 
217         LogbackMDCAdapter logbackMDCAdapter;
218         String firstKey;
219         String secondKey;
220         boolean successful;
221         Map<String, String> childHM;
222         CountDownLatch countDownLatch;
223 
224         ChildThread(LogbackMDCAdapter logbackMDCAdapter) {
225             this(logbackMDCAdapter, null, null);
226         }
227 
228         ChildThread(LogbackMDCAdapter logbackMDCAdapter, String firstKey, String secondKey) {
229             this(logbackMDCAdapter, firstKey, secondKey, null);
230         }
231 
232         ChildThread(LogbackMDCAdapter logbackMDCAdapter, String firstKey, String secondKey, CountDownLatch countDownLatch) {
233             super("chil");
234             this.logbackMDCAdapter = logbackMDCAdapter;
235             this.firstKey = firstKey;
236             this.secondKey = secondKey;
237             this.countDownLatch = countDownLatch;
238         }
239 
240         @Override
241         public void run() {
242             logbackMDCAdapter.put(secondKey, secondKey + A_SUFFIX);
243             assertNull(logbackMDCAdapter.get(firstKey));
244             if (countDownLatch != null)
245                 countDownLatch.countDown();
246             assertNotNull(logbackMDCAdapter.get(secondKey));
247             assertEquals(secondKey + A_SUFFIX, logbackMDCAdapter.get(secondKey));
248 
249             successful = true;
250             childHM = getMapFromMDCAdapter(logbackMDCAdapter);
251         }
252     }
253 }