I'm attaching the patch for LBCLASSIC-35 to this email since I
apparently don't have permission to add it to the Jira.
Ralph Goers (JIRA) wrote:
[ http://jira.qos.ch/browse/LBCLASSIC-35?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=10750#action_10750 ]
Ralph Goers commented on LBCLASSIC-35:
--------------------------------------
This bug needs to be fixed as does the bug in AppenderBase. I have attached the
fix for this issue and will open a second issue for AppenderBase.
FWIW, in some cases slow appenders wouldn't be as much of a problem if it
weren't for these synchronization points. I have a custom appender that is
slow, but is thread safe so many of them can be run in parallel. With these two
synchronization points however, Logback ends up showing up as a system wide
bottleneck.
Deadlock when running on multiple core processors
-------------------------------------------------
Key: LBCLASSIC-35
URL: http://jira.qos.ch/browse/LBCLASSIC-35
Project: logback-classic
Issue Type: Bug
Components: Other
Affects Versions: unspecified
Environment: Operating System: Windows
Platform: PC
Reporter: Toni Heimala
Assignee: Ceki Gulcu
Priority: Blocker
When you run logging into same file from many threads on a system that has more
than one physical processor (Dual Core for example), a deadlock will occur
after a while. This can not be reproduced on HyperThreading processors. Here's
an example program that will demonstrate the behavior:
-----------------------------
Main.java
-----------------------------
import java.util.Date;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
public class Main extends Thread
{
private final static String LOGGER_CONFIGURATION_FILE = "logger.xml";
private final Logger logger = LoggerFactory.getLogger(Main.class);
private final long start;
public Main()
throws JoranException
{
start = new Date().getTime();
LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
JoranConfigurator configurator = new JoranConfigurator();
lc.shutdownAndReset();
configurator.setContext(lc);
configurator.doConfigure(LOGGER_CONFIGURATION_FILE);
}
public void start()
{
ScheduledThreadPoolExecutor ex1 = new ScheduledThreadPoolExecutor(1);
ScheduledThreadPoolExecutor ex2 = new ScheduledThreadPoolExecutor(1);
ScheduledThreadPoolExecutor ex3 = new ScheduledThreadPoolExecutor(1);
ScheduledThreadPoolExecutor ex4 = new ScheduledThreadPoolExecutor(1);
ScheduledThreadPoolExecutor ex5 = new ScheduledThreadPoolExecutor(1);
ex1.scheduleAtFixedRate(new Task("EX1"), 10, 10, TimeUnit.MICROSECONDS);
ex2.scheduleAtFixedRate(new Task("EX2"), 10, 10, TimeUnit.MICROSECONDS);
ex3.scheduleAtFixedRate(new Task("EX3"), 10, 10, TimeUnit.MICROSECONDS);
ex4.scheduleAtFixedRate(new Task("EX4"), 10, 10, TimeUnit.MICROSECONDS);
ex5.scheduleAtFixedRate(new Task("EX5"), 10, 10, TimeUnit.MICROSECONDS);
super.start();
}
public void run()
{
try
{
while(true)
{
logger.debug("[MAIN] {}", new Date().getTime() - start);
Thread.sleep(10);
}
}
catch (InterruptedException e)
{
logger.info("[MAIN]: Interrupted: {}", e.getMessage());
}
}
public static void main(String[] args)
{
try
{
Main main = new Main();
main.start();
}
catch (JoranException e)
{
System.out.println("Failed to load application: " + e.getMessage());
}
}
}
-------------------------------
Task.java
-------------------------------
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Task implements Runnable
{
private final Logger logger = LoggerFactory.getLogger(Task.class);
private final Logger logger_main = LoggerFactory.getLogger(Main.class);
private final String name;
private final long start;
public Task(final String name)
{
this.name = name;
start = new Date().getTime();
}
public void run()
{
logger.debug("[{}] {}", name, new Date().getTime() - start);
logger_main.debug("[MAIN] - [{}] {}", name, new Date().getTime() -
start);
}
}
Index:
logback-access/src/test/java/ch/qos/logback/access/jetty/JettyBasicTest.java
===================================================================
---
logback-access/src/test/java/ch/qos/logback/access/jetty/JettyBasicTest.java
(revision 1828)
+++
logback-access/src/test/java/ch/qos/logback/access/jetty/JettyBasicTest.java
(working copy)
@@ -59,7 +59,7 @@
String result = Util.readToString(connection.getInputStream());
assertEquals("hello world", result);
- //Thread.sleep(100);
+ Thread.sleep(100);
ListAppender appender = (ListAppender) requestLogImpl.getAppender("list");
assertTrue(appender.list.size()>0);
AccessEvent event = (AccessEvent) appender.list.get(0);
Index: logback-classic/src/main/java/ch/qos/logback/classic/Logger.java
===================================================================
--- logback-classic/src/main/java/ch/qos/logback/classic/Logger.java
(revision 1828)
+++ logback-classic/src/main/java/ch/qos/logback/classic/Logger.java
(working copy)
@@ -196,9 +196,7 @@
// propagate the parent levelInt change to this logger's children
// propagate the parent levelInt change to this logger's children
if (childrenList != null) {
- int len = childrenList.size();
- for (int i = 0; i < len; i++) {
- Logger child = (Logger) childrenList.get(i);
+ for (Logger child : childrenList) {
child.handleParentLevelChange(newParentLevel);
}
}
@@ -209,22 +207,24 @@
* Remove all previously added appenders from this logger instance. <p/> This
* is useful when re-reading configuration information.
*/
- public synchronized void detachAndStopAllAppenders() {
+ public void detachAndStopAllAppenders() {
if (aai != null) {
aai.detachAndStopAllAppenders();
}
}
- public synchronized Appender<LoggingEvent> detachAppender(String name) {
+ public Appender<LoggingEvent> detachAppender(String name) {
if (aai == null) {
return null;
}
return aai.detachAppender(name);
}
- public synchronized void addAppender(Appender<LoggingEvent> newAppender) {
- if (aai == null) {
- aai = new AppenderAttachableImpl<LoggingEvent>();
+ public void addAppender(Appender<LoggingEvent> newAppender) {
+ synchronized(this) {
+ if (aai == null) {
+ aai = new AppenderAttachableImpl<LoggingEvent>();
+ }
}
aai.addAppender(newAppender);
}
@@ -236,7 +236,7 @@
return aai.isAttached(appender);
}
- public synchronized Iterator iteratorForAppenders() {
+ public Iterator iteratorForAppenders() {
if (aai == null) {
return Collections.EMPTY_LIST.iterator();
}
@@ -287,7 +287,7 @@
/**
* Remove the appender passed as parameter form the list of appenders.
*/
- public synchronized boolean detachAppender(Appender appender) {
+ public boolean detachAppender(Appender appender) {
if (aai == null) {
return false;
}
Index: logback-core/src/test/java/ch/qos/logback/core/AllCoreTest.java
===================================================================
--- logback-core/src/test/java/ch/qos/logback/core/AllCoreTest.java
(revision 1828)
+++ logback-core/src/test/java/ch/qos/logback/core/AllCoreTest.java
(working copy)
@@ -23,6 +23,7 @@
suite.addTest(ch.qos.logback.core.joran.PackageTest.suite());
suite.addTest(ch.qos.logback.core.appender.PackageTest.suite());
suite.addTest(ch.qos.logback.core.rolling.PackageTest.suite());
+ suite.addTest(ch.qos.logback.core.spi.PackageTest.suite());
return suite;
}
}
Index:
logback-core/src/test/java/ch/qos/logback/core/spi/AppenderAttachableImplTest.java
===================================================================
---
logback-core/src/test/java/ch/qos/logback/core/spi/AppenderAttachableImplTest.java
(revision 0)
+++
logback-core/src/test/java/ch/qos/logback/core/spi/AppenderAttachableImplTest.java
(revision 0)
@@ -0,0 +1,185 @@
+package ch.qos.logback.core.spi;
+
+import junit.framework.TestCase;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.AppenderBase;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.appender.NOPAppender;
+import ch.qos.logback.core.layout.NopLayout;
+
+import java.util.Iterator;
+
+/**
+ * This test case verifies all the methods of AppenderAttableImpl work
properly.
+ *
+ * @author Ceki
+ */
+public class AppenderAttachableImplTest extends TestCase {
+ /**
+ * To run test suite from command line.
+ */
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(suite());
+ }
+
+ /**
+ * Set up a test suite.
+ */
+ public static Test suite() {
+ return new TestSuite(AppenderAttachableImplTest.class);
+ }
+
+ public AppenderAttachableImplTest(String testName) {
+ super(testName);
+ }
+
+ private AppenderAttachableImpl at;
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ at = new AppenderAttachableImpl();
+ }
+
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ at = null;
+ }
+
+ public void testAddAppender() throws Exception {
+ TestEvent event = new TestEvent();
+ TestAppender<TestEvent> ta = new TestAppender<TestEvent>();
+ ta.setLayout(new NopLayout<TestEvent>());
+ ta.start();
+ at.addAppender(ta);
+ ta = new TestAppender<TestEvent>();
+ ta.setLayout(new NopLayout<TestEvent>());
+ ta.setName("test");
+ ta.start();
+ at.addAppender(ta);
+ int size = at.appendLoopOnAppenders(event);
+ assertTrue("Incorrect number of appenders", size == 2);
+ }
+
+ public void testIteratorForAppenders() throws Exception {
+ TestAppender<TestEvent> ta = new TestAppender<TestEvent>();
+ ta.setLayout(new NopLayout<TestEvent>());
+ ta.start();
+ at.addAppender(ta);
+ TestAppender<TestEvent> tab = new TestAppender<TestEvent>();
+ tab.setLayout(new NopLayout<TestEvent>());
+ tab.setName("test");
+ tab.start();
+ at.addAppender(tab);
+ Iterator<TestAppender<TestEvent>> iter = at.iteratorForAppenders();
+ int size = 0;
+ while (iter.hasNext()) {
+ ++size;
+ TestAppender<TestEvent> app = iter.next();
+ assertTrue("Bad Appender", app == ta || app == tab);
+ }
+ assertTrue("Incorrect number of appenders", size == 2);
+ }
+
+ public void getGetAppender() throws Exception {
+ TestAppender<TestEvent> ta = new TestAppender<TestEvent>();
+ ta.setLayout(new NopLayout<TestEvent>());
+ ta.start();
+ at.addAppender(ta);
+ ta.setName("test1");
+ TestAppender<TestEvent> tab = new TestAppender<TestEvent>();
+ tab.setLayout(new NopLayout<TestEvent>());
+ tab.setName("test");
+ tab.start();
+ at.addAppender(tab);
+ Appender a = at.getAppender("test");
+ assertNotNull("Could not find appender", a);
+ assertTrue("Wrong appender", a == tab);
+ a = at.getAppender("test1");
+ assertNotNull("Could not find appender", a);
+ assertTrue("Wrong appender", a == ta);
+ a = at.getAppender("NotThere");
+ assertNull("Appender was returned", a);
+ }
+
+ public void testIsAttached() throws Exception {
+ TestAppender<TestEvent> ta = new TestAppender<TestEvent>();
+ ta.setLayout(new NopLayout<TestEvent>());
+ ta.start();
+ at.addAppender(ta);
+ ta.setName("test1");
+ TestAppender<TestEvent> tab = new TestAppender<TestEvent>();
+ tab.setLayout(new NopLayout<TestEvent>());
+ tab.setName("test");
+ tab.start();
+ at.addAppender(tab);
+ assertTrue("Appender is not attached", at.isAttached(ta));
+ assertTrue("Appender is not attached", at.isAttached(tab));
+ }
+
+ public void testDetachAndStopAllAppenders() throws Exception {
+ TestAppender<TestEvent> ta = new TestAppender<TestEvent>();
+ ta.setLayout(new NopLayout<TestEvent>());
+ ta.start();
+ at.addAppender(ta);
+ ta.setName("test1");
+ TestAppender<TestEvent> tab = new TestAppender<TestEvent>();
+ tab.setLayout(new NopLayout<TestEvent>());
+ tab.setName("test");
+ tab.start();
+ at.addAppender(tab);
+ assertTrue("Appender was not started", tab.isStarted());
+ at.detachAndStopAllAppenders();
+ assertNull("Appender was not removed", at.getAppender("test"));
+ assertFalse("Appender was not stopped", tab.isStarted());
+ }
+
+ public void testDetachAppender() throws Exception {
+ TestAppender<TestEvent> ta = new TestAppender<TestEvent>();
+ ta.setLayout(new NopLayout<TestEvent>());
+ ta.start();
+ at.addAppender(ta);
+ ta.setName("test1");
+ TestAppender<TestEvent> tab = new TestAppender<TestEvent>();
+ tab.setLayout(new NopLayout<TestEvent>());
+ tab.setName("test");
+ tab.start();
+ at.addAppender(tab);
+ assertTrue("Appender not detached", at.detachAppender(tab));
+ assertNull("Appender was not removed", at.getAppender("test"));
+ assertFalse("Appender detach error", at.detachAppender(tab));
+ }
+
+ public void testDetachAppenderByName() throws Exception {
+ TestAppender<TestEvent> ta = new TestAppender<TestEvent>();
+ ta.setLayout(new NopLayout<TestEvent>());
+ ta.start();
+ at.addAppender(ta);
+ ta.setName("test1");
+ TestAppender<TestEvent> tab = new TestAppender<TestEvent>();
+ tab.setLayout(new NopLayout<TestEvent>());
+ tab.setName("test");
+ tab.start();
+ at.addAppender(tab);
+ Appender a = at.detachAppender("test");
+ assertNotNull("Appender not detached", a);
+ a = at.detachAppender("test1");
+ assertNotNull("Appender not detached", a);
+ a = at.detachAppender("test1");
+ assertNull("Appender detach error", a);
+ }
+
+ private static class TestEvent {
+
+ }
+
+
+ private static class TestAppender<TestEvent> extends AppenderBase<TestEvent>
{
+
+ protected void append(TestEvent event) {
+
+ }
+
+ }
+}
\ No newline at end of file
Index: logback-core/src/test/java/ch/qos/logback/core/spi/PackageTest.java
===================================================================
--- logback-core/src/test/java/ch/qos/logback/core/spi/PackageTest.java
(revision 0)
+++ logback-core/src/test/java/ch/qos/logback/core/spi/PackageTest.java
(revision 0)
@@ -0,0 +1,15 @@
+package ch.qos.logback.core.spi;
+
+import junit.framework.TestCase;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+
+public class PackageTest extends TestCase {
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ suite.addTestSuite(AppenderAttachableImplTest.class);
+ return suite;
+ }
+}
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 1828)
+++
logback-core/src/main/java/ch/qos/logback/core/spi/AppenderAttachableImpl.java
(working copy)
@@ -9,20 +9,29 @@
*/
package ch.qos.logback.core.spi;
-import java.util.ArrayList;
import java.util.Iterator;
-import java.util.List;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
import ch.qos.logback.core.Appender;
/**
- * A straightforward implementation of the [EMAIL PROTECTED]
AppenderAttachable} interface.
+ * An implementation of the [EMAIL PROTECTED] AppenderAttachable} interface.
This implementation would be
+ * much simpler if the name attribute of the Appender interface was required
and was immutable.
+ * Since the name is not required to be set and might be changed or set after
attaching it, this
+ * class must account for those rare occurances.
*
* @author Ceki Gülcü
*/
public class AppenderAttachableImpl<E> implements AppenderAttachable<E> {
- final private List<Appender<E>> appenderList = new ArrayList<Appender<E>>();
+ private final ConcurrentMap<String, Appender<E>> appenders =
+ new ConcurrentHashMap<String, Appender<E>>();
+ private AtomicLong counter = new AtomicLong();
+ private static final String APPENDER_NAME_ROOT = "__APPENDER__:";
/**
* Attach an appender. If the appender is already in the list in won't be
@@ -33,21 +42,21 @@
if (newAppender == null) {
throw new IllegalArgumentException("Cannot null as an appener");
}
- if (!appenderList.contains(newAppender)) {
- appenderList.add(newAppender);
+ String name = newAppender.getName();
+ if (name == null)
+ {
+ name = APPENDER_NAME_ROOT + counter.getAndIncrement();
}
+ appenders.putIfAbsent(name, newAppender);
}
/**
* Call the <code>doAppend</code> method on all attached appenders.
*/
public int appendLoopOnAppenders(E e) {
- int size = 0;
- Appender<E> appender;
-
- size = appenderList.size();
- for (int i = 0; i < size; i++) {
- appender = (Appender<E>) appenderList.get(i);
+ Collection<Appender<E>> coll = appenders.values();
+ int size = coll.size();
+ for (Appender appender : coll) {
appender.doAppend(e);
}
return size;
@@ -60,7 +69,7 @@
* @return Enumeration An enumeration of attached appenders.
*/
public Iterator iteratorForAppenders() {
- return appenderList.iterator();
+ return appenders.values().iterator();
}
/**
@@ -74,15 +83,13 @@
if (name == null) {
return null;
}
-
- int size = appenderList.size();
- Appender<E> appender;
-
- for (int i = 0; i < size; i++) {
- appender = appenderList.get(i);
-
- if (name.equals(appender.getName())) {
- return appender;
+ Appender<E> appender = appenders.get(name);
+ if (appender != null) {
+ return appender;
+ }
+ for (Appender<E> a : appenders.values()) {
+ if (name.equals(a.getName())) {
+ return a;
}
}
@@ -100,17 +107,18 @@
return false;
}
- int size = appenderList.size();
- Appender a;
-
- for (int i = 0; i < size; i++) {
- a = (Appender) appenderList.get(i);
-
+ String name = appender.getName();
+ if (name != null) {
+ Appender a = appenders.get(name);
+ if (a != null && a == appender) {
+ return true;
+ }
+ }
+ for (Appender a : appenders.values()) {
if (a == appender) {
return true;
}
}
-
return false;
}
@@ -118,14 +126,10 @@
* Remove and stop all previously attached appenders.
*/
public void detachAndStopAllAppenders() {
- int len = appenderList.size();
-
- for (int i = 0; i < len; i++) {
- Appender a = (Appender) appenderList.get(i);
- a.stop();
+ for (Map.Entry<String, Appender<E>> entry : appenders.entrySet()) {
+ entry.getValue().stop();
+ appenders.remove(entry.getKey(), entry.getValue());
}
-
- appenderList.clear();
}
/**
@@ -136,7 +140,16 @@
if (appender == null) {
return false;
}
- return appenderList.remove(appender);
+ String name = appender.getName();
+ if (appenders.containsKey(name)) {
+ return appenders.remove(name, appender);
+ }
+ for (Map.Entry<String, Appender<E>> entry : appenders.entrySet()) {
+ if (appender == entry.getValue()) {
+ return appenders.remove(entry.getKey(), appender);
+ }
+ }
+ return false;
}
/**
@@ -147,12 +160,13 @@
if (name == null) {
return null;
}
-
- int size = appenderList.size();
-
- for (int i = 0; i < size; i++) {
- if (name.equals((appenderList.get(i)).getName())) {
- return appenderList.remove(i);
+ Appender<E> a = appenders.remove(name);
+ if (a != null) {
+ return a;
+ }
+ for (Map.Entry<String, Appender<E>> entry : appenders.entrySet()) {
+ if (name.equals(entry.getValue().getName())) {
+ return appenders.remove(entry.getKey());
}
}
return null;
_______________________________________________
logback-dev mailing list
[email protected]
http://qos.ch/mailman/listinfo/logback-dev