001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic.util;
015
016import static org.junit.Assert.assertEquals;
017import static org.junit.Assert.assertNotNull;
018import static org.junit.Assert.assertNull;
019import static org.junit.Assert.assertSame;
020import static org.junit.Assert.assertTrue;
021
022import java.util.HashMap;
023import java.util.Map;
024import java.util.concurrent.CountDownLatch;
025
026import org.junit.Test;
027import ch.qos.logback.core.testUtil.RandomUtil;
028
029public class LogbackMDCAdapterTest {
030
031    final static String A_SUFFIX = "A_SUFFIX";
032    final static String B_SUFFIX = "B_SUFFIX";
033
034    int diff = RandomUtil.getPositiveInt();
035
036    private final LogbackMDCAdapter mdcAdapter = new LogbackMDCAdapter();
037
038    /**
039     * Test that CopyOnInheritThreadLocal does not barf when the
040     * MDC hashmap is null
041     *
042     * @throws InterruptedException
043     */
044    @Test
045    public void LOGBACK_442() throws InterruptedException {
046        Map<String, String> parentHM = getMapFromMDCAdapter(mdcAdapter);
047        assertNull(parentHM);
048
049        ChildThreadForMDCAdapter childThread = new ChildThreadForMDCAdapter(mdcAdapter);
050        childThread.start();
051        childThread.join();
052        assertTrue(childThread.successul);
053        assertNull(childThread.childHM);
054    }
055
056    @Test
057    public void removeForNullKeyTest() {
058        mdcAdapter.remove(null);
059    }
060
061    @Test
062    public void removeInexistentKey() {
063        mdcAdapter.remove("abcdlw0");
064    }
065
066    @Test
067    public void sequenceWithGet() {
068        mdcAdapter.put("k0", "v0");
069        Map<String, String> map0 = mdcAdapter.copyOnThreadLocal.get();
070        mdcAdapter.get("k0");
071        mdcAdapter.put("k1", "v1"); // no map copy required
072
073        // verify that map0 is the same instance and that value was updated
074        assertSame(map0, mdcAdapter.copyOnThreadLocal.get());
075    }
076
077    @Test
078    public void sequenceWithGetPropertyMap() {
079        mdcAdapter.put("k0", "v0");
080        Map<String, String> map0 = mdcAdapter.getPropertyMap(); // point 0
081        mdcAdapter.put("k0", "v1"); // new map should be created
082        // verify that map0 is that in point 0
083        assertEquals("v0", map0.get("k0"));
084    }
085
086    @Test
087    public void sequenceWithCopyContextMap() {
088        mdcAdapter.put("k0", "v0");
089        Map<String, String> map0 = mdcAdapter.copyOnThreadLocal.get();
090        mdcAdapter.getCopyOfContextMap();
091        mdcAdapter.put("k1", "v1"); // no map copy required
092
093        // verify that map0 is the same instance and that value was updated
094        assertSame(map0, mdcAdapter.copyOnThreadLocal.get());
095    }
096
097    // =================================================
098
099    /**
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}