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.core.spi; 15 16 import ch.qos.logback.core.Appender; 17 import org.junit.jupiter.api.Test; 18 import org.junit.jupiter.api.Timeout; 19 20 import java.util.concurrent.TimeUnit; 21 22 import static org.mockito.Mockito.mock; 23 import static org.mockito.Mockito.when; 24 25 /** 26 * This test shows the general problem I described in LOGBACK-102. 27 * 28 * In the two test cases below, an appender that throws an RuntimeException 29 * while getName is called - but this is just an example to show the general 30 * problem. 31 * 32 * The tests below fail without fixing LBCORE-67 and pass when Joern Huxhorn's 33 * patch is applied. 34 * 35 * Additionally, the following, probably more realistic, situations could 36 * happen: 37 * 38 * -addAppender: appenderList.add() could throw OutOfMemoryError. This could 39 * only be shown by using an appenderList mock but appenderList does not (and 40 * should not) have a setter. This would leave the write lock locked. 41 * 42 * -iteratorForAppenders: new ArrayList() could throw an OutOfMemoryError, 43 * leaving the read lock locked. 44 * 45 * I can't imagine a bad situation in isAttached, detachAppender(Appender) or 46 * detachAppender(String) but I'd change the code anyway for consistency. I'm 47 * also pretty sure that something stupid can happen at any time so it's best to 48 * just stick to conventions. 49 * 50 * @author Joern Huxhorn 51 */ 52 public class AppenderAttachableImplLockTest { 53 54 private AppenderAttachableImpl<Integer> aai = new AppenderAttachableImpl<Integer>(); 55 56 @SuppressWarnings("unchecked") 57 @Test 58 @Timeout(value=1, unit = TimeUnit.SECONDS) 59 public void getAppenderBoom() { 60 Appender<Integer> mockAppender1 = mock(Appender.class); 61 62 when(mockAppender1.getName()).thenThrow(new RuntimeException("oops")); 63 aai.addAppender(mockAppender1); 64 try { 65 // appender.getName called as a result of next statement 66 aai.getAppender("foo"); 67 } catch (RuntimeException e) { 68 // this leaves the read lock locked. 69 } 70 71 Appender<Integer> mockAppender2 = mock(Appender.class); 72 // the next call used to freeze with the earlier ReadWriteLock lock 73 // implementation 74 aai.addAppender(mockAppender2); 75 } 76 77 @SuppressWarnings("unchecked") 78 @Test 79 @Timeout(value=1, unit = TimeUnit.SECONDS) 80 public void detachAppenderBoom() throws InterruptedException { 81 Appender<Integer> mockAppender = mock(Appender.class); 82 when(mockAppender.getName()).thenThrow(new RuntimeException("oops")); 83 mockAppender.doAppend(17); 84 85 aai.addAppender(mockAppender); 86 Thread t = new Thread(new Runnable() { 87 88 public void run() { 89 try { 90 // appender.getName called as a result of next statement 91 aai.detachAppender("foo"); 92 } catch (RuntimeException e) { 93 System.out.println("Caught " + e.toString()); 94 // this leaves the write lock locked. 95 } 96 } 97 }); 98 t.start(); 99 t.join(); 100 101 // the next call used to freeze with the earlier ReadWriteLock lock 102 // implementation 103 aai.appendLoopOnAppenders(17); 104 } 105 }