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