[JIRA] Created: (LBCORE-97) Starvation on AppenderBase.doAppend

Starvation on AppenderBase.doAppend ----------------------------------- Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4].
From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation."
This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/ -- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn updated LBCORE-97: -------------------------------- Attachment: AppenderBaseLock.patch Patch fixing the starvation problem using a fair ReentrantLock.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu commented on LBCORE-97: ----------------------------------
Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't.
Could you supply a test case which demonstrates your point, even if it is for a specific JDK? I would at least like to see an example. FYI, I have several multi-core PCs available running Linux and/or Windows.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu edited comment on LBCORE-97 at 5/29/09 4:39 PM: -----------------------------------------------------------
Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't.
Could you lease supply a test case which demonstrates your point, even if it is for a specific JDK? I would at least like to see an example. FYI, I have several multi-core PCs available running Linux and/or Windows. was (Author: noreply.ceki@qos.ch): > Concerning a test-case, this would be pretty hardcore to implement
since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't.
Could you supply a test case which demonstrates your point, even if it is for a specific JDK? I would at least like to see an example. FYI, I have several multi-core PCs available running Linux and/or Windows.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu edited comment on LBCORE-97 at 5/29/09 4:39 PM: -----------------------------------------------------------
Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't.
Could you please supply a test case which demonstrates your point, even if it is for a specific JDK? I would at least like to see an example. FYI, I have several multi-core PCs available running Linux and/or Windows. was (Author: noreply.ceki@qos.ch): > Concerning a test-case, this would be pretty hardcore to implement
since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't.
Could you lease supply a test case which demonstrates your point, even if it is for a specific JDK? I would at least like to see an example. FYI, I have several multi-core PCs available running Linux and/or Windows.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- I could try to provide a sample program that shows the general problem of starvation. Would this have to be logback related or is it sufficient if I show that what the documenation says is correct? Or are you talking about a real JUnit testcase that's using logback? Would this be worth anything if it fails on some systems and works on others? As always, assumptions about absolute program execution are pretty hard to make when it comes to multi-threaded applications. The example attached to LBCORE-96 shows that the threads are "synchronized" with the patch in place, i.e. every pool gets the chance to log a message 2009-05-19 22:03:14,520 DEBUG [pool-1-thread-1] [EX1] 5936 2009-05-19 22:03:14,520 DEBUG [pool-3-thread-1] [MAIN] - [EX3] 5925 2009-05-19 22:03:14,520 DEBUG [pool-2-thread-1] [EX2] 5926 2009-05-19 22:03:14,520 DEBUG [pool-5-thread-1] [MAIN] - [EX5] 5900 2009-05-19 22:03:14,520 DEBUG [pool-4-thread-1] [MAIN] - [EX4] 5925 2009-05-19 22:03:14,521 DEBUG [pool-1-thread-1] [MAIN] - [EX1] 5937 2009-05-19 22:03:14,521 DEBUG [pool-3-thread-1] [EX3] 5926 2009-05-19 22:03:14,521 DEBUG [pool-2-thread-1] [MAIN] - [EX2] 5927 2009-05-19 22:03:14,521 DEBUG [pool-5-thread-1] [EX5] 5901 2009-05-19 22:03:14,521 DEBUG [pool-4-thread-1] [EX4] 5926 I inserted newlines to better show the point. It's always executing 1-3-2-5-4. Without the patch, the threads are executed unevenly. This is solely caused by the non-fairness of synchronized as the rest of the code is untouched. The only thing that I could do is to write some program that shows this more drastically, e.g. letting 10 threads run for some time in an endless loop, each containing a synchronized(sameObject) block, counting the times they loop. After a specific time, e.g. 10 sec, I could interrupt all threads and they would certainly have quite different counter values (the amount of difference would vary from system to system and VM to VM). Running the same code with a fair ReentrantLock would generate results with (nearly, because of the interruption of the threads) identical numbers. That's the whole point of fair locks. I had the exact same wrong assumption about fairness in a wait()/notifyAll() situation in Lilith. A thread with the purpose of removing received events from an intermittent cyclic buffer just wouldn't get the chance to remove the events. This was caused by updating the used JRE to Java 1.6. It took me a while to understand the problem and I was quite baffled.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu commented on LBCORE-97: ---------------------------------- No, the "test case " does not need to be logback specific. I am just looking for sample code with an accompanying analysis. If it can be reproduced on a Windows or Linux machine (with whichever JDK) that would be a step forward. Thank you in advance.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- Here are my personal result: iMac 2.4GHz Intel Core 2 Duo i.e. multi-core Environment: java.runtime.name = Java(TM) SE Runtime Environment java.runtime.version = 1.6.0_07-b06-153 java.vendor = Apple Inc. java.version = 1.6.0_07 java.vm.name = Java HotSpot(TM) 64-Bit Server VM java.vm.info = mixed mode os.name = Mac OS X os.version = 10.5.7 os.arch = x86_64 ########################################## About to execute usingSynchronized... Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=394, running=false] runnables[1]: SynchronizedRunnable[counter=1, running=false] runnables[2]: SynchronizedRunnable[counter=1, running=false] runnables[3]: SynchronizedRunnable[counter=1, running=false] runnables[4]: SynchronizedRunnable[counter=1, running=false] runnables[5]: SynchronizedRunnable[counter=1, running=false] runnables[6]: SynchronizedRunnable[counter=1, running=false] runnables[7]: SynchronizedRunnable[counter=1, running=false] runnables[8]: SynchronizedRunnable[counter=1, running=false] runnables[9]: SynchronizedRunnable[counter=605, running=false] ########################################## About to execute usingUnfairLock... Results for usingUnfairLock: runnables[0]: LockRunnable[counter=997, running=false] runnables[1]: LockRunnable[counter=1, running=false] runnables[2]: LockRunnable[counter=1, running=false] runnables[3]: LockRunnable[counter=1, running=false] runnables[4]: LockRunnable[counter=1, running=false] runnables[5]: LockRunnable[counter=1, running=false] runnables[6]: LockRunnable[counter=1, running=false] runnables[7]: LockRunnable[counter=1, running=false] runnables[8]: LockRunnable[counter=1, running=false] runnables[9]: LockRunnable[counter=1, running=false] ########################################## About to execute usingFairLock... Results for usingFairLock: runnables[0]: LockRunnable[counter=101, running=false] runnables[1]: LockRunnable[counter=101, running=false] runnables[2]: LockRunnable[counter=101, running=false] runnables[3]: LockRunnable[counter=101, running=false] runnables[4]: LockRunnable[counter=100, running=false] runnables[5]: LockRunnable[counter=100, running=false] runnables[6]: LockRunnable[counter=100, running=false] runnables[7]: LockRunnable[counter=100, running=false] runnables[8]: LockRunnable[counter=100, running=false] runnables[9]: LockRunnable[counter=100, running=false] ########################################## Environment: java.runtime.name = Java(TM) 2 Runtime Environment, Standard Edition java.runtime.version = 1.5.0_16-b06-284 java.vendor = Apple Inc. java.version = 1.5.0_16 java.vm.name = Java HotSpot(TM) Client VM java.vm.info = mixed mode, sharing os.name = Mac OS X os.version = 10.5.7 os.arch = i386 ########################################## About to execute usingSynchronized... Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=101, running=false] runnables[1]: SynchronizedRunnable[counter=101, running=false] runnables[2]: SynchronizedRunnable[counter=101, running=false] runnables[3]: SynchronizedRunnable[counter=101, running=false] runnables[4]: SynchronizedRunnable[counter=101, running=false] runnables[5]: SynchronizedRunnable[counter=101, running=false] runnables[6]: SynchronizedRunnable[counter=100, running=false] runnables[7]: SynchronizedRunnable[counter=100, running=false] runnables[8]: SynchronizedRunnable[counter=100, running=false] runnables[9]: SynchronizedRunnable[counter=100, running=false] ########################################## About to execute usingUnfairLock... Results for usingUnfairLock: runnables[0]: LockRunnable[counter=485, running=false] runnables[1]: LockRunnable[counter=495, running=false] runnables[2]: LockRunnable[counter=19, running=false] runnables[3]: LockRunnable[counter=1, running=false] runnables[4]: LockRunnable[counter=1, running=false] runnables[5]: LockRunnable[counter=1, running=false] runnables[6]: LockRunnable[counter=1, running=false] runnables[7]: LockRunnable[counter=1, running=false] runnables[8]: LockRunnable[counter=1, running=false] runnables[9]: LockRunnable[counter=1, running=false] ########################################## About to execute usingFairLock... Results for usingFairLock: runnables[0]: LockRunnable[counter=101, running=false] runnables[1]: LockRunnable[counter=101, running=false] runnables[2]: LockRunnable[counter=101, running=false] runnables[3]: LockRunnable[counter=101, running=false] runnables[4]: LockRunnable[counter=101, running=false] runnables[5]: LockRunnable[counter=100, running=false] runnables[6]: LockRunnable[counter=100, running=false] runnables[7]: LockRunnable[counter=100, running=false] runnables[8]: LockRunnable[counter=100, running=false] runnables[9]: LockRunnable[counter=100, running=false] ########################################## #################################################################################### IBM ThinkPad Intel Pentium M processor 1.86GHz i.e. single-core Environment: java.runtime.name = Java(TM) 2 Runtime Environment, Standard Edition java.runtime.version = 1.5.0_13-b05 java.vendor = Sun Microsystems Inc. java.version = 1.5.0_13 java.vm.name = Java HotSpot(TM) Client VM java.vm.info = mixed mode, sharing os.name = Windows XP os.version = 5.1 os.arch = x86 ########################################## About to execute usingSynchronized... Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=65, running=false] runnables[1]: SynchronizedRunnable[counter=65, running=false] runnables[2]: SynchronizedRunnable[counter=65, running=false] runnables[3]: SynchronizedRunnable[counter=65, running=false] runnables[4]: SynchronizedRunnable[counter=65, running=false] runnables[5]: SynchronizedRunnable[counter=65, running=false] runnables[6]: SynchronizedRunnable[counter=65, running=false] runnables[7]: SynchronizedRunnable[counter=64, running=false] runnables[8]: SynchronizedRunnable[counter=64, running=false] runnables[9]: SynchronizedRunnable[counter=64, running=false] ########################################## About to execute usingUnfairLock... Results for usingUnfairLock: runnables[0]: LockRunnable[counter=50, running=false] runnables[1]: LockRunnable[counter=86, running=false] runnables[2]: LockRunnable[counter=63, running=false] runnables[3]: LockRunnable[counter=54, running=false] runnables[4]: LockRunnable[counter=72, running=false] runnables[5]: LockRunnable[counter=72, running=false] runnables[6]: LockRunnable[counter=54, running=false] runnables[7]: LockRunnable[counter=72, running=false] runnables[8]: LockRunnable[counter=70, running=false] runnables[9]: LockRunnable[counter=57, running=false] ########################################## About to execute usingFairLock... Results for usingFairLock: runnables[0]: LockRunnable[counter=65, running=false] runnables[1]: LockRunnable[counter=65, running=false] runnables[2]: LockRunnable[counter=65, running=false] runnables[3]: LockRunnable[counter=65, running=false] runnables[4]: LockRunnable[counter=64, running=false] runnables[5]: LockRunnable[counter=64, running=false] runnables[6]: LockRunnable[counter=64, running=false] runnables[7]: LockRunnable[counter=64, running=false] runnables[8]: LockRunnable[counter=64, running=false] runnables[9]: LockRunnable[counter=64, running=false] ########################################## Environment: java.runtime.name = Java(TM) SE Runtime Environment java.runtime.version = 1.6.0_13-b03 java.vendor = Sun Microsystems Inc. java.version = 1.6.0_13 java.vm.name = Java HotSpot(TM) Client VM java.vm.info = mixed mode, sharing os.name = Windows XP os.version = 5.1 os.arch = x86 ########################################## About to execute usingSynchronized... Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=6, running=false] runnables[1]: SynchronizedRunnable[counter=11, running=false] runnables[2]: SynchronizedRunnable[counter=85, running=false] runnables[3]: SynchronizedRunnable[counter=70, running=false] runnables[4]: SynchronizedRunnable[counter=75, running=false] runnables[5]: SynchronizedRunnable[counter=73, running=false] runnables[6]: SynchronizedRunnable[counter=74, running=false] runnables[7]: SynchronizedRunnable[counter=88, running=false] runnables[8]: SynchronizedRunnable[counter=72, running=false] runnables[9]: SynchronizedRunnable[counter=90, running=false] ########################################## About to execute usingUnfairLock... Results for usingUnfairLock: runnables[0]: LockRunnable[counter=64, running=false] runnables[1]: LockRunnable[counter=87, running=false] runnables[2]: LockRunnable[counter=60, running=false] runnables[3]: LockRunnable[counter=60, running=false] runnables[4]: LockRunnable[counter=76, running=false] runnables[5]: LockRunnable[counter=54, running=false] runnables[6]: LockRunnable[counter=54, running=false] runnables[7]: LockRunnable[counter=71, running=false] runnables[8]: LockRunnable[counter=56, running=false] runnables[9]: LockRunnable[counter=67, running=false] ########################################## About to execute usingFairLock... Results for usingFairLock: runnables[0]: LockRunnable[counter=65, running=false] runnables[1]: LockRunnable[counter=65, running=false] runnables[2]: LockRunnable[counter=65, running=false] runnables[3]: LockRunnable[counter=65, running=false] runnables[4]: LockRunnable[counter=65, running=false] runnables[5]: LockRunnable[counter=65, running=false] runnables[6]: LockRunnable[counter=65, running=false] runnables[7]: LockRunnable[counter=64, running=false] runnables[8]: LockRunnable[counter=64, running=false] runnables[9]: LockRunnable[counter=64, running=false] ##########################################
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn updated LBCORE-97: -------------------------------- Attachment: SynchronizedVsFairLock.java Execute the attached application on both 1.5 and 1.6 to see the difference. Check on both single- and multi-core systems.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- Conclusion: synchronized was fair on 1.5, is a bit unfair on 1.6 single-core and very unfair on 1.6 multi-core. Please apply my patch, thanks ;)
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Maarten Bosteels commented on LBCORE-97: ---------------------------------------- Joern, I have not yet looked at your appender, but I wonder if it wouldn't be better to extend UnsynchronizedAppenderBase ? I have implemented an AsyncSocketAppender myself (not yet in use in production) and it takes care of syncronization itself : it adds all events to a BlockingQueue and another thread takes care of writing the events to the socket. This way you can use whatever locking mechanism you prefer.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- This problem is not at all related to my appender but bites *every* appender that extends AppenderBase, i.e. console, file and socket appender. On a side-note, the Lilith appender is semi-asynchronous and has a buffer of 1000 already serialized events so it's possible to handle bursts of events gracefully. It blocks only after that buffer has been filled. The buffer content is attached to async sub-appenders that are either sending the bytes or discarding them in case of no connection. Since the events are already serialized all sub-appenders receive the exact same byte arrays to send. Take a look at http://apps.sourceforge.net/trac/lilith/wiki/MultiplexAppenderBackground if you are interested. On another side-note, I still consider UnsynchronizedAppenderBase downright dangerous because I firmly (read: religiously) believe that async handling of LoggingEvent instances will introduce problems like http://www.nabble.com/Best-practices-to-avoid-deadlocks-while-logging-td2363... reloaded, i.e. on a whole new level. Serialization (or, more generally, evaluation) *must* be handled synchronously because it's impossible to predict how objects in the MDC and message arguments will behave if converted to a string asynchronously. Even if no deadlock will occur, they *might* have changed in the meantime, essentially producing "lying" log statements in the end. But all this is really 100% unrelated to this bug report. This is a general problem that was introduced in Java 1.6. It's only showing up badly in case of real multi-core systems which made it a bit hard for me to analyze on my terribly outdated 3-year old laptop at work... I guess that's the price a company has to pay if their developers are forced to work with flint stone devices. Are you reading this, IT department?? :p
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

Joern Huxhorn (JIRA) skrev:
This is a general problem that was introduced in Java 1.6. It's only showing up badly in case of real multi-core systems which made it a bit hard for me to analyze on my terribly outdated 3-year old laptop at work... I guess that's the price a company has to pay if their developers are forced to work with flint stone devices.
Are you reading this, IT department?? :p
If I understand you correctly, your current machine WORKS, but a newer will NOT work? So you are basically proving that you should NEVER have a newer machine? -- Thorbjørn Ravn Andersen "...plus... Tubular Bells!"

On 30.05.2009, at 15:11, Thorbjoern Ravn Andersen wrote:
Joern Huxhorn (JIRA) skrev:
This is a general problem that was introduced in Java 1.6. It's only showing up badly in case of real multi-core systems which made it a bit hard for me to analyze on my terribly outdated 3-year old laptop at work... I guess that's the price a company has to pay if their developers are forced to work with flint stone devices.
Are you reading this, IT department?? :p
If I understand you correctly, your current machine WORKS, but a newer will NOT work?
So you are basically proving that you should NEVER have a newer machine?
I'd suggest that the development machine should ideally be comparable to the machine that the app is later deployed to ;) Granted, this is a very special case but my laptop is really slow as hell and constantly overburdened by IDEA plus the rest of the apps necessary for day-to-day work :( And to get serious again :) our app may work on my machine but it does so only by chance since it essentially contains code based on wrong assumptions... Regards, Joern.

Joern Huxhorn skrev:
On 30.05.2009, at 15:11, Thorbjoern Ravn Andersen wrote:
Joern Huxhorn (JIRA) skrev:
This is a general problem that was introduced in Java 1.6. It's only showing up badly in case of real multi-core systems which made it a bit hard for me to analyze on my terribly outdated 3-year old laptop at work... I guess that's the price a company has to pay if their developers are forced to work with flint stone devices.
Are you reading this, IT department?? :p
If I understand you correctly, your current machine WORKS, but a newer will NOT work?
So you are basically proving that you should NEVER have a newer machine?
I'd suggest that the development machine should ideally be comparable to the machine that the app is later deployed to ;)
Ok. So NOBODY should EVER have a newer machine :) The IT-department will love that - think of all the money saved :-D
Granted, this is a very special case but my laptop is really slow as hell and constantly overburdened by IDEA plus the rest of the apps necessary for day-to-day work :(
First step is maximizing the RAM. You have, already? Next step is sitting down with the one responsible for your _productivity_ and let him see you work for ten minutes with all the waits.
And to get serious again :) our app may work on my machine but it does so only by chance since it essentially contains code based on wrong assumptions... Raise the flag :)
-- Thorbjørn Ravn Andersen "...plus... Tubular Bells!"

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ralph Goers commented on LBCORE-97: ----------------------------------- Your notion that UnsynchronizedAppenderBase is "downright dangerous" is complete nonsense. That is like saying multithreaded programming is dangerous so don't do it. The Appender that extends UnsynchronizedAppenderBase is called on the same thread that called the logger method. All UnsynchronizedAppenderBase does is leave whatever synchronization is necessary up to the actual Appender. If the appender is going to pass work of to some other thread then the work queue elements need to be fully populated with data on the thread creating the work elememnts, i.e. the same thread that called the logging method. Trying to have the worker thread call methods on objects in a logger event is a very poor design. In many cases the only synchronization needed in an appender will be in the JDK itself when it performs some kind of I/O. There have been many demonstrated examples where the synchronization that is occuring within logback has caused deadlocks because the application using logback was unaware that the locking would occur. UnsynchronizedAppenderBase actually helps with avoiding deadlocks since the locking moves down to where it is actually needed, not at some higher level "just in case".
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

Let's move this discussion over to the dev list to leave the bug alone... On 30.05.2009, at 20:52, Ralph Goers (JIRA) wrote:
[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ]
Ralph Goers commented on LBCORE-97: -----------------------------------
Your notion that UnsynchronizedAppenderBase is "downright dangerous" is complete nonsense. That is like saying multithreaded programming is dangerous so don't do it.
I'd call "complete nonsense" a bit harsh. It's only one step away from bullshit ;) What I would say, however, is that multi-threaded programming is more complex and dangerous than single-threaded, so use it with care.
The Appender that extends UnsynchronizedAppenderBase is called on the same thread that called the logger method. All UnsynchronizedAppenderBase does is leave whatever synchronization is necessary up to the actual Appender. If the appender is going to pass work of to some other thread then the work queue elements need to be fully populated with data on the thread creating the work elememnts, i.e. the same thread that called the logging method. Trying to have the worker thread call methods on objects in a logger event is a very poor design.
I agree 100%. The only thing that I don't like about UnsynchronizedAppenderBase is that it's more error prone because everything you mentioned in the last paragraph must be implemented by every developer that extends UnsynchronizedAB. I know all the above, you know it, Ceki knows it, but lots of people won't have enough know how to know it. Asynchronous appenders can be implemented using the synchronous AppenderBase. Just put the LoggingEvent => DataObject transformation in the doAppend call and handle all the rest in different threads.
In many cases the only synchronization needed in an appender will be in the JDK itself when it performs some kind of I/O.
There have been many demonstrated examples where the synchronization that is occuring within logback has caused deadlocks because the application using logback was unaware that the locking would occur. UnsynchronizedAppenderBase actually helps with avoiding deadlocks since the locking moves down to where it is actually needed, not at some higher level "just in case".
I see your point. You want to perform the LE => DO transformation without a lock and lock afterwards while performing the actual appending. That's a *very* good idea and would indeed have the potential to solve issues like http://www.nabble.com/Best-practices-to-avoid-deadlocks-while-logging-td2363... ... The only real problem I see is the one I showed in the bug. It's absolutely crucial that appenders perform fair locking and most people (including Ceki and me) are/were absolutely unaware that synchronized can be very unfair - even though it *is* documented! Leaving the synchronization to the appender implementation is therefore somewhat dangerous :p and can result in starvation if done wrong. And it's easy to do it wrong. It would probably be a good idea to write a base class that performs the LE => DO transformation (converting message arguments, transforming MDC to String, etc.) without a lock, handing the DO to a method that performs a fair lock which in turn executes an abstract method that performs the actual work? I'm just brainstorming here... My primary aim is just to prevent a situation like the one in the bug in lots of appenders that extend UnsynchronizedAppenderBase. We'd need dedicated Logback Logging(- and Access?)DOs in that case. Regards, Joern.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp- requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira
_______________________________________________ logback-dev mailing list logback-dev@qos.ch http://qos.ch/mailman/listinfo/logback-dev

On May 30, 2009, at 6:23 PM, Joern Huxhorn wrote:
Let's move this discussion over to the dev list to leave the bug alone...
On 30.05.2009, at 20:52, Ralph Goers (JIRA) wrote:
[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ]
Ralph Goers commented on LBCORE-97: -----------------------------------
Your notion that UnsynchronizedAppenderBase is "downright dangerous" is complete nonsense. That is like saying multithreaded programming is dangerous so don't do it.
I'd call "complete nonsense" a bit harsh. It's only one step away from bullshit ;)
Yes, I tried to think of a phrase that wasn't so harsh but came up short. I probably wouldn't have replied quite that way if it was on this list, but in a Jira issue where it will more easily be referenced I felt it appropriate to word it a bit more strongly and a bit less diplomatic.
What I would say, however, is that multi-threaded programming is more complex and dangerous than single-threaded, so use it with care.
The Appender that extends UnsynchronizedAppenderBase is called on the same thread that called the logger method. All UnsynchronizedAppenderBase does is leave whatever synchronization is necessary up to the actual Appender. If the appender is going to pass work of to some other thread then the work queue elements need to be fully populated with data on the thread creating the work elememnts, i.e. the same thread that called the logging method. Trying to have the worker thread call methods on objects in a logger event is a very poor design.
I agree 100%. The only thing that I don't like about UnsynchronizedAppenderBase is that it's more error prone because everything you mentioned in the last paragraph must be implemented by every developer that extends UnsynchronizedAB.
I know all the above, you know it, Ceki knows it, but lots of people won't have enough know how to know it.
Asynchronous appenders can be implemented using the synchronous AppenderBase. Just put the LoggingEvent => DataObject transformation in the doAppend call and handle all the rest in different threads.
Well, yes. But that introduces needless inefficiency. The Appender will be synchronized while adding the work to a queue. But the queue must also be synchronized so that the worker thread can remove it from the queue safely. The synchronization of AppenderBase is, IMO, a mistake. Log4j did it and they have plenty of bugs reported about deadlocks. Ceki has already had a couple here. They aren't actually caused by Logback of course, but they often appear to be which is almost as bad as if they were. Every Appender in Logback could be modified so that the synchronization could be moved to a smaller granularity. Unfortunately, some of the layout processing isn't thread-safe so there isn't much point in trying to do that until that is changed.
In many cases the only synchronization needed in an appender will be in the JDK itself when it performs some kind of I/O.
There have been many demonstrated examples where the synchronization that is occuring within logback has caused deadlocks because the application using logback was unaware that the locking would occur. UnsynchronizedAppenderBase actually helps with avoiding deadlocks since the locking moves down to where it is actually needed, not at some higher level "just in case".
I see your point. You want to perform the LE => DO transformation without a lock and lock afterwards while performing the actual appending. That's a *very* good idea and would indeed have the potential to solve issues like http://www.nabble.com/Best-practices-to-avoid-deadlocks-while-logging-td2363... ...
The only real problem I see is the one I showed in the bug.
It's absolutely crucial that appenders perform fair locking and most people (including Ceki and me) are/were absolutely unaware that synchronized can be very unfair - even though it *is* documented! Leaving the synchronization to the appender implementation is therefore somewhat dangerous :p and can result in starvation if done wrong. And it's easy to do it wrong.
I have news for you. In a multithreaded program you aren't going to be able to insure that log records ever occur in a particular order unless the application does the synchronization itself. For example, it is highly likely you will see logging like: Thread 1 - about to do action 1 Thread 2 - about to do action 1 Thread 1 - action 1 performed Thread 3 - about to do action 1 Thread 3 - action 1 performed Thread 2 - action 1 performed This is regardless of whatever locking is going on in Logback. And, of course, there is no assurance that the order of the log entries corresponds to the order in which the actions actually occurred.
It would probably be a good idea to write a base class that performs the LE => DO transformation (converting message arguments, transforming MDC to String, etc.) without a lock, handing the DO to a method that performs a fair lock which in turn executes an abstract method that performs the actual work? I'm just brainstorming here... My primary aim is just to prevent a situation like the one in the bug in lots of appenders that extend UnsynchronizedAppenderBase. We'd need dedicated Logback Logging(- and Access?)DOs in that case.
I'm not sure what "bug in lots of appenders that extend UnsynchronizedAppenderBase" you are talking about. In reading your opening remarks you discuss "fairness" in locking. But it doesn't really matter. Thread 1 can call logger.info() and before the appender even gets a chance to lock Thread 2 can call logger.info(). The CPU might give conrol to Thread 2 and let it finish ahead of thread 1. Even if you have multiple CPUs, as long as you have more threads hitting the same logic than there are CPUs you will run into this issue. This is just a fact of life when dealing with multithreaded applications. If you look at a standard debug log with a fair amount of logging you will see this all the time. It isn't readily apparent because the granularity of the time is usually a millisecond, but occasionally you will see log records that appear to be out of order. Ralph

Hi Ralph. On 31.05.2009, at 09:21, Ralph Goers wrote:
On May 30, 2009, at 6:23 PM, Joern Huxhorn wrote:
Let's move this discussion over to the dev list to leave the bug alone...
On 30.05.2009, at 20:52, Ralph Goers (JIRA) wrote:
[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ]
Ralph Goers commented on LBCORE-97: -----------------------------------
Your notion that UnsynchronizedAppenderBase is "downright dangerous" is complete nonsense. That is like saying multithreaded programming is dangerous so don't do it.
I'd call "complete nonsense" a bit harsh. It's only one step away from bullshit ;)
Yes, I tried to think of a phrase that wasn't so harsh but came up short. I probably wouldn't have replied quite that way if it was on this list, but in a Jira issue where it will more easily be referenced I felt it appropriate to word it a bit more strongly and a bit less diplomatic.
No problem ;)
What I would say, however, is that multi-threaded programming is more complex and dangerous than single-threaded, so use it with care.
The Appender that extends UnsynchronizedAppenderBase is called on the same thread that called the logger method. All UnsynchronizedAppenderBase does is leave whatever synchronization is necessary up to the actual Appender. If the appender is going to pass work of to some other thread then the work queue elements need to be fully populated with data on the thread creating the work elememnts, i.e. the same thread that called the logging method. Trying to have the worker thread call methods on objects in a logger event is a very poor design.
I agree 100%. The only thing that I don't like about UnsynchronizedAppenderBase is that it's more error prone because everything you mentioned in the last paragraph must be implemented by every developer that extends UnsynchronizedAB.
I know all the above, you know it, Ceki knows it, but lots of people won't have enough know how to know it.
Asynchronous appenders can be implemented using the synchronous AppenderBase. Just put the LoggingEvent => DataObject transformation in the doAppend call and handle all the rest in different threads.
Well, yes. But that introduces needless inefficiency. The Appender will be synchronized while adding the work to a queue. But the queue must also be synchronized so that the worker thread can remove it from the queue safely.
The synchronization of AppenderBase is, IMO, a mistake. Log4j did it and they have plenty of bugs reported about deadlocks. Ceki has already had a couple here. They aren't actually caused by Logback of course, but they often appear to be which is almost as bad as if they were. Every Appender in Logback could be modified so that the synchronization could be moved to a smaller granularity. Unfortunately, some of the layout processing isn't thread-safe so there isn't much point in trying to do that until that is changed.
The sledgehammer synchronization using synchronized on the instance itself is definitely a mistake as I've shown in the bug report. A first (and absolutely necessary step!!) is to replace it with a fair ReentrantLock so Logback isn't responsible for starvation situations anymore.
In many cases the only synchronization needed in an appender will be in the JDK itself when it performs some kind of I/O.
There have been many demonstrated examples where the synchronization that is occuring within logback has caused deadlocks because the application using logback was unaware that the locking would occur. UnsynchronizedAppenderBase actually helps with avoiding deadlocks since the locking moves down to where it is actually needed, not at some higher level "just in case".
I see your point. You want to perform the LE => DO transformation without a lock and lock afterwards while performing the actual appending. That's a *very* good idea and would indeed have the potential to solve issues like http://www.nabble.com/Best-practices-to-avoid-deadlocks-while-logging-td2363... ...
The only real problem I see is the one I showed in the bug.
It's absolutely crucial that appenders perform fair locking and most people (including Ceki and me) are/were absolutely unaware that synchronized can be very unfair - even though it *is* documented! Leaving the synchronization to the appender implementation is therefore somewhat dangerous :p and can result in starvation if done wrong. And it's easy to do it wrong.
I have news for you. In a multithreaded program you aren't going to be able to insure that log records ever occur in a particular order unless the application does the synchronization itself. For example, it is highly likely you will see logging like: Thread 1 - about to do action 1 Thread 2 - about to do action 1 Thread 1 - action 1 performed Thread 3 - about to do action 1 Thread 3 - action 1 performed Thread 2 - action 1 performed
This is regardless of whatever locking is going on in Logback. And, of course, there is no assurance that the order of the log entries corresponds to the order in which the actions actually occurred.
That's not news but the whole point of multi-threaded programming ;)
It would probably be a good idea to write a base class that performs the LE => DO transformation (converting message arguments, transforming MDC to String, etc.) without a lock, handing the DO to a method that performs a fair lock which in turn executes an abstract method that performs the actual work? I'm just brainstorming here... My primary aim is just to prevent a situation like the one in the bug in lots of appenders that extend UnsynchronizedAppenderBase. We'd need dedicated Logback Logging(- and Access?)DOs in that case.
I'm not sure what "bug in lots of appenders that extend UnsynchronizedAppenderBase" you are talking about. In reading your opening remarks you discuss "fairness" in locking. But it doesn't really matter.
Whoa, it doesn't really matter?? This is complete nonsense ;), read below.
Thread 1 can call logger.info() and before the appender even gets a chance to lock Thread 2 can call logger.info(). The CPU might give conrol to Thread 2 and let it finish ahead of thread 1. Even if you have multiple CPUs, as long as you have more threads hitting the same logic than there are CPUs you will run into this issue. This is just a fact of life when dealing with multithreaded applications. If you look at a standard debug log with a fair amount of logging you will see this all the time. It isn't readily apparent because the granularity of the time is usually a millisecond, but occasionally you will see log records that appear to be out of order.
I'm talking about the following situation in case of JDK 1.6 on multi- core systems: Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=394, running=false] runnables[1]: SynchronizedRunnable[counter=1, running=false] runnables[2]: SynchronizedRunnable[counter=1, running=false] runnables[3]: SynchronizedRunnable[counter=1, running=false] runnables[4]: SynchronizedRunnable[counter=1, running=false] runnables[5]: SynchronizedRunnable[counter=1, running=false] runnables[6]: SynchronizedRunnable[counter=1, running=false] runnables[7]: SynchronizedRunnable[counter=1, running=false] runnables[8]: SynchronizedRunnable[counter=1, running=false] runnables[9]: SynchronizedRunnable[counter=605, running=false] This is the result of the app appended to http://jira.qos.ch/browse/LBCORE-97 in that case. Please take a look at the code. All 10 threads are executing the same Runnable: public static class SynchronizedRunnable implements Runnable { private final Object lockObject; private int counter; private boolean running; public SynchronizedRunnable(Object lockObject) { this.lockObject=lockObject; this.counter=0; this.running=false; } public void run() { running=true; for(;;) { synchronized(lockObject) { counter++; try { Thread.sleep(10); } catch(InterruptedException ex) { break; } } } running=false; } public String toString() { return "SynchronizedRunnable[counter="+counter+", running="+running +"]"; } } I'm simulating the AppenderBase situation here, lots of threads are trying to access the very same resource. With Java 1.6 on multi-core systems, only <number of cores> threads will ever get the chance to obtain the object monitor. All other threads are starving. And this is a valid simplification because it shows exactly what's happening in our webapp right now. We had to disable logging to get it back into a usable state. Using a fair ReentrantLock fixes this. It makes sure that threads are handled fairly concerning the shared resource, i.e. they are served in a "first come, first served" order. I assume you hadn't understood the point of LBCORE-97 until now... and please don't take that as an insult ;) I wrote code under the assumption that synchronized would be at least sufficiently semi-fair, Ceki did in both LOG4J and Logback, and reality up until Java 1.6 on multi-core seemed to indicate that it's ok to assume that total fairness isn't necessary... but all this changed because synchronized is absolutely unfair now - and the documentation shows that the fault is on our side. We were simply working with wrong assumptions. This just proves my point that locking should be handled by the logging system in a correct, i.e. fair, way. It is "downright dangerous" to assume that every person that implements an appender is aware of this situation and the problem will only show up on Java 1.6 multi-core systems. This is a *very* big risk. But I agree 100% that the transformation/evaluation of the logging event can and should be handled outside of whatever fair locking mechanism is used. This would solve some issues and seems very logical. We should definitely aim for this in the future... Ideally, AppenderBase would be changed to support this, IMHO. It would probably be a better idea to use a protected final ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock(true); fair ReentrantReadWriteLock in AppenderBase instead. doAppend would then use the write lock while actual implementations would have the freedom of using either read or write lock, as needed. This would remove the need of synchronizing the worker queue you mentioned earlier. I'm not sure if we really need a ReadWriteLock, though, as removal from the queue would be a write, too. But *some* appender implementation could have a use case for read-only-access... One way to handle transformation of the event outside the lock is what I suggested in http://jira.qos.ch/browse/LBCLASSIC-45. Just dumb down LoggingEvent enough so that it already is detached from the rest of the system after it's created. But someone, and I honestly can't remember but I think it was you, said that it would be bad if the MDC was already transformed into Map<String,String> at that point... I've just changed my patch to make lock protected instead of private. Seems to be a much better idea :p And just to get my point straight once more: A logging system MUST NOT be a point of starvation! It will naturally always be a chokepoint but it MUST NEVER be a point of starvation! Regards, Joern.
Ralph _______________________________________________ logback-dev mailing list logback-dev@qos.ch http://qos.ch/mailman/listinfo/logback-dev

On May 31, 2009, at 6:11 AM, Joern Huxhorn wrote:
The sledgehammer synchronization using synchronized on the instance itself is definitely a mistake as I've shown in the bug report. A first (and absolutely necessary step!!) is to replace it with a fair ReentrantLock so Logback isn't responsible for starvation situations anymore.
While that may be true of AppenderBase, the point I've been making is that since UnsynchronizedAppenderBase doesn't lock the issue is avoided completely (at least at this level).
Thread 1 can call logger.info() and before the appender even gets a chance to lock Thread 2 can call logger.info(). The CPU might give conrol to Thread 2 and let it finish ahead of thread 1. Even if you have multiple CPUs, as long as you have more threads hitting the same logic than there are CPUs you will run into this issue. This is just a fact of life when dealing with multithreaded applications. If you look at a standard debug log with a fair amount of logging you will see this all the time. It isn't readily apparent because the granularity of the time is usually a millisecond, but occasionally you will see log records that appear to be out of order.
I'm talking about the following situation in case of JDK 1.6 on multi-core systems: Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=394, running=false] runnables[1]: SynchronizedRunnable[counter=1, running=false] runnables[2]: SynchronizedRunnable[counter=1, running=false] runnables[3]: SynchronizedRunnable[counter=1, running=false] runnables[4]: SynchronizedRunnable[counter=1, running=false] runnables[5]: SynchronizedRunnable[counter=1, running=false] runnables[6]: SynchronizedRunnable[counter=1, running=false] runnables[7]: SynchronizedRunnable[counter=1, running=false] runnables[8]: SynchronizedRunnable[counter=1, running=false] runnables[9]: SynchronizedRunnable[counter=605, running=false]
This is the result of the app appended to http://jira.qos.ch/browse/LBCORE-97 in that case. Please take a look at the code. All 10 threads are executing the same Runnable: public static class SynchronizedRunnable implements Runnable { private final Object lockObject; private int counter; private boolean running;
public SynchronizedRunnable(Object lockObject) { this.lockObject=lockObject; this.counter=0; this.running=false; }
public void run() { running=true; for(;;) { synchronized(lockObject) { counter++; try { Thread.sleep(10); } catch(InterruptedException ex) { break; } } } running=false; }
public String toString() { return "SynchronizedRunnable[counter="+counter+", running="+running +"]"; } }
I'm simulating the AppenderBase situation here, lots of threads are trying to access the very same resource. With Java 1.6 on multi-core systems, only <number of cores> threads will ever get the chance to obtain the object monitor. All other threads are starving. And this is a valid simplification because it shows exactly what's happening in our webapp right now. We had to disable logging to get it back into a usable state.
Using a fair ReentrantLock fixes this. It makes sure that threads are handled fairly concerning the shared resource, i.e. they are served in a "first come, first served" order.
The issue here isn't really that there is a problem with how synchronized locks work. If there were Tomcat would fall on its face. Take a look at https://svn.apache.org/repos/asf/tomcat/trunk/java/org/apache/catalina/loade... . I guarantee you this is being called far more often then logging is and it doesn't exhibit these problems. The real culprit here is how much time is being spent inside the synchronized block. When you look at the WebappClassLoader you will see that locks are held for very short periods of time. This is contrasted with Appenders that hold the lock for an extremely long period of time. To prove this I simply added for (int i=0; i < 100000; ++i) { if lock == null { throw new NullPointerException(); } } before the synchronized or locked block (using lockObject instead of lock for the synchronzied block). While the counters don't come out equal they are very close. This shows that the problem is really how much time is spent in the synchronized block vs the actual application. The significance of this is that even when you "fix" the locking so it is fair your application performance is still going to suck because it is spending so much time in the Appender vs doing actual work.
I assume you hadn't understood the point of LBCORE-97 until now... and please don't take that as an insult ;)
I the understanding of it. I just don't think it is the right way to fix the problem.
I wrote code under the assumption that synchronized would be at least sufficiently semi-fair, Ceki did in both LOG4J and Logback, and reality up until Java 1.6 on multi-core seemed to indicate that it's ok to assume that total fairness isn't necessary... but all this changed because synchronized is absolutely unfair now - and the documentation shows that the fault is on our side. We were simply working with wrong assumptions.
This just proves my point that locking should be handled by the logging system in a correct, i.e. fair, way. It is "downright dangerous" to assume that every person that implements an appender is aware of this situation and the problem will only show up on Java 1.6 multi-core systems. This is a *very* big risk.
But I agree 100% that the transformation/evaluation of the logging event can and should be handled outside of whatever fair locking mechanism is used. This would solve some issues and seems very logical. We should definitely aim for this in the future... Ideally, AppenderBase would be changed to support this, IMHO.
It would probably be a better idea to use a
protected final ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock(true);
fair ReentrantReadWriteLock in AppenderBase instead. doAppend would then use the write lock while actual implementations would have the freedom of using either read or write lock, as needed. This would remove the need of synchronizing the worker queue you mentioned earlier. I'm not sure if we really need a ReadWriteLock, though, as removal from the queue would be a write, too. But *some* appender implementation could have a use case for read-only-access...
One way to handle transformation of the event outside the lock is what I suggested in http://jira.qos.ch/browse/LBCLASSIC-45. Just dumb down LoggingEvent enough so that it already is detached from the rest of the system after it's created. But someone, and I honestly can't remember but I think it was you, said that it would be bad if the MDC was already transformed into Map<String,String> at that point...
I've just changed my patch to make lock protected instead of private. Seems to be a much better idea :p
And just to get my point straight once more: A logging system MUST NOT be a point of starvation! It will naturally always be a chokepoint but it MUST NEVER be a point of starvation!
On this we agree. If switching to a ReentrantReadWriteLock fixes the problem for AppenderBase then I certainly don't have a problem with that. Personally, I see it as sort of a band-aid though. I'd like to go through and deal with all the code that isn't thread-safe and move the locking down to just those areas where it can't be avoided. Every reference you can find on multithreaded programming will tell you to hold locks for as short a duration as possible. Logback and Log4j don't do that which opens them up to these kinds of issues. Again, even if you make it "fair" the amount of time the lock is being held means AppenderBase will almost certainly still be the biggest bottleneck in your system when logging is enabled. Ralph

Hi Ralph, On 01.06.2009, at 03:39, Ralph Goers wrote:
On May 31, 2009, at 6:11 AM, Joern Huxhorn wrote:
The sledgehammer synchronization using synchronized on the instance itself is definitely a mistake as I've shown in the bug report. A first (and absolutely necessary step!!) is to replace it with a fair ReentrantLock so Logback isn't responsible for starvation situations anymore.
While that may be true of AppenderBase, the point I've been making is that since UnsynchronizedAppenderBase doesn't lock the issue is avoided completely (at least at this level).
But locking will occur at some point and has to be done fairly. Otherwise starvation will happen on multi-core systems. I can't imagine an appender that doesn't have to lock at *some* point. Do you have any use case for this? It would be possible to generally use a fixed-size blocking queue (fair!) in case of UnsynchronizedAppenderBase, leaving the async handling of events put into the queue to the actual implementation. Assuming, of course, detached data objects again. What I really want is *one* correct way of handling/implementing async appenders so the possibility of failure is reduced as much as possible. Nothing is worse than a logging system that crashes the logged application.
I'm simulating the AppenderBase situation here, lots of threads are trying to access the very same resource. With Java 1.6 on multi-core systems, only <number of cores> threads will ever get the chance to obtain the object monitor. All other threads are starving. And this is a valid simplification because it shows exactly what's happening in our webapp right now. We had to disable logging to get it back into a usable state.
Using a fair ReentrantLock fixes this. It makes sure that threads are handled fairly concerning the shared resource, i.e. they are served in a "first come, first served" order.
The issue here isn't really that there is a problem with how synchronized locks work. If there were Tomcat would fall on its face. Take a look at https://svn.apache.org/repos/asf/tomcat/trunk/java/org/apache/catalina/loade... . I guarantee you this is being called far more often then logging is and it doesn't exhibit these problems.
The difference is that WebappClassLoader wouldn't suffer from the starvation effect because threads (at least if they are ordinary threads that don't perform manual classloading magic) will load all classes in a burst and then just execute. In that case, the optimization done in Java 6 will even increase the performance and there is no point in using a fair lock. Logging, however, is potentially executed all the time during the life of the thread(s), probably even in a tight loop. This is were starvation is kicking in @tight loop.
The real culprit here is how much time is being spent inside the synchronized block. When you look at the WebappClassLoader you will see that locks are held for very short periods of time. This is contrasted with Appenders that hold the lock for an extremely long period of time.
See *.
To prove this I simply added
for (int i=0; i < 100000; ++i) { if lock == null { throw new NullPointerException(); } }
before the synchronized or locked block (using lockObject instead of lock for the synchronzied block). While the counters don't come out equal they are very close. This shows that the problem is really how much time is spent in the synchronized block vs the actual application.
I don't understand this example. The whole point of the program is to show the starvation effect. The Thread.sleep is only included to be able to interrupt the thread and simulate some work.
The significance of this is that even when you "fix" the locking so it is fair your application performance is still going to suck because it is spending so much time in the Appender vs doing actual work.
See *.
I assume you hadn't understood the point of LBCORE-97 until now... and please don't take that as an insult ;)
I the understanding of it. I just don't think it is the right way to fix the problem.
I wrote code under the assumption that synchronized would be at least sufficiently semi-fair, Ceki did in both LOG4J and Logback, and reality up until Java 1.6 on multi-core seemed to indicate that it's ok to assume that total fairness isn't necessary... but all this changed because synchronized is absolutely unfair now - and the documentation shows that the fault is on our side. We were simply working with wrong assumptions.
This just proves my point that locking should be handled by the logging system in a correct, i.e. fair, way. It is "downright dangerous" to assume that every person that implements an appender is aware of this situation and the problem will only show up on Java 1.6 multi-core systems. This is a *very* big risk.
But I agree 100% that the transformation/evaluation of the logging event can and should be handled outside of whatever fair locking mechanism is used. This would solve some issues and seems very logical. We should definitely aim for this in the future... Ideally, AppenderBase would be changed to support this, IMHO.
It would probably be a better idea to use a
protected final ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock(true);
fair ReentrantReadWriteLock in AppenderBase instead. doAppend would then use the write lock while actual implementations would have the freedom of using either read or write lock, as needed. This would remove the need of synchronizing the worker queue you mentioned earlier. I'm not sure if we really need a ReadWriteLock, though, as removal from the queue would be a write, too. But *some* appender implementation could have a use case for read-only-access...
One way to handle transformation of the event outside the lock is what I suggested in http://jira.qos.ch/browse/LBCLASSIC-45. Just dumb down LoggingEvent enough so that it already is detached from the rest of the system after it's created. But someone, and I honestly can't remember but I think it was you, said that it would be bad if the MDC was already transformed into Map<String,String> at that point...
I've just changed my patch to make lock protected instead of private. Seems to be a much better idea :p
And just to get my point straight once more: A logging system MUST NOT be a point of starvation! It will naturally always be a chokepoint but it MUST NEVER be a point of starvation!
On this we agree. If switching to a ReentrantReadWriteLock fixes the problem for AppenderBase then I certainly don't have a problem with that. Personally, I see it as sort of a band-aid though. I'd like to go through and deal with all the code that isn't thread-safe and move the locking down to just those areas where it can't be avoided. Every reference you can find on multithreaded programming will tell you to hold locks for as short a duration as possible. Logback and Log4j don't do that which opens them up to these kinds of issues.
See * ;) That's correct. And yes, it's some kind of a band-aid, albeit a very important one. Async logging has other downsides, though, as it will be unable to make sure that events are actually appended before the VM exits/ crashes, for example. This *can* be a real downside. So in some cases, e.g. file appender, you'll *want* synchronous logging, no matter what the costs are. Moving the synchronization to a lower level wouldn't help much, IMO, since it would simply move the congestion and I don't really care if it happens while converting events or while actually writing the data to a file if writing has the performance impact. The application wouldn't be able to handle other logging, anyway. Unless I miss some important point, feel free to prove me wrong. Take a look at http://apps.sourceforge.net/trac/lilith/wiki/AppenderPitfalls if you haven't already and let me know if you find any mistakes, please. My big pain-point with UnsynchronizedAppenderBase is also it's main feature: It's leaving all the locking to the respective implementation. All I'm trying to say is that it's very easy to do that part wrong and that it won't be apparent if it *is* done wrong.
Again, even if you make it "fair" the amount of time the lock is being held means AppenderBase will almost certainly still be the biggest bottleneck in your system when logging is enabled.
Yep, if you don't want that bottleneck you have to disable/reduce logging. Logging *will* always be a bottleneck. The only way to "fix" this is by using one file/socket/whatever per thread which I'd consider a bit unpractical. Regards, Jörn. * My actual, personal Lilith appender implementation is just converting the log event into a byte array, adding it to a BlockingQueue, while holding the lock. This could be enhanced by just using a fair BlockingQueue and removing the surrounding lock alltogether, as you said correctly. Those bytes are then sent to n recipients, each in it's own thread. But that's not really the point here. The point is that all appenders extending the current AppenderBase may result in starvation effects on multi-core systems, as shown.

On Mon, Jun 1, 2009 at 6:51 AM, Joern Huxhorn <jhuxhorn@googlemail.com>wrote:
But locking will occur at some point and has to be done fairly. Otherwise starvation will happen on multi-core systems.
Not if the synchronized blocks take a minimal amount of time. My modification to your sample app demonstrated that.
I can't imagine an appender that doesn't have to lock at *some* point. Do you have any use case for this?
Of course they will have to lock, possibly a few times. But if each lock is for a very short duration the system will behave much better.
It would be possible to generally use a fixed-size blocking queue (fair!) in case of UnsynchronizedAppenderBase, leaving the async handling of events put into the queue to the actual implementation. Assuming, of course, detached data objects again.
What I really want is *one* correct way of handling/implementing async appenders so the possibility of failure is reduced as much as possible. Nothing is worse than a logging system that crashes the logged application.
We agree on that.
I'm simulating the AppenderBase situation here, lots of threads are trying to access the very same resource. With Java 1.6 on multi-core systems, only <number of cores> threads will ever get the chance to obtain the object monitor. All other threads are starving. And this is a valid simplification because it shows exactly what's happening in our webapp right now. We had to disable logging to get it back into a usable state.
Using a fair ReentrantLock fixes this. It makes sure that threads are handled fairly concerning the shared resource, i.e. they are served in a "first come, first served" order.
The issue here isn't really that there is a problem with how synchronized locks work. If there were Tomcat would fall on its face. Take a look at https://svn.apache.org/repos/asf/tomcat/trunk/java/org/apache/catalina/loade.... I guarantee you this is being called far more often then logging is and it doesn't exhibit these problems.
The difference is that WebappClassLoader wouldn't suffer from the starvation effect because threads (at least if they are ordinary threads that don't perform manual classloading magic) will load all classes in a burst and then just execute. In that case, the optimization done in Java 6 will even increase the performance and there is no point in using a fair lock.
Your assumption here is wrong. Every call to new calls the class loader. In most applications objects are created far more than logging is called.
Logging, however, is potentially executed all the time during the life of the thread(s), probably even in a tight loop. This is were starvation is kicking in @tight loop.
That is no surprise to me.
The real culprit here is how much time is being spent inside the
synchronized block. When you look at the WebappClassLoader you will see that locks are held for very short periods of time. This is contrasted with Appenders that hold the lock for an extremely long period of time.
See *.
To prove this I simply added
for (int i=0; i < 100000; ++i) { if lock == null { throw new NullPointerException(); } }
before the synchronized or locked block (using lockObject instead of lock for the synchronzied block). While the counters don't come out equal they are very close. This shows that the problem is really how much time is spent in the synchronized block vs the actual application.
I don't understand this example. The whole point of the program is to show the starvation effect. The Thread.sleep is only included to be able to interrupt the thread and simulate some work.
Yes. And my point was to show that if your program logic is even somewhat substantial compared to the synchronized block that the starvation effect goes away. So changing Appenders to not have a single lock held for a long time to several locks held for short durations would minimize or eliminate this effect.
The significance of this is that even when you "fix" the locking so it is fair your application performance is still going to suck because it is spending so much time in the Appender vs doing actual work.
See *.
I assume you hadn't understood the point of LBCORE-97 until now... and please don't take that as an insult ;)
I the understanding of it. I just don't think it is the right way to fix the problem.
I wrote code under the assumption that synchronized would be at least sufficiently semi-fair, Ceki did in both LOG4J and Logback, and reality up until Java 1.6 on multi-core seemed to indicate that it's ok to assume that total fairness isn't necessary... but all this changed because synchronized is absolutely unfair now - and the documentation shows that the fault is on our side. We were simply working with wrong assumptions.
This just proves my point that locking should be handled by the logging system in a correct, i.e. fair, way. It is "downright dangerous" to assume that every person that implements an appender is aware of this situation and the problem will only show up on Java 1.6 multi-core systems. This is a *very* big risk.
But I agree 100% that the transformation/evaluation of the logging event can and should be handled outside of whatever fair locking mechanism is used. This would solve some issues and seems very logical. We should definitely aim for this in the future... Ideally, AppenderBase would be changed to support this, IMHO.
It would probably be a better idea to use a
protected final ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock(true);
fair ReentrantReadWriteLock in AppenderBase instead. doAppend would then use the write lock while actual implementations would have the freedom of using either read or write lock, as needed. This would remove the need of synchronizing the worker queue you mentioned earlier. I'm not sure if we really need a ReadWriteLock, though, as removal from the queue would be a write, too. But *some* appender implementation could have a use case for read-only-access...
One way to handle transformation of the event outside the lock is what I suggested in http://jira.qos.ch/browse/LBCLASSIC-45. Just dumb down LoggingEvent enough so that it already is detached from the rest of the system after it's created. But someone, and I honestly can't remember but I think it was you, said that it would be bad if the MDC was already transformed into Map<String,String> at that point...
I've just changed my patch to make lock protected instead of private. Seems to be a much better idea :p
And just to get my point straight once more: A logging system MUST NOT be a point of starvation! It will naturally always be a chokepoint but it MUST NEVER be a point of starvation!
On this we agree. If switching to a ReentrantReadWriteLock fixes the
problem for AppenderBase then I certainly don't have a problem with that. Personally, I see it as sort of a band-aid though. I'd like to go through and deal with all the code that isn't thread-safe and move the locking down to just those areas where it can't be avoided. Every reference you can find on multithreaded programming will tell you to hold locks for as short a duration as possible. Logback and Log4j don't do that which opens them up to these kinds of issues.
See * ;)
That's correct. And yes, it's some kind of a band-aid, albeit a very important one. Async logging has other downsides, though, as it will be unable to make sure that events are actually appended before the VM exits/crashes, for example. This *can* be a real downside.
I'm not talking about synchronous vs asynchronous logging. All appenders should be modified to use UnsynchronizedAppenderBase. This is a fair amount of work but will eliminate most, if not all, of these problems.
So in some cases, e.g. file appender, you'll *want* synchronous logging, no matter what the costs are. Moving the synchronization to a lower level wouldn't help much, IMO, since it would simply move the congestion and I don't really care if it happens while converting events or while actually writing the data to a file if writing has the performance impact. The application wouldn't be able to handle other logging, anyway. Unless I miss some important point, feel free to prove me wrong.
In the case of file appenders, the congestion will end up occuring in java.io somewhere. This is a good thing. It avoids deadlocks and any performance analysis will show the bottleneck as being there instead of in Logback.
Take a look at http://apps.sourceforge.net/trac/lilith/wiki/AppenderPitfalls if you haven't already and let me know if you find any mistakes, please.
Sure, when I get a chance this evening.
My big pain-point with UnsynchronizedAppenderBase is also it's main feature: It's leaving all the locking to the respective implementation. All I'm trying to say is that it's very easy to do that part wrong and that it won't be apparent if it *is* done wrong.
You'd be surprised how little locking is actually required. Before condemning this approach I would suggest you take a look at what it would actually require. Ralph

I would just like to observe that when FileAppender writes an event to a file, the said event must be written out completely as a single block. We can't have event A with an associated exception intermingled with event B in middle of A's stack trace. I'll look into Joern's example tomorrow. ralph.goers @dslextreme.com wrote:
On Mon, Jun 1, 2009 at 6:51 AM, Joern Huxhorn <jhuxhorn@googlemail.com <mailto:jhuxhorn@googlemail.com>> wrote:
But locking will occur at some point and has to be done fairly. Otherwise starvation will happen on multi-core systems.
Not if the synchronized blocks take a minimal amount of time. My modification to your sample app demonstrated that.
I can't imagine an appender that doesn't have to lock at *some* point. Do you have any use case for this?
Of course they will have to lock, possibly a few times. But if each lock is for a very short duration the system will behave much better.
It would be possible to generally use a fixed-size blocking queue (fair!) in case of UnsynchronizedAppenderBase, leaving the async handling of events put into the queue to the actual implementation. Assuming, of course, detached data objects again.
What I really want is *one* correct way of handling/implementing async appenders so the possibility of failure is reduced as much as possible. Nothing is worse than a logging system that crashes the logged application.
We agree on that.
I'm simulating the AppenderBase situation here, lots of threads are trying to access the very same resource. With Java 1.6 on multi-core systems, only <number of cores> threads will ever get the chance to obtain the object monitor. All other threads are starving. And this is a valid simplification because it shows exactly what's happening in our webapp right now. We had to disable logging to get it back into a usable state.
Using a fair ReentrantLock fixes this. It makes sure that threads are handled fairly concerning the shared resource, i.e. they are served in a "first come, first served" order.
The issue here isn't really that there is a problem with how synchronized locks work. If there were Tomcat would fall on its face. Take a look at https://svn.apache.org/repos/asf/tomcat/trunk/java/org/apache/catalina/loade.... I guarantee you this is being called far more often then logging is and it doesn't exhibit these problems.
The difference is that WebappClassLoader wouldn't suffer from the starvation effect because threads (at least if they are ordinary threads that don't perform manual classloading magic) will load all classes in a burst and then just execute. In that case, the optimization done in Java 6 will even increase the performance and there is no point in using a fair lock.
Your assumption here is wrong. Every call to new calls the class loader. In most applications objects are created far more than logging is called.
Logging, however, is potentially executed all the time during the life of the thread(s), probably even in a tight loop. This is were starvation is kicking in @tight loop.
That is no surprise to me.
The real culprit here is how much time is being spent inside the synchronized block. When you look at the WebappClassLoader you will see that locks are held for very short periods of time. This is contrasted with Appenders that hold the lock for an extremely long period of time.
See *.
To prove this I simply added
for (int i=0; i < 100000; ++i) { if lock == null { throw new NullPointerException(); } }
before the synchronized or locked block (using lockObject instead of lock for the synchronzied block). While the counters don't come out equal they are very close. This shows that the problem is really how much time is spent in the synchronized block vs the actual application.
I don't understand this example. The whole point of the program is to show the starvation effect. The Thread.sleep is only included to be able to interrupt the thread and simulate some work.
Yes. And my point was to show that if your program logic is even somewhat substantial compared to the synchronized block that the starvation effect goes away. So changing Appenders to not have a single lock held for a long time to several locks held for short durations would minimize or eliminate this effect.
The significance of this is that even when you "fix" the locking so it is fair your application performance is still going to suck because it is spending so much time in the Appender vs doing actual work.
See *.
I assume you hadn't understood the point of LBCORE-97 until now... and please don't take that as an insult ;)
I the understanding of it. I just don't think it is the right way to fix the problem.
I wrote code under the assumption that synchronized would be at least sufficiently semi-fair, Ceki did in both LOG4J and Logback, and reality up until Java 1.6 on multi-core seemed to indicate that it's ok to assume that total fairness isn't necessary... but all this changed because synchronized is absolutely unfair now - and the documentation shows that the fault is on our side. We were simply working with wrong assumptions.
This just proves my point that locking should be handled by the logging system in a correct, i.e. fair, way. It is "downright dangerous" to assume that every person that implements an appender is aware of this situation and the problem will only show up on Java 1.6 multi-core systems. This is a *very* big risk.
But I agree 100% that the transformation/evaluation of the logging event can and should be handled outside of whatever fair locking mechanism is used. This would solve some issues and seems very logical. We should definitely aim for this in the future... Ideally, AppenderBase would be changed to support this, IMHO.
It would probably be a better idea to use a
protected final ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock(true);
fair ReentrantReadWriteLock in AppenderBase instead. doAppend would then use the write lock while actual implementations would have the freedom of using either read or write lock, as needed. This would remove the need of synchronizing the worker queue you mentioned earlier. I'm not sure if we really need a ReadWriteLock, though, as removal from the queue would be a write, too. But *some* appender implementation could have a use case for read-only-access...
One way to handle transformation of the event outside the lock is what I suggested in http://jira.qos.ch/browse/LBCLASSIC-45. Just dumb down LoggingEvent enough so that it already is detached from the rest of the system after it's created. But someone, and I honestly can't remember but I think it was you, said that it would be bad if the MDC was already transformed into Map<String,String> at that point...
I've just changed my patch to make lock protected instead of private. Seems to be a much better idea :p
And just to get my point straight once more: A logging system MUST NOT be a point of starvation! It will naturally always be a chokepoint but it MUST NEVER be a point of starvation!
On this we agree. If switching to a ReentrantReadWriteLock fixes the problem for AppenderBase then I certainly don't have a problem with that. Personally, I see it as sort of a band-aid though. I'd like to go through and deal with all the code that isn't thread-safe and move the locking down to just those areas where it can't be avoided. Every reference you can find on multithreaded programming will tell you to hold locks for as short a duration as possible. Logback and Log4j don't do that which opens them up to these kinds of issues.
See * ;)
That's correct. And yes, it's some kind of a band-aid, albeit a very important one. Async logging has other downsides, though, as it will be unable to make sure that events are actually appended before the VM exits/crashes, for example. This *can* be a real downside.
I'm not talking about synchronous vs asynchronous logging. All appenders should be modified to use UnsynchronizedAppenderBase. This is a fair amount of work but will eliminate most, if not all, of these problems.
So in some cases, e.g. file appender, you'll *want* synchronous logging, no matter what the costs are. Moving the synchronization to a lower level wouldn't help much, IMO, since it would simply move the congestion and I don't really care if it happens while converting events or while actually writing the data to a file if writing has the performance impact. The application wouldn't be able to handle other logging, anyway. Unless I miss some important point, feel free to prove me wrong.
In the case of file appenders, the congestion will end up occuring in java.io <http://java.io> somewhere. This is a good thing. It avoids deadlocks and any performance analysis will show the bottleneck as being there instead of in Logback.
Take a look at http://apps.sourceforge.net/trac/lilith/wiki/AppenderPitfalls if you haven't already and let me know if you find any mistakes, please.
Sure, when I get a chance this evening.
My big pain-point with UnsynchronizedAppenderBase is also it's main feature: It's leaving all the locking to the respective implementation. All I'm trying to say is that it's very easy to do that part wrong and that it won't be apparent if it *is* done wrong.
You'd be surprised how little locking is actually required. Before condemning this approach I would suggest you take a look at what it would actually require.
Ralph
------------------------------------------------------------------------
_______________________________________________ logback-dev mailing list logback-dev@qos.ch http://qos.ch/mailman/listinfo/logback-dev
-- Ceki Gülcü Logback: The reliable, generic, fast and flexible logging framework for Java. http://logback.qos.ch

Just as an academic question, what do you think of an architecture where the various debug/info/error etc. calls on Logger all put something like a LoggingEvent (though probably an immutable equivalent) on a blocking queue, and a single thread takes events off the top of the queue and calls the appenders with it? Then appender calls would be done in a single threaded way, and so an appender implementation would not need to worry about thread safety. My take on the pros and cons of such an architecture: Pros: Using the proven j.u.concurrent queues for thread safety is simpler and safer than rolling your own No need for individual appender implementations to worry about thread safety An application thread isn't slowed down by the I/O involved in appending Cons: Makes your app multi-threaded even in a single threaded environment (mind you, using just about any JVM does this anyway...) The single consumer thread may be flooded by multiple producer application threads, particularly in a web app where you may have a large number of threads logging - that's a memory leak unless you use a bounded buffer, and if you use a bounded buffer then you may suddenly find yourself with random delays as the application threads block waiting for the logging consumer thread to take events off the queue. However, you could mitigate this by deliberately making particular appenders hand off their work asynchronous so that you had more threads doing the actual logging work. Not seriously suggesting that Logback makes such a radical change in architecture, just wondering what people thought of the concept - is there something I'm missing? Would the problem Joern is experiencing with multiple threads competing for a lock and only one of them getting the lock still occur?

On 31 May 2009, at 17:51, Robert Elliot wrote:
Just as an academic question, what do you think of an architecture where the various debug/info/error etc. calls on Logger all put something like a LoggingEvent (though probably an immutable equivalent) on a blocking queue, and a single thread takes events off the top of the queue and calls the appenders with it?
Then appender calls would be done in a single threaded way, and so an appender implementation would not need to worry about thread safety. ... Would the problem Joern is experiencing with multiple threads competing for a lock and only one of them getting the lock still occur?
I think I can answer my own question at the end there - yes, it might see the same problem if the bounded queue were full and there were multiple threads blocked waiting to log an event - you could see one thread go round and round and round being the only one adding to the queue while all the others sit there, if it were back waiting to log again before the consumer thread had managed to take another event off the top of the queue. But ArrayBlockingQueue can be made fair via a constructor parameter, in which case once the queue is full threads will get to add to it in the order they started waiting to do so and so the problem goes away.

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- I replied on the dev list because this discussion is unrelated to the bug.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn updated LBCORE-97: -------------------------------- Attachment: AppenderBaseLock2.patch Same patch as before with the exception that lock is now protected instead of private so extending classes can (and should!) use that same lock for any synchronization.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock.patch, AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn updated LBCORE-97: -------------------------------- Attachment: (was: AppenderBaseLock.patch)
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn edited comment on LBCORE-97 at 5/31/09 3:02 PM: -------------------------------------------------------------- Patch fixing the starvation problem using a fair ReentrantLock. Same patch as before with the exception that lock is now protected instead of private so extending classes can (and should!) use that same lock for any synchronization. was (Author: jhuxhorn): Same patch as before with the exception that lock is now protected instead of private so extending classes can (and should!) use that same lock for any synchronization.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- I've written an article about this problem in the Lilith wiki: http://apps.sourceforge.net/trac/lilith/wiki/SynchronizedVsFairLock
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu commented on LBCORE-97: ---------------------------------- Joern, thank you for supplying SynchronizedVsFairLock. Here are my results running SynchronizedVsFairLock. Only results for SynchronizedRunnable are shown. Environment: java.runtime.name = Java(TM) SE Runtime Environment java.runtime.version = 1.6.0_05-b13 java.vendor = Sun Microsystems Inc. java.version = 1.6.0_05 os.name = Windows XP os.version = 5.1 os.arch = x86 ########################################## Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=97, running=false] runnables[1]: SynchronizedRunnable[counter=43, running=false] runnables[2]: SynchronizedRunnable[counter=33, running=false] runnables[3]: SynchronizedRunnable[counter=96, running=false] runnables[4]: SynchronizedRunnable[counter=99, running=false] runnables[5]: SynchronizedRunnable[counter=134, running=false] runnables[6]: SynchronizedRunnable[counter=184, running=false] runnables[7]: SynchronizedRunnable[counter=78, running=false] runnables[8]: SynchronizedRunnable[counter=131, running=false] runnables[9]: SynchronizedRunnable[counter=44, running=false] Environment: java.runtime.name = Java(TM) SE Runtime Environment java.runtime.version = 1.6.0_11-b03 java.vendor = Sun Microsystems Inc. java.version = 1.6.0_11 java.vm.name = Java HotSpot(TM) 64-Bit Server VM java.vm.info = mixed mode os.name = Linux os.version = 2.6.25-gentoo-r6 os.arch = amd64 ########################################## About to execute usingSynchronized... Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=995, running=false] runnables[1]: SynchronizedRunnable[counter=1, running=false] runnables[2]: SynchronizedRunnable[counter=1, running=false] runnables[3]: SynchronizedRunnable[counter=1, running=false] runnables[4]: SynchronizedRunnable[counter=1, running=false] runnables[5]: SynchronizedRunnable[counter=1, running=false] runnables[6]: SynchronizedRunnable[counter=1, running=false] runnables[7]: SynchronizedRunnable[counter=1, running=false] runnables[8]: SynchronizedRunnable[counter=1, running=false] runnables[9]: SynchronizedRunnable[counter=1, running=false] Environment: java.runtime.name = Java(TM) SE Runtime Environment java.runtime.version = jvmxa6460-20081105_25433 java.vendor = IBM Corporation java.version = 1.6.0 java.vm.name = IBM J9 VM java.vm.info = J2RE 1.6.0 IBM J9 2.4 Linux amd64-64 jvmxa6460-20081105_25433 (JIT enabled, AOT enabled) J9VM - 20081105_025433_LHdSMr JIT - r9_20081031_1330 GC - 20081027_AB os.name = Linux os.version = 2.6.25-gentoo-r6 os.arch = amd64 ########################################## About to execute usingSynchronized... Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=36, running=false] runnables[1]: SynchronizedRunnable[counter=148, running=false] runnables[2]: SynchronizedRunnable[counter=192, running=false] runnables[3]: SynchronizedRunnable[counter=26, running=false] runnables[4]: SynchronizedRunnable[counter=124, running=false] runnables[5]: SynchronizedRunnable[counter=62, running=false] runnables[6]: SynchronizedRunnable[counter=122, running=false] runnables[7]: SynchronizedRunnable[counter=32, running=false] runnables[8]: SynchronizedRunnable[counter=27, running=false] runnables[9]: SynchronizedRunnable[counter=137, running=false] The results show that on Windows and Sun's JDK 1.6, the various threads compete fairly for the monitor, whereas on a 64bit Dual Core AMD Opteron running Linux and Sun's JDK 1.6, the first thread almost always wins the monitor while the other threads go starving. On the same Dual Core AMD machine changing to IBM's JDK 1.6.0, the results while not completely fair are not as lopsided as with Sun's JDK. I am tempted to say that it's a bug in Sun JDK. While we are not allowed to assume any particular ordering between competing threads, the behavior displayed by Sun's JDK is too extreme to be considered reasonable. Regardless of what I or anyone else thinks about Sun's 1.6. scheduling behavior, it should be considered as an established fact that we have to deal with. The question is whether we ignore it until Sun comes to their senses, or if we apply Joern's patch.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

Ceki Gulcu (JIRA) skrev:
I am tempted to say that it's a bug in Sun JDK. While we are not allowed to assume any particular ordering between competing threads, the behavior displayed by Sun's JDK is too extreme to be considered reasonable.
If you think so, I suggest you open an issue in the OpenJDK bug tracker so it can be taken care of for Java 7. -- Thorbjørn Ravn Andersen "...plus... Tubular Bells!"

Thorbjoern Ravn Andersen wrote:
Ceki Gulcu (JIRA) skrev:
I am tempted to say that it's a bug in Sun JDK. While we are not allowed to assume any particular ordering between competing threads, the behavior displayed by Sun's JDK is too extreme to be considered reasonable.
If you think so, I suggest you open an issue in the OpenJDK bug tracker so it can be taken care of for Java 7.
I'll do that. In the mean time, I made an entry in my blog: http://ceki.blogspot.com/2009/06/biased-locking-in-java-se-60.html -- Ceki Gülcü Logback: The reliable, generic, fast and flexible logging framework for Java. http://logback.qos.ch

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- Have you done any benchmarking concerning the performance impact of the fair ReentrantLock? I guess you have logback benchmarks at your fingertips, I'd be quite interested in the numbers. Concerning the "bug in the Sun JDK" part: It's the same way in case of the Mac version of Java 6. I highly doubt that Sun will come to it's senses ;) and I'm not even sure that it can be validly called a bug since it doesn't breach any documented behavior. This might really be a valid performance enhancement because switching threads means degrading performance. I'd be really grateful if this patch could be applied before the next release. We are suffering quite bad because of it. In our case it's - as mentioned - a Solaris and we can't just switch the VM or the OS as we like because of customer rules.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

Hello all, While I have not tested any of the scenario's described above, I think the remarks from Ralph make a lot of sense. And I am also interested in knowing the impact on throughput of using fair locks. When the fairness of non-fair locks has changed so drastically with 1.6, I would guess that more people would suffer from it, no ? Logback is certainly not the only code using non-fair locks. I googled a bit about "java 1.6 starvation" but nothing really alarming came up. regards, Maarten On Thu, Jun 4, 2009 at 7:12 PM, Joern Huxhorn (JIRA) <noreply-jira@qos.ch>wrote:
[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is...]
Joern Huxhorn commented on LBCORE-97: -------------------------------------
Have you done any benchmarking concerning the performance impact of the fair ReentrantLock?
I guess you have logback benchmarks at your fingertips, I'd be quite interested in the numbers.
Concerning the "bug in the Sun JDK" part: It's the same way in case of the Mac version of Java 6. I highly doubt that Sun will come to it's senses ;) and I'm not even sure that it can be validly called a bug since it doesn't breach any documented behavior. This might really be a valid performance enhancement because switching threads means degrading performance.
I'd be really grateful if this patch could be applied before the next release. We are suffering quite bad because of it. In our case it's - as mentioned - a Solaris and we can't just switch the VM or the OS as we like because of customer rules.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll()<http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll%28%29> [3] http://en.wikipedia.org/wiki/Starvation_(computing)<http://en.wikipedia.org/wiki/Starvation_%28computing%29> [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira
_______________________________________________ logback-dev mailing list logback-dev@qos.ch http://qos.ch/mailman/listinfo/logback-dev

Maarten Bosteels wrote:
Hello all,
While I have not tested any of the scenario's described above, I think the remarks from Ralph make a lot of sense. And I am also interested in knowing the impact on throughput of using fair locks.
When the fairness of non-fair locks has changed so drastically with 1.6, I would guess that more people would suffer from it, no ? Logback is certainly not the only code using non-fair locks. I googled a bit about "java 1.6 starvation" but nothing really alarming came up.
regards, Maarten
I just googled for "java synchronized fairness" and the first two links that came up were http://tutorials.jenkov.com/java-concurrency/starvation-and-fairness.html and http://tutorials.jenkov.com/java-concurrency/locks.html I've also found the following PDF: http://home.comcast.net/~pjbishop/Dave/MustangSync.pdf See page 12: Fairness vs throughput
Optimized for system-wide throughput at the expensive of short-term thread-specific fairness Succession policy: try to wake recently run threads Improved $ and TLB utilization
TLB means http://en.wikipedia.org/wiki/Translation_lookaside_buffer This post from the author of the PDF is quite technical but on the topic: http://blogs.sun.com/dave/entry/biased_locking_in_hotspot So it is a performance enhancement as I guessed. I highly doubt that this will be reverted. I'd more or less expect the other implementations to follow. After all, this is *not* a bug at all. This behavior, while obviously being extreme, is absolutely in conformance with all documentation. So if it doesn't breach the documentation contract and means a worthwhile performance increase: why switch it back? Regards, Joern.

Joern Huxhorn wrote:
So it is a performance enhancement as I guessed. I highly doubt that this will be reverted. I'd more or less expect the other implementations to follow.
After all, this is *not* a bug at all. This behavior, while obviously being extreme, is absolutely in conformance with all documentation. So if it doesn't breach the documentation contract and means a worthwhile performance increase: why switch it back?
Given the highly technical nature of the subject, it is possible that the change went through without anyone noticing its negative effects. Unfortunately, such wide-ranging mistakes happen all the time. As for the argument about no guarantees offered about fairness, while true in letter, it is invalid in spirit. The argument is too punctilious and formalistic. Any developer reading the docs will naturally assume that while there are no guarantees, the synchronization mechanism will not actively impede fairness. In JDK 1.6, the scheduler will systematically favor the thread last holding the lock so that it will always get access to the synchronized resource. Sun calls it biased locking. Biased locking, as introduced in JDK 1.6, will effect thousands of unsuspecting applications. The fact that the bias is intentional does not make it less damaging. It's still a bug. Having said that, I also agree that Sun will not revert to the previous locking behavior anytime soon. I intend to complain about this change as loudly as I can. In the mean time, we'll have to accommodate JDK 1.6 by adopting fair locking primitives as provided by java.util.concurrent.
Regards, Joern.
-- Ceki Gülcü Logback: The reliable, generic, fast and flexible logging framework for Java. http://logback.qos.ch

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Philippe Marschall commented on LBCORE-97: ------------------------------------------ Standard java synchronization was never guaranteed to be fair. Java 6 (or the OS scheduler) is simply doing more optimizations that it didn't do previously but was always allowed to do. However I don't see why this is a problem unless logging is your bottleneck because there is no real ordering in multi thread applications anyway. If logging is your bottleneck I see this as a serious issue because synchronous logging will in this case turn your application into a single threaded one and you're effectively producing logging events faster than you can log them. If this is the case I'd investigate why you're spending so much time in logging and if that's unfixable change to asynchronous logging. Sure it doesn't work out of the box with MDCs but there are ways to solve that. Either copy the MDC or format the message in the calling thread (without aquiring a lock) and adding the formatted instead of the unformatted string to the queue.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: -------------------------------------
Standard java synchronization was never guaranteed to be fair. Java 6 (or the OS scheduler) is simply doing more optimizations that it didn't do previously but was always allowed to do.
+1
However I don't see why this is a problem unless logging is your bottleneck
Logging is, indeed, the bottleneck of our application at the moment, but this is expected, "by design" and will change at a later time. - We are rolling out a new version of our app so logging is still unusually high - We are doing a proprietary CMS with XSLT as the template language. There are several XSLT developers receiving compilation specific INFO, WARN and ERROR events via my MulticastSocketAppender. - The slowest receiver essentially dictates the overall speed of the application
If this is the case I'd investigate why you're spending so much time in logging and if that's unfixable change to asynchronous logging.
The MultiplexAppender is already semi-asynchronous, meaning that it has a buffer of fixed size that is filled before the appender blocks. See http://apps.sourceforge.net/trac/lilith/wiki/AppenderPitfalls We've done this "abusive" logging for over a year with *very* acceptable performance. Then we moved from a single-core Java 1.5 server to a multi-core Java 1.6 server and, suddenly, performance was extremely crappy. I narrowed the problem down to the issue discussed here and never really expected any discussion whether this patch should be applied or not because - honestly - I think I did a pretty good job citing all relevant documentation, showing up that we all worked with false presumptions - and this explicitly includes myself! I *knew* that fairness wasn't guaranteed but I *assumed* that something like nearly-fair or somewhat-unfair could be expected. This assumption was wrong.
because synchronous logging will in this case turn your application into a single threaded one
There's some truth in this statement.;) However, large amounts of code won't log anything while other parts log in relatively big bursts so, theoretically, all CPUs should be able to work quite a bit, anyway, even if parts of the application are running semi-single-threaded.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- FYI, I changed MultiplexSocketAppenderBase to extend UnsynchronizedAppenderBase instead of AppenderBase to circumvent this problem. Locking is now solely handled by a fair (!!) BlockingQueue. This issue is still valid, though, since it's also apparent - to a lesser degree - if only file or console appender is used. In addition to applying my patch I'd also strongly suggest that a comment is added to UnsynchronizedAppenderBase stating that any locking performed by an extending class *must* be fair to prevent starvation. This is really the case, please believe me. If this comment is added to UnsynchronizedAppenderBase, an extending class is effectively breaching contract if locking differently. A logging system is, in fact, a prime example where locking just *has* to be fair to prevent starvation since multiple threads *are* trying to access the same resource (Logger/Appender) all the time. http://java.sun.com/docs/books/tutorial/essential/concurrency/starvelive.htm...
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ralph Goers commented on LBCORE-97: ----------------------------------- I think that your recommendation makes sense if for no other reason than to make anyone extending the class aware of the issue. I tend to agree that logging, when enabled, is a high overhead activity and is far more likely to experience this behavior than most other scenarios. It is too bad that this behavior can't be controlled through an annotation.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu commented on LBCORE-97: ---------------------------------- Have you noticed that the starvation issue arises 1) only if the sleep duration is 3 milliseconds and over (on Linux) 2) with JDK 1.5 as well ? I had a long exchange with Dave Dice on the topic of <a href="http://ceki.blogspot.com/2009/06/biased-locking-in-java-se-60.html">biased locking</a> in my blog. Apparently, the issue is not JDK 1.6 specific and was present in older JVMs as well. Given that logback usually does not hold the lock (on itself) for more than 10 microseconds, there starvation condition should not occur in practice. (This assertion applies to tests on Linux and Windows). I'd apprecite if the 3 millisecond threshold could be confirmed for OS X and Solaris. To set a lower sleep duration, within the run() method of SynchronizedRunnable modify Thread.sleep(10); to Thread.sleep(1); or even a smaller value: Thread.sleep(0, 10*1000); // 10 microseconds
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu edited comment on LBCORE-97 at 6/8/09 9:38 PM: ---------------------------------------------------------- Have you noticed that the starvation issue arises 1) only if the sleep duration is 3 milliseconds and over (on Linux) 2) with JDK 1.5 as well ? I had a long exchange with Dave Dice on the topic of biased locking [1] in my blog. Apparently, the issue is not JDK 1.6 specific and was present in older JVMs as well. Given that logback usually does not hold the lock (on itself) for more than 10 microseconds, there starvation condition should not occur in practice. (This assertion applies to tests on Linux and Windows). I'd apprecite if the 3 millisecond threshold could be confirmed for OS X and Solaris. To set a lower sleep duration, within the run() method of SynchronizedRunnable modify Thread.sleep(10); to Thread.sleep(1); or even a smaller value: Thread.sleep(0, 10*1000); // 10 microseconds [1] http://ceki.blogspot.com/2009/06/biased-locking-in-java-se-60.html was (Author: noreply.ceki@qos.ch): Have you noticed that the starvation issue arises 1) only if the sleep duration is 3 milliseconds and over (on Linux) 2) with JDK 1.5 as well ? I had a long exchange with Dave Dice on the topic of <a href="http://ceki.blogspot.com/2009/06/biased-locking-in-java-se-60.html">biased locking</a> in my blog. Apparently, the issue is not JDK 1.6 specific and was present in older JVMs as well. Given that logback usually does not hold the lock (on itself) for more than 10 microseconds, there starvation condition should not occur in practice. (This assertion applies to tests on Linux and Windows). I'd apprecite if the 3 millisecond threshold could be confirmed for OS X and Solaris. To set a lower sleep duration, within the run() method of SynchronizedRunnable modify Thread.sleep(10); to Thread.sleep(1); or even a smaller value: Thread.sleep(0, 10*1000); // 10 microseconds
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu edited comment on LBCORE-97 at 6/8/09 9:39 PM: ---------------------------------------------------------- Have you noticed that the starvation issue arises 1) only if the sleep duration is 3 milliseconds and over (on Linux) 2) with JDK 1.5 as well ? I had a long exchange with Dave Dice on the topic of biased locking [1] in my blog. Apparently, the issue is not JDK 1.6 specific and was present in older JVMs as well. Given that logback usually does not hold the lock (on itself) for more than 10 microseconds, the starvation condition should not occur in practice. (This assertion is based on experimentation done on Linux and Windows). I'd appreciate if the 3 millisecond threshold could be confirmed for OS X and Solaris. To set a lower sleep duration, within the run() method of SynchronizedRunnable modify Thread.sleep(10); to Thread.sleep(1); or even a smaller value: Thread.sleep(0, 10*1000); // 10 microseconds [1] http://ceki.blogspot.com/2009/06/biased-locking-in-java-se-60.html was (Author: noreply.ceki@qos.ch): Have you noticed that the starvation issue arises 1) only if the sleep duration is 3 milliseconds and over (on Linux) 2) with JDK 1.5 as well ? I had a long exchange with Dave Dice on the topic of biased locking [1] in my blog. Apparently, the issue is not JDK 1.6 specific and was present in older JVMs as well. Given that logback usually does not hold the lock (on itself) for more than 10 microseconds, there starvation condition should not occur in practice. (This assertion applies to tests on Linux and Windows). I'd apprecite if the 3 millisecond threshold could be confirmed for OS X and Solaris. To set a lower sleep duration, within the run() method of SynchronizedRunnable modify Thread.sleep(10); to Thread.sleep(1); or even a smaller value: Thread.sleep(0, 10*1000); // 10 microseconds [1] http://ceki.blogspot.com/2009/06/biased-locking-in-java-se-60.html
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- I was able to get the following results after several runs of my modified SynchronizedVsFairLock ( http://apps.sourceforge.net/trac/lilith/browser/trunk/misc/synchronized-lock... ). In other cases, locking wasn't as unfair as the numbers below. The starvation behavior isn't as reproducible as before but it's still there. The chance of starvation increases with the amount of delay. Given that there are Appenders that are candidates for a delay > 3ms, e.g. SocketAppender, DBAppender, SMTPAppender and even FileAppender (especially if the log file is located on a NAS), I'd still strongly suggest to change AppenderBase according to my suggestion. Why should really aim to switch over to a "locking as needed" strategy, as Ralph suggested. Changing more and more appenders to extend UnsynchronizedAppenderBase and handling locking - in a fair way - as needed by the respective implementation. Depending on unfair locking is IMO a risk that is way to high and is, essentially, wrong. Concerning "real life experience", leaving my proof of problem aside, I can tell you that the starvation effect is still happening with a RollingFileAppender in place, it's just not as bad as with a SocketAppender. Taking into account LBCORE-62 and LBCORE-96, I think it's save to assume that this is a general problem. My results for Mac OS are below. I can't test on Solaris right now because I'm on vacation. Environment: delay = 3 java.runtime.name = Java(TM) SE Runtime Environment java.runtime.version = 1.6.0_07-b06-153 java.vendor = Apple Inc. java.version = 1.6.0_07 java.vm.name = Java HotSpot(TM) 64-Bit Server VM java.vm.info = mixed mode os.name = Mac OS X os.version = 10.5.7 os.arch = x86_64 ########################################## About to execute usingSynchronized... Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=967, running=false, delay=3] runnables[1]: SynchronizedRunnable[counter=155, running=false, delay=3] runnables[2]: SynchronizedRunnable[counter=753, running=false, delay=3] runnables[3]: SynchronizedRunnable[counter=583, running=false, delay=3] runnables[4]: SynchronizedRunnable[counter=471, running=false, delay=3] runnables[5]: SynchronizedRunnable[counter=367, running=false, delay=3] runnables[6]: SynchronizedRunnable[counter=1, running=false, delay=3] runnables[7]: SynchronizedRunnable[counter=1, running=false, delay=3] runnables[8]: SynchronizedRunnable[counter=1, running=false, delay=3] runnables[9]: SynchronizedRunnable[counter=2, running=false, delay=3] ########################################## About to execute usingUnfairLock... Results for usingUnfairLock: runnables[0]: LockRunnable[counter=375, running=false, delay=3] runnables[1]: LockRunnable[counter=51, running=false, delay=3] runnables[2]: LockRunnable[counter=1154, running=false, delay=3] runnables[3]: LockRunnable[counter=38, running=false, delay=3] runnables[4]: LockRunnable[counter=1583, running=false, delay=3] runnables[5]: LockRunnable[counter=91, running=false, delay=3] runnables[6]: LockRunnable[counter=1, running=false, delay=3] runnables[7]: LockRunnable[counter=1, running=false, delay=3] runnables[8]: LockRunnable[counter=1, running=false, delay=3] runnables[9]: LockRunnable[counter=1, running=false, delay=3] ########################################## About to execute usingFairLock... Results for usingFairLock: runnables[0]: LockRunnable[counter=328, running=false, delay=3] runnables[1]: LockRunnable[counter=328, running=false, delay=3] runnables[2]: LockRunnable[counter=328, running=false, delay=3] runnables[3]: LockRunnable[counter=328, running=false, delay=3] runnables[4]: LockRunnable[counter=328, running=false, delay=3] runnables[5]: LockRunnable[counter=328, running=false, delay=3] runnables[6]: LockRunnable[counter=328, running=false, delay=3] runnables[7]: LockRunnable[counter=328, running=false, delay=3] runnables[8]: LockRunnable[counter=328, running=false, delay=3] runnables[9]: LockRunnable[counter=328, running=false, delay=3] ##########################################
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn edited comment on LBCORE-97 at 6/9/09 8:41 AM: ------------------------------------------------------------- I was able to get the following results after several runs of my modified SynchronizedVsFairLock ( http://apps.sourceforge.net/trac/lilith/browser/trunk/misc/synchronized-lock... ). In other cases, locking wasn't as unfair as the numbers below. The starvation behavior isn't as reproducible as before but it's still there. The chance of starvation increases with the amount of delay. Given that there are Appenders that are candidates for a delay > 3ms, e.g. SocketAppender, DBAppender, SMTPAppender and even FileAppender (especially if the log file is located on a NAS), I'd still strongly suggest to change AppenderBase according to my suggestion. We should really aim to switch over to a "locking as needed" strategy, as Ralph suggested. Changing more and more appenders to extend UnsynchronizedAppenderBase and handling locking - in a fair way - as needed by the respective implementation. Depending on unfair locking is IMO a risk that is way to high and is, essentially, wrong. Concerning "real life experience", leaving my proof of problem aside, I can tell you that the starvation effect is still happening with a RollingFileAppender in place, it's just not as bad as with a SocketAppender. Taking into account LBCORE-62 and LBCORE-96, I think it's save to assume that this is a general problem. My results for Mac OS are below. I can't test on Solaris right now because I'm on vacation. Environment: delay = 3 java.runtime.name = Java(TM) SE Runtime Environment java.runtime.version = 1.6.0_07-b06-153 java.vendor = Apple Inc. java.version = 1.6.0_07 java.vm.name = Java HotSpot(TM) 64-Bit Server VM java.vm.info = mixed mode os.name = Mac OS X os.version = 10.5.7 os.arch = x86_64 ########################################## About to execute usingSynchronized... Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=967, running=false, delay=3] runnables[1]: SynchronizedRunnable[counter=155, running=false, delay=3] runnables[2]: SynchronizedRunnable[counter=753, running=false, delay=3] runnables[3]: SynchronizedRunnable[counter=583, running=false, delay=3] runnables[4]: SynchronizedRunnable[counter=471, running=false, delay=3] runnables[5]: SynchronizedRunnable[counter=367, running=false, delay=3] runnables[6]: SynchronizedRunnable[counter=1, running=false, delay=3] runnables[7]: SynchronizedRunnable[counter=1, running=false, delay=3] runnables[8]: SynchronizedRunnable[counter=1, running=false, delay=3] runnables[9]: SynchronizedRunnable[counter=2, running=false, delay=3] ########################################## About to execute usingUnfairLock... Results for usingUnfairLock: runnables[0]: LockRunnable[counter=375, running=false, delay=3] runnables[1]: LockRunnable[counter=51, running=false, delay=3] runnables[2]: LockRunnable[counter=1154, running=false, delay=3] runnables[3]: LockRunnable[counter=38, running=false, delay=3] runnables[4]: LockRunnable[counter=1583, running=false, delay=3] runnables[5]: LockRunnable[counter=91, running=false, delay=3] runnables[6]: LockRunnable[counter=1, running=false, delay=3] runnables[7]: LockRunnable[counter=1, running=false, delay=3] runnables[8]: LockRunnable[counter=1, running=false, delay=3] runnables[9]: LockRunnable[counter=1, running=false, delay=3] ########################################## About to execute usingFairLock... Results for usingFairLock: runnables[0]: LockRunnable[counter=328, running=false, delay=3] runnables[1]: LockRunnable[counter=328, running=false, delay=3] runnables[2]: LockRunnable[counter=328, running=false, delay=3] runnables[3]: LockRunnable[counter=328, running=false, delay=3] runnables[4]: LockRunnable[counter=328, running=false, delay=3] runnables[5]: LockRunnable[counter=328, running=false, delay=3] runnables[6]: LockRunnable[counter=328, running=false, delay=3] runnables[7]: LockRunnable[counter=328, running=false, delay=3] runnables[8]: LockRunnable[counter=328, running=false, delay=3] runnables[9]: LockRunnable[counter=328, running=false, delay=3] ########################################## was (Author: jhuxhorn): I was able to get the following results after several runs of my modified SynchronizedVsFairLock ( http://apps.sourceforge.net/trac/lilith/browser/trunk/misc/synchronized-lock... ). In other cases, locking wasn't as unfair as the numbers below. The starvation behavior isn't as reproducible as before but it's still there. The chance of starvation increases with the amount of delay. Given that there are Appenders that are candidates for a delay > 3ms, e.g. SocketAppender, DBAppender, SMTPAppender and even FileAppender (especially if the log file is located on a NAS), I'd still strongly suggest to change AppenderBase according to my suggestion. Why should really aim to switch over to a "locking as needed" strategy, as Ralph suggested. Changing more and more appenders to extend UnsynchronizedAppenderBase and handling locking - in a fair way - as needed by the respective implementation. Depending on unfair locking is IMO a risk that is way to high and is, essentially, wrong. Concerning "real life experience", leaving my proof of problem aside, I can tell you that the starvation effect is still happening with a RollingFileAppender in place, it's just not as bad as with a SocketAppender. Taking into account LBCORE-62 and LBCORE-96, I think it's save to assume that this is a general problem. My results for Mac OS are below. I can't test on Solaris right now because I'm on vacation. Environment: delay = 3 java.runtime.name = Java(TM) SE Runtime Environment java.runtime.version = 1.6.0_07-b06-153 java.vendor = Apple Inc. java.version = 1.6.0_07 java.vm.name = Java HotSpot(TM) 64-Bit Server VM java.vm.info = mixed mode os.name = Mac OS X os.version = 10.5.7 os.arch = x86_64 ########################################## About to execute usingSynchronized... Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=967, running=false, delay=3] runnables[1]: SynchronizedRunnable[counter=155, running=false, delay=3] runnables[2]: SynchronizedRunnable[counter=753, running=false, delay=3] runnables[3]: SynchronizedRunnable[counter=583, running=false, delay=3] runnables[4]: SynchronizedRunnable[counter=471, running=false, delay=3] runnables[5]: SynchronizedRunnable[counter=367, running=false, delay=3] runnables[6]: SynchronizedRunnable[counter=1, running=false, delay=3] runnables[7]: SynchronizedRunnable[counter=1, running=false, delay=3] runnables[8]: SynchronizedRunnable[counter=1, running=false, delay=3] runnables[9]: SynchronizedRunnable[counter=2, running=false, delay=3] ########################################## About to execute usingUnfairLock... Results for usingUnfairLock: runnables[0]: LockRunnable[counter=375, running=false, delay=3] runnables[1]: LockRunnable[counter=51, running=false, delay=3] runnables[2]: LockRunnable[counter=1154, running=false, delay=3] runnables[3]: LockRunnable[counter=38, running=false, delay=3] runnables[4]: LockRunnable[counter=1583, running=false, delay=3] runnables[5]: LockRunnable[counter=91, running=false, delay=3] runnables[6]: LockRunnable[counter=1, running=false, delay=3] runnables[7]: LockRunnable[counter=1, running=false, delay=3] runnables[8]: LockRunnable[counter=1, running=false, delay=3] runnables[9]: LockRunnable[counter=1, running=false, delay=3] ########################################## About to execute usingFairLock... Results for usingFairLock: runnables[0]: LockRunnable[counter=328, running=false, delay=3] runnables[1]: LockRunnable[counter=328, running=false, delay=3] runnables[2]: LockRunnable[counter=328, running=false, delay=3] runnables[3]: LockRunnable[counter=328, running=false, delay=3] runnables[4]: LockRunnable[counter=328, running=false, delay=3] runnables[5]: LockRunnable[counter=328, running=false, delay=3] runnables[6]: LockRunnable[counter=328, running=false, delay=3] runnables[7]: LockRunnable[counter=328, running=false, delay=3] runnables[8]: LockRunnable[counter=328, running=false, delay=3] runnables[9]: LockRunnable[counter=328, running=false, delay=3] ##########################################
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- Below are value for a delay of 1: Environment: delay = 1 java.runtime.name = Java(TM) SE Runtime Environment java.runtime.version = 1.6.0_07-b06-153 java.vendor = Apple Inc. java.version = 1.6.0_07 java.vm.name = Java HotSpot(TM) 64-Bit Server VM java.vm.info = mixed mode os.name = Mac OS X os.version = 10.5.7 os.arch = x86_64 ########################################## About to execute usingSynchronized... Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=1005, running=false, delay=1] runnables[1]: SynchronizedRunnable[counter=541, running=false, delay=1] runnables[2]: SynchronizedRunnable[counter=796, running=false, delay=1] runnables[3]: SynchronizedRunnable[counter=1669, running=false, delay=1] runnables[4]: SynchronizedRunnable[counter=1543, running=false, delay=1] runnables[5]: SynchronizedRunnable[counter=638, running=false, delay=1] runnables[6]: SynchronizedRunnable[counter=197, running=false, delay=1] runnables[7]: SynchronizedRunnable[counter=212, running=false, delay=1] runnables[8]: SynchronizedRunnable[counter=2587, running=false, delay=1] runnables[9]: SynchronizedRunnable[counter=486, running=false, delay=1] While this isn't as critical and as obvious as before, the difference between the lowest value 197 and the highest value 2587 is still very high. The values should be relatively equal, after all, especially since the only thing preventing this is, in reality, the logging system. That's a big no-no in my books.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn updated LBCORE-97: -------------------------------- Attachment: Suggested_change_of_documentation.patch Quick try at improving documentation of UnsynchronizedAppenderBase.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, Suggested_change_of_documentation.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu commented on LBCORE-97: ---------------------------------- Thank you for these new figures and I hope you enjoy your holidays.
Given that there are Appenders that are candidates for a delay > 3ms, e.g. SocketAppender, DBAppender, SMTPAppender and even FileAppender (especially if the log file is located on a NAS), I'd still strongly suggest to change AppenderBase according to my suggestion.
Only DBAppender has a latency in the order of a millisecond. SMTPAppender causes delays in the order of a millisecond only when it sends email, which is rare and thus can be ignored. SocketAppender has a throughput of over 20'000 messages per second, translating to a latency of less than 50 microseconds. FileAppender, when writing to a local file, has a throughput of over 100'000 messages per second, corresponding to a latency of less than 10 microseconds. FileAppender writing on a networked file system is an interesting case because delays can be significantly larger. However, unless the network awfully slow, we would still be situated well below the millisecond range. By the way, 3 ms (milliseconds) is the threshold where things go awry, at least on Linux. At 2ms and lower, the time slicer distributes resources rather fairly.
While this isn't as critical and as obvious as before, the difference between the lowest value 197 and the highest value 2587 is still very high. The values should be relatively equal, after all, especially since the only thing preventing this is, in reality, the logging system.
Yes, the ration between 2587 and 195, that is 13.13, is rather uneven. We could reasonably begin to talk about starvation. Nevertheless, one could also argue that, if you remove the two extreme values, 195 and 2597, and if you draw a frequency distribution where the x-axis represents frequency groupings of width 300, starting at 0 ending at 1500, you get a graph looking like a nice bell curve. (I've actually done this.) Note that if you reduce the delay Thread.sleep() to the range of 10 microseconds, which in my opinion is more representative, you will see that the numbers are distributed more smoothly. Also note that, and this is quite important, a normal application would not spend all its time logging, it would have other activities which would cause the thread last holding the "appender" lock to lose it in favor of a competing thread. You seem to be fairly convinced that the starvation problem observed in your application is due to logging. What makes you think this? Have you observed improved behavior when logging is disabled? You could replace logback-classic.jar with slf4j-nop.jar just to test. As for a pluggable locking mechanism, it is an interesting idea. However, I would first like to establish that there is actually a problem in logback, especially as Java itself is not a real-time language.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, Suggested_change_of_documentation.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- It's definitely related to logging. We haven't replaced logback-classic with slf4j-nop,yet, but we have removed all appenders and the behavior of the app went back to normal. I'm really convinced that the problem described in this issue is the root cause since the problems started when we switched to a multi-core system. Concerning SocketAppender, I'm wondering about the use case where it's showing less than 50 microseconds latency. Are you running it against the loopback device? In our case it's running over a VPN so a certain slowdown is expected. The problem seems to be that: a) some delay is happening, either due to a slow client (SocketAppender will block if the client isn't reading fast enough), network latency, large bursts of log-messages or a combination of all that. b) this delay sends the logged application into "starvation mode" because the critical time, be it 1ms, 3ms or 10ms was reached. It's not acceptable that certain threads are starving because of any of the above. The app, as a whole, may slow down significantly but starvation must not happen. This is a prime-example of starvation where multiple threads are fighting over scarce resources, in this case just one, the appender. To prevent starvation, fairness must be established. Have you had the chance to do benchmarks about the impact of the fair ReentrantLock, yet? If the impact is negligible than this should be changed ASAP. Are we really in disagreement that logging mustn't impact the execution of an application in a way that can result in starvation?
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, Suggested_change_of_documentation.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- Additional info: As I said before, this does happen with just a simple RollingFileAppender in place, too. This may be caused by some huge debug messages but this shouldn't be able to render the whole application unusable.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, Suggested_change_of_documentation.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu commented on LBCORE-97: ----------------------------------
I'm really convinced that the problem described in this issue is the root cause since the problems started when we switched to a multi-core system.
The multi-core system may exhibit a different time-slicing behavior, which does not necessarily implicate logback. There have been many instances where users have blamed log4j/logback for race-conditions or deadlocks in their application. In most, albeit not all cases, the culprit is the application itself, logging merely exposes an existing problem. I am not saying that there is no problem, let us just not jump to conclusions.
Concerning SocketAppender, I'm wondering about the use case where it's showing less than 50 microseconds latency. Are you running it against the loopback device?
No, just sending events to a nearby host via a 100Mb Ethernet connection. The records show an average latency of 42 microseconds. However, these records are somewhat old and the current figures may be higher (or lower).
This is a prime-example of starvation where multiple threads are fighting over scarce resources, in this case just one, the appender. To prevent starvation, fairness must be established.
You are assuming that starvation is caused by lack of fairness. It may also be caused becasue the shared resource is slow. Could it be that the VPN is the culprit?
Have you had the chance to do benchmarks about the impact of the fair ReentrantLock, yet? If the impact is negligible than this should be changed ASAP.
No I have not done any benchmarks about the impact of the fair ReentrantLock. However, I am still not convinced that the problem is related to the fairness of the appender lock. Have you tried applying your patch in production? And if you have, what have you observed? As far as I understood, thus far, disabling SocketAppender as well as other appenders, has improved your application's performance but this does not necessarily mean that the problem is related to fair time slicing. In this regards, Dave's last comment in my blog regarding the difference between spinning and blocking is quite informative. Let me remind you that lock fairness issue is not related to JDK 1.6. It was present in older JVMs as well. Anyway, I understand that you are under pressure to resolve this issue.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, Suggested_change_of_documentation.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu edited comment on LBCORE-97 at 6/9/09 12:08 PM: -----------------------------------------------------------
I'm really convinced that the problem described in this issue is the root cause since the problems started when we switched to a multi-core system.
The multi-core system may exhibit a different time-slicing behavior, which does not necessarily implicate logback. There have been many instances where users have blamed log4j/logback for race-conditions or deadlocks in their application. In most, albeit not all cases, the culprit was the application itself, logging merely exposed a pre-existing problem. I am not saying that there is no problem, but that we should not jump to conclusions.
Concerning SocketAppender, I'm wondering about the use case where it's showing less than 50 microseconds latency. Are you running it against the loopback device?
No, just sending events to a nearby host via a 100Mb Ethernet connection. The records show an average latency of 42 microseconds. However, these records are somewhat old and the current figures may be higher.
This is a prime-example of starvation where multiple threads are fighting over scarce resources, in this case just one, the appender. To prevent starvation, fairness must be established.
You are assuming that starvation is caused by lack of fairness. It may also be caused because the shared resource is intrinsically slow. Could it be that the VPN is the culprit?
Have you had the chance to do benchmarks about the impact of the fair ReentrantLock, yet? If the impact is negligible than this should be changed ASAP.
No I have not done any benchmarks about the impact of the fair ReentrantLock. However, I am still not convinced that the problem is related to the fairness of the appender lock. Have you tried applying your patch in production? And if you have, what have you observed? As far as I understood, thus far, disabling SocketAppender as well as other appenders, has improved your application's performance but this does not necessarily mean that the problem is related to fair time slicing. In this regards, Dave's last comment in my blog regarding the difference between spinning and blocking is quite informative. Let me remind you that lock fairness issue is not related to JDK 1.6. It was present in older JVMs as well. Anyway, I understand that you are under pressure to resolve this issue. I would encourage you to apply the patch in production and kindly report back to us the results. was (Author: noreply.ceki@qos.ch): > I'm really convinced that the problem described in this issue is the
root cause since the problems started when we switched to a multi-core system.
The multi-core system may exhibit a different time-slicing behavior, which does not necessarily implicate logback. There have been many instances where users have blamed log4j/logback for race-conditions or deadlocks in their application. In most, albeit not all cases, the culprit is the application itself, logging merely exposes an existing problem. I am not saying that there is no problem, let us just not jump to conclusions.
Concerning SocketAppender, I'm wondering about the use case where it's showing less than 50 microseconds latency. Are you running it against the loopback device?
No, just sending events to a nearby host via a 100Mb Ethernet connection. The records show an average latency of 42 microseconds. However, these records are somewhat old and the current figures may be higher (or lower).
This is a prime-example of starvation where multiple threads are fighting over scarce resources, in this case just one, the appender. To prevent starvation, fairness must be established.
You are assuming that starvation is caused by lack of fairness. It may also be caused becasue the shared resource is slow. Could it be that the VPN is the culprit?
Have you had the chance to do benchmarks about the impact of the fair ReentrantLock, yet? If the impact is negligible than this should be changed ASAP.
No I have not done any benchmarks about the impact of the fair ReentrantLock. However, I am still not convinced that the problem is related to the fairness of the appender lock. Have you tried applying your patch in production? And if you have, what have you observed? As far as I understood, thus far, disabling SocketAppender as well as other appenders, has improved your application's performance but this does not necessarily mean that the problem is related to fair time slicing. In this regards, Dave's last comment in my blog regarding the difference between spinning and blocking is quite informative. Let me remind you that lock fairness issue is not related to JDK 1.6. It was present in older JVMs as well. Anyway, I understand that you are under pressure to resolve this issue.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, Suggested_change_of_documentation.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu commented on LBCORE-97: ----------------------------------
As I said before, this does happen with just a simple RollingFileAppender in place, too. This may be caused by some huge debug messages but this shouldn't be able to render the whole application unusable.
Writing a very large log message will obviously take time. Since RollingAppender writes only one message at a time, it is natural for other threads wishing to access RollingAppender to queue, regardless of the fairness policy.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, Suggested_change_of_documentation.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- The problem isn't that the application is slowing down but that some threads (the starving) are literally grinding to a halt while others (the greedy) keep executing as expected. I can't apply my patch to our production system, partly because I'm not at work and partly because I'm not sure I'd get the OK to do it. But I did test it with the example application attached to http://jira.qos.ch/browse/LBCORE-63 and it solved the issue. I just realized that I added the result of this test to LBCORE-96 by mistake :( I'll just copy the comment over: I just did a test using the above example and it looks good with the patch applied. Without the patch, threads are executed unevenly. 2009-05-19 22:21:49,481 DEBUG [pool-2-thread-1] [MAIN] - [EX2] 54803 2009-05-19 22:21:49,481 DEBUG [pool-4-thread-1] [MAIN] - [EX4] 54803 2009-05-19 22:21:49,481 DEBUG [pool-2-thread-1] [EX2] 54803 2009-05-19 22:21:49,481 DEBUG [pool-2-thread-1] [MAIN] - [EX2] 54803 2009-05-19 22:21:49,481 DEBUG [pool-4-thread-1] [EX4] 54803 2009-05-19 22:21:49,481 DEBUG [pool-3-thread-1] [EX3] 54803 2009-05-19 22:21:49,481 DEBUG [pool-1-thread-1] [MAIN] - [EX1] 54805 2009-05-19 22:21:49,481 DEBUG [pool-5-thread-1] [EX5] 54803 2009-05-19 22:21:49,482 DEBUG [pool-5-thread-1] [MAIN] - [EX5] 54804 2009-05-19 22:21:49,481 DEBUG [pool-4-thread-1] [MAIN] - [EX4] 54803 2009-05-19 22:21:49,482 DEBUG [pool-1-thread-1] [EX1] 54806 2009-05-19 22:21:49,481 DEBUG [pool-2-thread-1] [EX2] 54803 2009-05-19 22:21:49,481 DEBUG [pool-3-thread-1] [MAIN] - [EX3] 54803 2009-05-19 22:21:49,482 DEBUG [pool-5-thread-1] [EX5] 54804 2009-05-19 22:21:49,482 DEBUG [pool-4-thread-1] [EX4] 54804 2009-05-19 22:21:49,482 DEBUG [pool-1-thread-1] [MAIN] - [EX1] 54806 2009-05-19 22:21:49,482 DEBUG [pool-2-thread-1] [MAIN] - [EX2] 54804 2009-05-19 22:21:49,482 DEBUG [pool-3-thread-1] [EX3] 54804 2009-05-19 22:21:49,482 DEBUG [pool-5-thread-1] [MAIN] - [EX5] 54804 2009-05-19 22:21:49,482 DEBUG [pool-3-thread-1] [MAIN] - [EX3] 54804 2009-05-19 22:21:49,482 DEBUG [pool-1-thread-1] [EX1] 54806 2009-05-19 22:21:49,482 DEBUG [pool-1-thread-1] [MAIN] - [EX1] 54806 2009-05-19 22:21:49,482 DEBUG [pool-4-thread-1] [MAIN] - [EX4] 54804 With the patch they are running evenly as I'd expect it. 2009-05-19 22:03:14,520 DEBUG [pool-5-thread-1] [EX5] 5900 2009-05-19 22:03:14,520 DEBUG [pool-4-thread-1] [EX4] 5925 2009-05-19 22:03:14,520 DEBUG [pool-1-thread-1] [EX1] 5936 2009-05-19 22:03:14,520 DEBUG [pool-3-thread-1] [MAIN] - [EX3] 5925 2009-05-19 22:03:14,520 DEBUG [pool-2-thread-1] [EX2] 5926 2009-05-19 22:03:14,520 DEBUG [pool-5-thread-1] [MAIN] - [EX5] 5900 2009-05-19 22:03:14,520 DEBUG [pool-4-thread-1] [MAIN] - [EX4] 5925 2009-05-19 22:03:14,521 DEBUG [pool-1-thread-1] [MAIN] - [EX1] 5937 2009-05-19 22:03:14,521 DEBUG [pool-3-thread-1] [EX3] 5926 2009-05-19 22:03:14,521 DEBUG [pool-2-thread-1] [MAIN] - [EX2] 5927 2009-05-19 22:03:14,521 DEBUG [pool-5-thread-1] [EX5] 5901 2009-05-19 22:03:14,521 DEBUG [pool-4-thread-1] [EX4] 5926 2009-05-19 22:03:14,521 DEBUG [pool-1-thread-1] [EX1] 5937 What do you think?
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, Suggested_change_of_documentation.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- The important point here is that the threads are always running in the same order 3-2-5-4-1 so no thread delays another one. But I'm repeating myself :p
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, Suggested_change_of_documentation.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- It's absolutely clear that a logging-system immune to starving effects (congestion is something different) has a serious advantage over one that suffers from one. Congestion happens if one thread writes a really big log-message. The whole system is slowed down to a certain degree in that case. That's absolutely OK. In case of starvation caused by unfair locking, that same thread will get the chance to immediately write *another* big log-message and so on. As we've seen, taking longer does even increase the chance to immediately get the lock again. With fair locking, all other threads that waited in line would first get the chance to execute their logging code, being able to work on, before the "chatty" thread would get another chance of taking a long time. This could very well be the case with our app because some parts are currently very chatty, writing many and large messages, but other parts can't go on, at all, because they are starving.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock2.patch, Suggested_change_of_documentation.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn updated LBCORE-97: -------------------------------- Attachment: AppenderBaseLock3.patch - Replacing synchronized with fair ReentrantLock to prevent starvation. - Changing guard into ThreadLocal<Boolean> and checking guard before locking. (LBCORE-99) - Added documentation to UnsynchronizedAppenderBase to encourage the use of fair mechanisms in extending appenders.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn updated LBCORE-97: -------------------------------- Attachment: (was: Suggested_change_of_documentation.patch)
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn updated LBCORE-97: -------------------------------- Attachment: (was: AppenderBaseLock2.patch)
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, SynchronizedVsFairLock.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- Environment: delay = 1 java.runtime.name = Java(TM) 2 Runtime Environment, Standard Edition java.runtime.version = 1.5.0_16-b06-284 java.vendor = Apple Inc. java.version = 1.5.0_16 java.vm.name = Java HotSpot(TM) Client VM java.vm.info = mixed mode, sharing os.name = Mac OS X os.version = 10.5.7 os.arch = i386 ########################################## About to execute usingSynchronized... Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=9050, running=false, delay=1] runnables[1]: WastedTimeSynchronizedRunnable[counter=5533, running=false, delay=1, minWasted=0, maxWasted=2035000, totalWasted=4307631000] runnables[2]: WastedTimeSynchronizedRunnable[counter=5564, running=false, delay=1, minWasted=0, maxWasted=3340000, totalWasted=4274103000] runnables[3]: WastedTimeSynchronizedRunnable[counter=5499, running=false, delay=1, minWasted=0, maxWasted=2613000, totalWasted=4341686000] runnables[4]: WastedTimeSynchronizedRunnable[counter=5541, running=false, delay=1, minWasted=0, maxWasted=2614000, totalWasted=4298552000] runnables[5]: WastedTimeSynchronizedRunnable[counter=5580, running=false, delay=1, minWasted=0, maxWasted=2115000, totalWasted=4257398000] runnables[6]: WastedTimeSynchronizedRunnable[counter=5543, running=false, delay=1, minWasted=0, maxWasted=3344000, totalWasted=4297607000] runnables[7]: WastedTimeSynchronizedRunnable[counter=5581, running=false, delay=1, minWasted=0, maxWasted=3332000, totalWasted=4254483000] runnables[8]: WastedTimeSynchronizedRunnable[counter=5570, running=false, delay=1, minWasted=0, maxWasted=2118000, totalWasted=4267368000] runnables[9]: WastedTimeSynchronizedRunnable[counter=5538, running=false, delay=1, minWasted=0, maxWasted=2518000, totalWasted=4305538000] ########################################## About to execute usingUnfairLock... Results for usingUnfairLock: runnables[0]: LockRunnable[counter=9655, running=false, delay=1] runnables[1]: WastedTimeLockRunnable[counter=8, running=false, delay=1, minWasted=0, maxWasted=3177612000, totalWasted=9992771000] runnables[2]: WastedTimeLockRunnable[counter=11, running=false, delay=1, minWasted=0, maxWasted=3175541000, totalWasted=9989755000] runnables[3]: WastedTimeLockRunnable[counter=8, running=false, delay=1, minWasted=0, maxWasted=3113132000, totalWasted=9992838000] runnables[4]: WastedTimeLockRunnable[counter=10, running=false, delay=1, minWasted=0, maxWasted=2945695000, totalWasted=9990695000] runnables[5]: WastedTimeLockRunnable[counter=11, running=false, delay=1, minWasted=0, maxWasted=2952869000, totalWasted=9989668000] runnables[6]: WastedTimeLockRunnable[counter=13, running=false, delay=1, minWasted=0, maxWasted=3092394000, totalWasted=9987500000] runnables[7]: WastedTimeLockRunnable[counter=8, running=false, delay=1, minWasted=0, maxWasted=3750551000, totalWasted=9992601000] runnables[8]: WastedTimeLockRunnable[counter=8, running=false, delay=1, minWasted=0, maxWasted=3750598000, totalWasted=9992641000] runnables[9]: WastedTimeLockRunnable[counter=10, running=false, delay=1, minWasted=0, maxWasted=3623426000, totalWasted=9990499000] ########################################## About to execute usingFairLock... Results for usingFairLock: runnables[0]: LockRunnable[counter=8756, running=false, delay=1] runnables[1]: WastedTimeLockRunnable[counter=8691, running=false, delay=1, minWasted=0, maxWasted=1384000, totalWasted=1141561000] runnables[2]: WastedTimeLockRunnable[counter=8691, running=false, delay=1, minWasted=0, maxWasted=1240000, totalWasted=1139965000] runnables[3]: WastedTimeLockRunnable[counter=8688, running=false, delay=1, minWasted=0, maxWasted=1373000, totalWasted=1142592000] runnables[4]: WastedTimeLockRunnable[counter=8695, running=false, delay=1, minWasted=0, maxWasted=1406000, totalWasted=1134775000] runnables[5]: WastedTimeLockRunnable[counter=8690, running=false, delay=1, minWasted=0, maxWasted=1447000, totalWasted=1138803000] runnables[6]: WastedTimeLockRunnable[counter=8687, running=false, delay=1, minWasted=0, maxWasted=2188000, totalWasted=1146588000] runnables[7]: WastedTimeLockRunnable[counter=8698, running=false, delay=1, minWasted=0, maxWasted=1395000, totalWasted=1134743000] runnables[8]: WastedTimeLockRunnable[counter=8688, running=false, delay=1, minWasted=0, maxWasted=1455000, totalWasted=1140360000] runnables[9]: WastedTimeLockRunnable[counter=8687, running=false, delay=1, minWasted=0, maxWasted=1226000, totalWasted=1145801000] ########################################## Environment: delay = 10 java.runtime.name = Java(TM) 2 Runtime Environment, Standard Edition java.runtime.version = 1.5.0_16-b06-284 java.vendor = Apple Inc. java.version = 1.5.0_16 java.vm.name = Java HotSpot(TM) Client VM java.vm.info = mixed mode, sharing os.name = Mac OS X os.version = 10.5.7 os.arch = i386 ########################################## About to execute usingSynchronized... Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=989, running=false, delay=10] runnables[1]: WastedTimeSynchronizedRunnable[counter=600, running=false, delay=10, minWasted=0, maxWasted=10844000, totalWasted=3976076000] runnables[2]: WastedTimeSynchronizedRunnable[counter=610, running=false, delay=10, minWasted=0, maxWasted=10850000, totalWasted=3875304000] runnables[3]: WastedTimeSynchronizedRunnable[counter=602, running=false, delay=10, minWasted=0, maxWasted=10851000, totalWasted=3954896000] runnables[4]: WastedTimeSynchronizedRunnable[counter=604, running=false, delay=10, minWasted=0, maxWasted=10840000, totalWasted=3935276000] runnables[5]: WastedTimeSynchronizedRunnable[counter=602, running=false, delay=10, minWasted=0, maxWasted=10838000, totalWasted=3955131000] runnables[6]: WastedTimeSynchronizedRunnable[counter=606, running=false, delay=10, minWasted=0, maxWasted=10863000, totalWasted=3915602000] runnables[7]: WastedTimeSynchronizedRunnable[counter=606, running=false, delay=10, minWasted=0, maxWasted=10841000, totalWasted=3915068000] runnables[8]: WastedTimeSynchronizedRunnable[counter=603, running=false, delay=10, minWasted=0, maxWasted=10814000, totalWasted=3944606000] runnables[9]: WastedTimeSynchronizedRunnable[counter=603, running=false, delay=10, minWasted=0, maxWasted=10840000, totalWasted=3944719000] ########################################## About to execute usingUnfairLock... Results for usingUnfairLock: runnables[0]: LockRunnable[counter=996, running=false, delay=10] runnables[1]: WastedTimeLockRunnable[counter=2, running=false, delay=10, minWasted=0, maxWasted=5718199000, totalWasted=9981366000] runnables[2]: WastedTimeLockRunnable[counter=3, running=false, delay=10, minWasted=0, maxWasted=8281023000, totalWasted=9971366000] runnables[3]: WastedTimeLockRunnable[counter=1, running=false, delay=10, minWasted=0, maxWasted=9990838000, totalWasted=9990838000] runnables[4]: WastedTimeLockRunnable[counter=1, running=false, delay=10, minWasted=0, maxWasted=9990895000, totalWasted=9990895000] runnables[5]: WastedTimeLockRunnable[counter=1, running=false, delay=10, minWasted=0, maxWasted=9990802000, totalWasted=9990802000] runnables[6]: WastedTimeLockRunnable[counter=1, running=false, delay=10, minWasted=0, maxWasted=9990792000, totalWasted=9990792000] runnables[7]: WastedTimeLockRunnable[counter=1, running=false, delay=10, minWasted=0, maxWasted=9990720000, totalWasted=9990720000] runnables[8]: WastedTimeLockRunnable[counter=1, running=false, delay=10, minWasted=0, maxWasted=9990713000, totalWasted=9990713000] runnables[9]: WastedTimeLockRunnable[counter=1, running=false, delay=10, minWasted=0, maxWasted=9990647000, totalWasted=9990647000] ########################################## About to execute usingFairLock... Results for usingFairLock: runnables[0]: LockRunnable[counter=985, running=false, delay=10] runnables[1]: WastedTimeLockRunnable[counter=972, running=false, delay=10, minWasted=0, maxWasted=10396000, totalWasted=250077000] runnables[2]: WastedTimeLockRunnable[counter=972, running=false, delay=10, minWasted=0, maxWasted=10184000, totalWasted=248853000] runnables[3]: WastedTimeLockRunnable[counter=970, running=false, delay=10, minWasted=0, maxWasted=11014000, totalWasted=268824000] runnables[4]: WastedTimeLockRunnable[counter=971, running=false, delay=10, minWasted=0, maxWasted=10564000, totalWasted=260988000] runnables[5]: WastedTimeLockRunnable[counter=970, running=false, delay=10, minWasted=0, maxWasted=10428000, totalWasted=268175000] runnables[6]: WastedTimeLockRunnable[counter=973, running=false, delay=10, minWasted=0, maxWasted=10295000, totalWasted=241207000] runnables[7]: WastedTimeLockRunnable[counter=974, running=false, delay=10, minWasted=0, maxWasted=10188000, totalWasted=231224000] runnables[8]: WastedTimeLockRunnable[counter=972, running=false, delay=10, minWasted=0, maxWasted=10314000, totalWasted=249652000] runnables[9]: WastedTimeLockRunnable[counter=970, running=false, delay=10, minWasted=0, maxWasted=10989000, totalWasted=268618000] ########################################## Environment: delay = 1 java.runtime.name = Java(TM) SE Runtime Environment java.runtime.version = 1.6.0_07-b06-153 java.vendor = Apple Inc. java.version = 1.6.0_07 java.vm.name = Java HotSpot(TM) 64-Bit Server VM java.vm.info = mixed mode os.name = Mac OS X os.version = 10.5.7 os.arch = x86_64 ########################################## About to execute usingSynchronized... Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=9673, running=false, delay=1] runnables[1]: WastedTimeSynchronizedRunnable[counter=15, running=false, delay=1, minWasted=0, maxWasted=2590599000, totalWasted=9987373000] runnables[2]: WastedTimeSynchronizedRunnable[counter=22, running=false, delay=1, minWasted=0, maxWasted=2675914000, totalWasted=9980257000] runnables[3]: WastedTimeSynchronizedRunnable[counter=23, running=false, delay=1, minWasted=0, maxWasted=1871833000, totalWasted=9978626000] runnables[4]: WastedTimeSynchronizedRunnable[counter=9, running=false, delay=1, minWasted=0, maxWasted=3243744000, totalWasted=9992860000] runnables[5]: WastedTimeSynchronizedRunnable[counter=8, running=false, delay=1, minWasted=0, maxWasted=3243700000, totalWasted=9993815000] runnables[6]: WastedTimeSynchronizedRunnable[counter=8, running=false, delay=1, minWasted=0, maxWasted=3243654000, totalWasted=9993713000] runnables[7]: WastedTimeSynchronizedRunnable[counter=12, running=false, delay=1, minWasted=0, maxWasted=2670722000, totalWasted=9989535000] runnables[8]: WastedTimeSynchronizedRunnable[counter=9, running=false, delay=1, minWasted=0, maxWasted=2959142000, totalWasted=9992392000] runnables[9]: WastedTimeSynchronizedRunnable[counter=13, running=false, delay=1, minWasted=0, maxWasted=2959018000, totalWasted=9988193000] ########################################## About to execute usingUnfairLock... Results for usingUnfairLock: runnables[0]: LockRunnable[counter=9607, running=false, delay=1] runnables[1]: WastedTimeLockRunnable[counter=20, running=false, delay=1, minWasted=0, maxWasted=2059909000, totalWasted=9986792000] runnables[2]: WastedTimeLockRunnable[counter=15, running=false, delay=1, minWasted=0, maxWasted=2059904000, totalWasted=9986182000] runnables[3]: WastedTimeLockRunnable[counter=38, running=false, delay=1, minWasted=0, maxWasted=1867779000, totalWasted=9963711000] runnables[4]: WastedTimeLockRunnable[counter=15, running=false, delay=1, minWasted=0, maxWasted=1832521000, totalWasted=9988460000] runnables[5]: WastedTimeLockRunnable[counter=16, running=false, delay=1, minWasted=0, maxWasted=1832510000, totalWasted=9988727000] runnables[6]: WastedTimeLockRunnable[counter=17, running=false, delay=1, minWasted=0, maxWasted=1976649000, totalWasted=9988772000] runnables[7]: WastedTimeLockRunnable[counter=38, running=false, delay=1, minWasted=0, maxWasted=2767358000, totalWasted=9967229000] runnables[8]: WastedTimeLockRunnable[counter=16, running=false, delay=1, minWasted=0, maxWasted=2732013000, totalWasted=9989731000] runnables[9]: WastedTimeLockRunnable[counter=31, running=false, delay=1, minWasted=0, maxWasted=2078606000, totalWasted=9974291000] ########################################## About to execute usingFairLock... Results for usingFairLock: runnables[0]: LockRunnable[counter=8495, running=false, delay=1] runnables[1]: WastedTimeLockRunnable[counter=8400, running=false, delay=1, minWasted=0, maxWasted=24380000, totalWasted=1290865000] runnables[2]: WastedTimeLockRunnable[counter=8371, running=false, delay=1, minWasted=0, maxWasted=21195000, totalWasted=1304971000] runnables[3]: WastedTimeLockRunnable[counter=8387, running=false, delay=1, minWasted=0, maxWasted=27723000, totalWasted=1282987000] runnables[4]: WastedTimeLockRunnable[counter=8367, running=false, delay=1, minWasted=0, maxWasted=22515000, totalWasted=1304042000] runnables[5]: WastedTimeLockRunnable[counter=8350, running=false, delay=1, minWasted=0, maxWasted=37975000, totalWasted=1333368000] runnables[6]: WastedTimeLockRunnable[counter=8389, running=false, delay=1, minWasted=0, maxWasted=29434000, totalWasted=1294695000] runnables[7]: WastedTimeLockRunnable[counter=8397, running=false, delay=1, minWasted=0, maxWasted=27503000, totalWasted=1271671000] runnables[8]: WastedTimeLockRunnable[counter=8387, running=false, delay=1, minWasted=0, maxWasted=27743000, totalWasted=1293263000] runnables[9]: WastedTimeLockRunnable[counter=8372, running=false, delay=1, minWasted=0, maxWasted=27691000, totalWasted=1295582000] ########################################## Environment: delay = 10 java.runtime.name = Java(TM) SE Runtime Environment java.runtime.version = 1.6.0_07-b06-153 java.vendor = Apple Inc. java.version = 1.6.0_07 java.vm.name = Java HotSpot(TM) 64-Bit Server VM java.vm.info = mixed mode os.name = Mac OS X os.version = 10.5.7 os.arch = x86_64 ########################################## About to execute usingSynchronized... Results for usingSynchronized: runnables[0]: SynchronizedRunnable[counter=997, running=false, delay=10] runnables[1]: WastedTimeSynchronizedRunnable[counter=1, running=false, delay=10, minWasted=0, maxWasted=9991803000, totalWasted=9991803000] runnables[2]: WastedTimeSynchronizedRunnable[counter=1, running=false, delay=10, minWasted=0, maxWasted=9991705000, totalWasted=9991705000] runnables[3]: WastedTimeSynchronizedRunnable[counter=1, running=false, delay=10, minWasted=0, maxWasted=9991312000, totalWasted=9991312000] runnables[4]: WastedTimeSynchronizedRunnable[counter=1, running=false, delay=10, minWasted=0, maxWasted=9991104000, totalWasted=9991104000] runnables[5]: WastedTimeSynchronizedRunnable[counter=1, running=false, delay=10, minWasted=0, maxWasted=9990939000, totalWasted=9990939000] runnables[6]: WastedTimeSynchronizedRunnable[counter=1, running=false, delay=10, minWasted=0, maxWasted=9990764000, totalWasted=9990764000] runnables[7]: WastedTimeSynchronizedRunnable[counter=1, running=false, delay=10, minWasted=0, maxWasted=9990540000, totalWasted=9990540000] runnables[8]: WastedTimeSynchronizedRunnable[counter=11, running=false, delay=10, minWasted=0, maxWasted=7930974000, totalWasted=9890440000] runnables[9]: WastedTimeSynchronizedRunnable[counter=6, running=false, delay=10, minWasted=0, maxWasted=7592208000, totalWasted=9940592000] ########################################## About to execute usingUnfairLock... Results for usingUnfairLock: runnables[0]: LockRunnable[counter=996, running=false, delay=10] runnables[1]: WastedTimeLockRunnable[counter=2, running=false, delay=10, minWasted=0, maxWasted=9757274000, totalWasted=9981028000] runnables[2]: WastedTimeLockRunnable[counter=2, running=false, delay=10, minWasted=0, maxWasted=9757284000, totalWasted=9982310000] runnables[3]: WastedTimeLockRunnable[counter=2, running=false, delay=10, minWasted=0, maxWasted=9757167000, totalWasted=9982302000] runnables[4]: WastedTimeLockRunnable[counter=2, running=false, delay=10, minWasted=0, maxWasted=9757132000, totalWasted=9982360000] runnables[5]: WastedTimeLockRunnable[counter=2, running=false, delay=10, minWasted=0, maxWasted=9757062000, totalWasted=9982479000] runnables[6]: WastedTimeLockRunnable[counter=2, running=false, delay=10, minWasted=0, maxWasted=9756981000, totalWasted=9982632000] runnables[7]: WastedTimeLockRunnable[counter=2, running=false, delay=10, minWasted=0, maxWasted=9756907000, totalWasted=9982735000] runnables[8]: WastedTimeLockRunnable[counter=2, running=false, delay=10, minWasted=0, maxWasted=9756817000, totalWasted=9983801000] runnables[9]: WastedTimeLockRunnable[counter=2, running=false, delay=10, minWasted=0, maxWasted=9756758000, totalWasted=9985104000] ########################################## About to execute usingFairLock... Results for usingFairLock: runnables[0]: LockRunnable[counter=981, running=false, delay=10] runnables[1]: WastedTimeLockRunnable[counter=968, running=false, delay=10, minWasted=0, maxWasted=12768000, totalWasted=282213000] runnables[2]: WastedTimeLockRunnable[counter=964, running=false, delay=10, minWasted=0, maxWasted=12802000, totalWasted=321530000] runnables[3]: WastedTimeLockRunnable[counter=967, running=false, delay=10, minWasted=0, maxWasted=12773000, totalWasted=293688000] runnables[4]: WastedTimeLockRunnable[counter=962, running=false, delay=10, minWasted=0, maxWasted=11011000, totalWasted=330372000] runnables[5]: WastedTimeLockRunnable[counter=963, running=false, delay=10, minWasted=0, maxWasted=22772000, totalWasted=330998000] runnables[6]: WastedTimeLockRunnable[counter=968, running=false, delay=10, minWasted=0, maxWasted=10242000, totalWasted=269637000] runnables[7]: WastedTimeLockRunnable[counter=966, running=false, delay=10, minWasted=0, maxWasted=10276000, totalWasted=290630000] runnables[8]: WastedTimeLockRunnable[counter=963, running=false, delay=10, minWasted=0, maxWasted=22807000, totalWasted=333009000] runnables[9]: WastedTimeLockRunnable[counter=968, running=false, delay=10, minWasted=0, maxWasted=12816000, totalWasted=282952000] ##########################################
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn updated LBCORE-97: -------------------------------- Attachment: TimeWasted.java The example we discussed on Skype. My results will be posted in an additional comment.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn commented on LBCORE-97: ------------------------------------- Wasted time, taking arbitrary sample: 10ms delay: 9940592000 synchronized 9985104000 unfair lock 282952000 fair lock 1ms delay: 9988193000 synchronized 9974291000 unfair lock 1295582000 fair lock As can be seen, much more actual "work" can be done in case of a fair lock. Work, in this case, would be the added up counter values. So the penalty of the fair lock isn't relevant anymore since the system, as a whole, is performing much better concerning concurrency. This is, of course, another pretty hardcore example... Nevertheless, it mimics the effect of a slow(ish) appender pretty well, I think/hope.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn edited comment on LBCORE-97 at 6/14/09 5:38 AM: -------------------------------------------------------------- Wasted time, taking arbitrary sample: 10ms delay: 9940592000 synchronized 9985104000 unfair lock 282952000 fair lock 1ms delay: 9988193000 synchronized 9974291000 unfair lock 1295582000 fair lock Even more relevant than the total amount of wasted time are the maxWasted values... they illustrate that a big amount of time is spent waiting in starvation instead of performing some work. 1ms delay: 2959018000 synchronized 2078606000 unfair lock 27691000 fair lock 10ms delay: 7592208000 synchronized 9756758000 unfair lock 12816000 fair lock As can be seen, much more actual "work" can be done in case of a fair lock. Work, in this case, would be the added up counter values of all threads. So the penalty of the fair lock isn't relevant anymore since the system, as a whole, is performing much better concerning concurrency. This is, of course, another pretty hardcore example... Nevertheless, it mimics the effect of a slow(ish) appender pretty well, I think/hope. was (Author: jhuxhorn): Wasted time, taking arbitrary sample: 10ms delay: 9940592000 synchronized 9985104000 unfair lock 282952000 fair lock 1ms delay: 9988193000 synchronized 9974291000 unfair lock 1295582000 fair lock As can be seen, much more actual "work" can be done in case of a fair lock. Work, in this case, would be the added up counter values. So the penalty of the fair lock isn't relevant anymore since the system, as a whole, is performing much better concerning concurrency. This is, of course, another pretty hardcore example... Nevertheless, it mimics the effect of a slow(ish) appender pretty well, I think/hope.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu commented on LBCORE-97: ---------------------------------- Joern, It seems to me that your latest examples shows that if the overall system is prone to starvation, then threads starve. In particular since the total amount of wasted time is often very close to that of the maxWasted value. I am still looking into this.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu commented on LBCORE-97: ---------------------------------- If you run NoLockThroughput found in the ch.qos.logback.core.issue on Sun's JDK 1.6 on Linux. You get: === NoLockThroughput === java.runtime.version = 1.6.0_11-b03 java.vendor = Sun Microsystems Inc. java.version = 1.6.0_11 os.name = Linux os.version = 2.6.25-gentoo-r6 The application never returns. Translation: any single line of code with "for(;;) {}" will bring your application to its knees (on Sun's JDK on a multicore 64 bit Linux). The same test under IBM's JDK 1.6 yields: === NoLockThroughput === java.runtime.version = jvmxa6460-20081105_25433 java.vendor = IBM Corporation java.version = 1.6.0 os.name = Linux os.version = 2.6.25-gentoo-r6 SelectiveLockRunnable NOLOCK count=220890254 SelectiveLockRunnable NOLOCK count=244948022 SelectiveLockRunnable NOLOCK count=312806143 No lock: total of 778644419 operations, or 389322 operations per millisecond Thus, IBMs JDK does reasonable time slicing. Perhaps Sun's does not want Java to run well on 64bit Linux. As for the fair locks, I don't believe its logback's job to fix the JDK.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Joern Huxhorn updated LBCORE-97: -------------------------------- Attachment: NoLockThroughput.patch Hi Ceki. Please check if the NoLockThroughput.patch fixes behavior on Linux. My guess is that the threads are never notified of the change of the done boolean. http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, NoLockThroughput.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu commented on LBCORE-97: ---------------------------------- I filed a bug report on this a while ago. It just got accepted the link is: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6855216
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Bug Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, NoLockThroughput.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCORE-97?page=com.atlassian.jira.plugin.system.is... ] Ceki Gulcu updated LBCORE-97: ----------------------------- Parent: LBCORE-62 Issue Type: Sub-task (was: Bug)
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCORE-97 URL: http://jira.qos.ch/browse/LBCORE-97 Project: logback-core Issue Type: Sub-task Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, NoLockThroughput.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCLASSIC-140?page=com.atlassian.jira.plugin.syste... ] Ceki Gulcu moved LBCORE-97 to LBCLASSIC-140: -------------------------------------------- Project: logback-classic (was: logback-core) Key: LBCLASSIC-140 (was: LBCORE-97)
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCLASSIC-140 URL: http://jira.qos.ch/browse/LBCLASSIC-140 Project: logback-classic Issue Type: Sub-task Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, NoLockThroughput.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCLASSIC-140?page=com.atlassian.jira.plugin.syste... ] Joern Huxhorn commented on LBCLASSIC-140: ----------------------------------------- But this *is* a core-issue. AppenderBase is part of logback-core... I'm confused :p
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCLASSIC-140 URL: http://jira.qos.ch/browse/LBCLASSIC-140 Project: logback-classic Issue Type: Sub-task Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, NoLockThroughput.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCLASSIC-140?page=com.atlassian.jira.plugin.syste... ] Ceki Gulcu commented on LBCLASSIC-140: -------------------------------------- Yes, you are right. However, it makes life easier for me (for testing purposes) if the bug is considered to be part of logback-classic
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCLASSIC-140 URL: http://jira.qos.ch/browse/LBCLASSIC-140 Project: logback-classic Issue Type: Sub-task Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, NoLockThroughput.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCLASSIC-140?page=com.atlassian.jira.plugin.syste... ] Ceki Gulcu edited comment on LBCLASSIC-140 at 6/26/09 11:08 PM: ---------------------------------------------------------------- Yes, you are right. However, it makes life easier for me (for testing purposes) if the bug is considered to be part of logback-classic. For example, it allows me to group LBCLASSIC-139 as sub-task of LBCLASSIC-135. I would not read too much into this re-organization. was (Author: noreply.ceki@qos.ch): Yes, you are right. However, it makes life easier for me (for testing purposes) if the bug is considered to be part of logback-classic
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCLASSIC-140 URL: http://jira.qos.ch/browse/LBCLASSIC-140 Project: logback-classic Issue Type: Sub-task Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Attachments: AppenderBaseLock3.patch, NoLockThroughput.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCLASSIC-140?page=com.atlassian.jira.plugin.syste... ] Ceki Gulcu resolved LBCLASSIC-140. ---------------------------------- Fix Version/s: unspecified Resolution: Won't Fix The issue relates to how Sun's JVM does time slicing more than anything else.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCLASSIC-140 URL: http://jira.qos.ch/browse/LBCLASSIC-140 Project: logback-classic Issue Type: Sub-task Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Fix For: unspecified
Attachments: AppenderBaseLock3.patch, NoLockThroughput.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCLASSIC-140?page=com.atlassian.jira.plugin.syste... ] Rü commented on LBCLASSIC-140: ------------------------------ @Ceki: Your bug report at oracle is marked as related to bug 4985566 [1] which is marked as "won't fix" for the following reason: --------------------- When multiple threads contend for a monitor, strict alternation of the threads is "fair" (when viewed from a short time-frame), but also results in excessive context switch costs. It's usually the case that large commercial applications show higher throughput with an unfair "competive" succession policy as compared to fair alternation. If you need truly fair locking with strict alternation then you might consider Doug Lea's dl.util.concurrent packages. --------------------- Thread starvation is not a VM bug but a valuable feature, so maybe you should consider reopening this bug. [1] http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4985566
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCLASSIC-140 URL: http://jira.qos.ch/browse/LBCLASSIC-140 Project: logback-classic Issue Type: Sub-task Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Fix For: unspecified
Attachments: AppenderBaseLock3.patch, NoLockThroughput.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira

[ http://jira.qos.ch/browse/LBCLASSIC-140?page=com.atlassian.jira.plugin.syste... ] Joern Huxhorn commented on LBCLASSIC-140: ----------------------------------------- Since the behavior of the Sun JVM, while extreme, is correct anyway according to the documentation, I still think that this should be fixed in Logback. I'm not personally involved anymore because I switched my implementation over to UnsynchronizedAppenderBase + fair locks, though. My opinion is that a logging framework simply must not cause such a starvation effect even if will perform slower because of the counter measures. This will get a bigger and bigger problem with increasing amount of CPU cores so I'm pretty sure that this will show up again, anyway.
Starvation on AppenderBase.doAppend -----------------------------------
Key: LBCLASSIC-140 URL: http://jira.qos.ch/browse/LBCLASSIC-140 Project: logback-classic Issue Type: Sub-task Components: Appender Affects Versions: 0.9.15 Reporter: Joern Huxhorn Assignee: Logback dev list Priority: Critical Fix For: unspecified
Attachments: AppenderBaseLock3.patch, NoLockThroughput.patch, SynchronizedVsFairLock.java, TimeWasted.java
The problem we are facing here is that several threads are trying to obtain the object monitor of the exact same resource, namely the Appender. The assumption that multiple threads waiting for ownership of a monitor would receive the monitor in the order that they tried to acquire it is simply incorrect. This is documented behavior. See the last paragraph of [1]: "Likewise, no assumptions should be made about the order in which threads are granted ownership of a monitor or the order in which threads wake in response to the notify or notifyAll method. An excellent reference for these topics is Chapter 9, "Threads," in Joshua Bloch's book Effective Java Programming Language Guide. " The documentation of Object.notifyAll() [2] states the following: "[..] The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object." The documentation in [5] also lists the following as a weak-spot of Built-in Synchronization in J2SE 1.4.x: "No way to alter the semantics of a lock, for example, with respect to reentrancy, read versus write protection, or fairness." In LBCORE-96 Ceki stated the following: "Logback is using the standard synchronization mechanism found in the JDK. You are saying that that mechanism is unsuitable which, truth be told, sounds quite suspicious." Yes, it's unsuitable in this situation because obtaining the object monitor is not guaranteed to be fair. It's not working in a "first come, first serve" manner. I assumed that, too, but it isn't the case. I had the exact same problem in Lilith some time ago because I made exactly this very same wrong assumption. Fairness of object monitor lock acquisition seems to be logical and a "good thing" but it's not specified that way, and for good reasons. Without fairness in place, the VM can optimize the execution of an application much better. A context switch is a costly operation for a CPU so performance is increased significantly if such a switch can be omitted. Concerning a test-case, this would be pretty hardcore to implement since it's 100% implementation dependent. One implementation *may* handle the locking of object monitors fairly while others don't. Therefore I'll try the following first: I assume I could convince you that object monitor acquisition (OMA) is not fair. If we take that for granted the following scenario should show my point: There are 4 Threads (e.g. 4 Threads handling concurrent webapp-requests) that have one single chokepoint, the appender. Since OMA isn't fair, it's possible that only some of those threads can work at all (see [3]). Exactly that is happening right now in our webapp running on a 4-core Solaris on JDK6. My next assumption is that we both agree that this isn't acceptable behavior. Logging should be handled "first come, first served" so that if Thread A is waiting to append an event and Thread B is waiting to append an event subsequently, the actual appending order should be A, then B. This is currently not the case. It *may* be the case but there is no guarantee of it. One could even argue that the logging system is working incorrectly (aside from the starvation problem) because events are appended in a different order than the actual execution of the logger call. The only way to prevent this is the introduction of fairness into the locking process. The way to do this is to use ReentrantLock [4]. From the ReentrantLock javadoc: "The constructor for this class accepts an optional fairness parameter. When set true, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation." This is exactly what is absolutely necessary for a logging framework. Logging must *NOT* be able to introduce a starvation problem into a multi-threaded application! And it does exactly that right now. I doubt that the performance will decrease in a significant manner due to the use of a fair ReentrantLock but even if this would be the case it would be necessary anyway. Otherwise, appending simply isn't working correctly. [1] http://java.sun.com/j2se/1.5.0/docs/guide/vm/thread-priorities.html [2] http://java.sun.com/javase/6/docs/api/java/lang/Object.html#notifyAll() [3] http://en.wikipedia.org/wiki/Starvation_(computing) [4] http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantLo... [5] http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
-- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://jira.qos.ch/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira
participants (13)
-
Ceki Gulcu
-
Ceki Gulcu (JIRA)
-
Joern Huxhorn
-
Joern Huxhorn (JIRA)
-
Maarten Bosteels
-
Maarten Bosteels (JIRA)
-
Philippe Marschall (JIRA)
-
Ralph Goers
-
Ralph Goers (JIRA)
-
ralph.goers @dslextreme.com
-
Robert Elliot
-
Rü (JIRA)
-
Thorbjoern Ravn Andersen