Index: logback-core/pom.xml
===================================================================
--- logback-core/pom.xml (revision 1953)
+++ logback-core/pom.xml (working copy)
@@ -51,6 +51,13 @@
compile
true
+
+ org.easymock
+ easymock
+ 2.4
+ test
+
+
Index: logback-core/src/main/java/ch/qos/logback/core/spi/AppenderAttachableImpl.java
===================================================================
--- logback-core/src/main/java/ch/qos/logback/core/spi/AppenderAttachableImpl.java (revision 1953)
+++ logback-core/src/main/java/ch/qos/logback/core/spi/AppenderAttachableImpl.java (working copy)
@@ -39,10 +39,13 @@
throw new IllegalArgumentException("Null argument disallowed");
}
w.lock();
- if (!appenderList.contains(newAppender)) {
- appenderList.add(newAppender);
+ try {
+ if (!appenderList.contains(newAppender)) {
+ appenderList.add(newAppender);
+ }
+ } finally {
+ w.unlock();
}
- w.unlock();
}
/**
@@ -71,8 +74,11 @@
public Iterator> iteratorForAppenders() {
List> copy;
r.lock();
- copy = new ArrayList>(appenderList);
- r.unlock();
+ try {
+ copy = new ArrayList>(appenderList);
+ } finally {
+ r.unlock();
+ }
return copy.iterator();
}
@@ -88,15 +94,18 @@
return null;
}
r.lock();
-
- for (Appender appender : appenderList) {
- if (name.equals(appender.getName())) {
- r.unlock();
- return appender;
+ Appender found=null;
+ try {
+ for (Appenderappender : appenderList) {
+ if (name.equals(appender.getName())) {
+ found=appender;
+ break;
+ }
}
+ } finally {
+ r.unlock();
}
- r.unlock();
- return null;
+ return found;
}
/**
@@ -110,26 +119,30 @@
return false;
}
r.lock();
- for (Appender a : appenderList) {
- if (a == appender) {
- r.unlock();
- return true;
+ boolean attached=false;
+ try {
+ for (Appender a : appenderList) {
+ if (a == appender) {
+ attached=true;
+ break;
+ }
}
+ } finally {
+ r.unlock();
}
- r.unlock();
- return false;
+ return attached;
}
/**
* Remove and stop all previously attached appenders.
*/
public void detachAndStopAllAppenders() {
+ w.lock();
try {
- w.lock();
for (Appender a : appenderList) {
- a.stop();
- }
- appenderList.clear();
+ a.stop();
+ }
+ appenderList.clear();
} finally {
w.unlock();
}
@@ -144,9 +157,13 @@
return false;
}
w.lock();
- boolean result = appenderList.remove(appender);
- w.unlock();
- return result;
+ boolean removed;
+ try {
+ removed = appenderList.remove(appender);
+ } finally {
+ w.unlock();
+ }
+ return removed;
}
/**
@@ -158,13 +175,17 @@
return false;
}
w.lock();
- for (Appender a : appenderList) {
- if (name.equals((a).getName())) {
- w.unlock();
- return appenderList.remove(a);
+ boolean removed=false;
+ try {
+ for (Appender a : appenderList) {
+ if (name.equals((a).getName())) {
+ removed=appenderList.remove(a);
+ break;
+ }
}
+ } finally {
+ w.unlock();
}
- w.unlock();
- return false;
+ return removed;
}
}
Index: logback-core/src/test/java/ch/qos/logback/core/spi/AppenderAttachableImplLockTest.java
===================================================================
--- logback-core/src/test/java/ch/qos/logback/core/spi/AppenderAttachableImplLockTest.java (revision 0)
+++ logback-core/src/test/java/ch/qos/logback/core/spi/AppenderAttachableImplLockTest.java (revision 0)
@@ -0,0 +1,101 @@
+package ch.qos.logback.core.spi;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.easymock.EasyMock.createStrictMock;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.easymock.EasyMock.makeThreadSafe;
+import ch.qos.logback.core.Appender;
+
+import java.io.IOException;
+
+/**
+ * This test shows the general problem I described in LBCORE-67.
+ *
+ * In the two testcases below I'm using an appender that throws an OutOfMemoryError while
+ * getName is called - but this is just an example to show the general problem.
+ *
+ * The tests below fail without my change and work if my patch is applied.
+ *
+ * Additionally, the following, probably more realistic, situations could happen:
+ *
+ * -addAppender: appenderList.add() could throw OutOfMemoryError. This could only be shown
+ * by using an appenderList mock but appenderList does not (and should not) have a setter.
+ * This would leave the write lock locked.
+ *
+ * -iteratorForAppenders: new ArrayList() could throw an OutOfMemoryError,
+ * leaving the read lock locked.
+ *
+ * I can't imagine a bad situation in isAttached, detachAppender(Appender) or
+ * detachAppender(String) but I'd change the code anyway for consistency. I'm also
+ * pretty sure that something stupid can happen at any time so it's best to just stick
+ * to conventions.
+ */
+public class AppenderAttachableImplLockTest
+{
+ private AppenderAttachableImpl instance;
+
+ @Before
+ public void init()
+ {
+ instance=new AppenderAttachableImpl();
+ }
+
+ @Test(timeout=1000)
+ public void getAppenderBoom()
+ {
+ Appender mockAppender1=createStrictMock(Appender.class);
+ mockAppender1.getName();
+ expectLastCall().andThrow(new OutOfMemoryError("ups"));
+ replay(mockAppender1);
+
+ Appender mockAppender2=createStrictMock(Appender.class);
+
+ instance.addAppender(mockAppender1);
+ try {
+ instance.getAppender("foo");
+ }
+ catch(OutOfMemoryError e) {
+ // this leaves the read lock locked.
+ }
+
+ // the next call will lock
+ instance.addAppender(mockAppender2);
+ verify(mockAppender1);
+ }
+
+ @Test(timeout=1000)
+ public void detachAppenderBoom() throws InterruptedException
+ {
+ Appender mockAppender=createStrictMock(Appender.class);
+ makeThreadSafe(mockAppender,true);
+ mockAppender.getName();
+ expectLastCall().andThrow(new OutOfMemoryError("ups"));
+ mockAppender.doAppend(17);
+ replay(mockAppender);
+
+ instance.addAppender(mockAppender);
+ Thread t=new Thread(new Runnable()
+ {
+
+ public void run()
+ {
+ try {
+ instance.detachAppender("foo");
+ }
+ catch(OutOfMemoryError e) {
+ // this leaves the write lock locked.
+ }
+ }
+ });
+ t.start();
+ t.join();
+
+ // the next call will lock
+ instance.appendLoopOnAppenders(17);
+ verify(mockAppender);
+ }
+
+}