1   package ch.qos.logback.core.spi;
2   
3   import static org.easymock.EasyMock.createStrictMock;
4   import static org.easymock.EasyMock.expect;
5   import static org.easymock.EasyMock.makeThreadSafe;
6   import static org.easymock.EasyMock.replay;
7   import static org.easymock.EasyMock.verify;
8   
9   import org.junit.Test;
10  
11  import ch.qos.logback.core.Appender;
12  
13  /**
14   * This test shows the general problem I described in LBCORE-67.
15   * 
16   * In the two test cases below, an appender that throws an OutOfMemoryError
17   * while getName is called - but this is just an example to show the general
18   * problem.
19   * 
20   * The tests below fail without fixing LBCORE-67 and pass when Joern Huxhorn's
21   * patch is applied.
22   * 
23   * Additionally, the following, probably more realistic, situations could
24   * happen:
25   * 
26   * -addAppender: appenderList.add() could throw OutOfMemoryError. This could
27   * only be shown by using an appenderList mock but appenderList does not (and
28   * should not) have a setter. This would leave the write lock locked.
29   * 
30   * -iteratorForAppenders: new ArrayList() could throw an OutOfMemoryError,
31   * leaving the read lock locked.
32   * 
33   * I can't imagine a bad situation in isAttached, detachAppender(Appender) or
34   * detachAppender(String) but I'd change the code anyway for consistency. I'm
35   * also pretty sure that something stupid can happen at any time so it's best to
36   * just stick to conventions.
37   * 
38   * @author Joern Huxhorn
39   */
40  public class AppenderAttachableImplLockTest {
41    private AppenderAttachableImpl<Integer> aai = new AppenderAttachableImpl<Integer>();
42  
43    @SuppressWarnings("unchecked")
44    @Test(timeout = 1000)
45    public void getAppenderBoom() {
46  
47      Appender<Integer> mockAppender1 = createStrictMock(Appender.class);
48      expect(mockAppender1.getName()).andThrow(new OutOfMemoryError("oops"));
49      replay(mockAppender1);
50  
51      aai.addAppender(mockAppender1);
52      try {
53        // appender.getName called as a result of next statement
54        aai.getAppender("foo");
55      } catch (OutOfMemoryError e) {
56        // this leaves the read lock locked.
57      }
58  
59      Appender<Integer> mockAppender2=createStrictMock(Appender.class);
60      // the next call will lock
61       aai.addAppender(mockAppender2);
62       verify(mockAppender1);
63    }
64  
65    @SuppressWarnings("unchecked")
66    @Test(timeout = 1000)
67    public void detachAppenderBoom() throws InterruptedException {
68      Appender<Integer> mockAppender = createStrictMock(Appender.class);
69      makeThreadSafe(mockAppender, true);
70      expect(mockAppender.getName()).andThrow(new OutOfMemoryError("oops"));
71      mockAppender.doAppend(17);
72      replay(mockAppender);
73  
74      aai.addAppender(mockAppender);
75      Thread t = new Thread(new Runnable() {
76  
77        public void run() {
78          try {
79            // appender.getName called as a result of next statement
80            aai.detachAppender("foo");
81          } catch (OutOfMemoryError e) {
82            // this leaves the write lock locked.
83          }
84        }
85      });
86      t.start();
87      t.join();
88  
89      // the next call will lock
90      aai.appendLoopOnAppenders(17);
91      verify(mockAppender);
92    }
93  
94  }