It's taken some time to study all possible ways of detecting Event Dispatch Thread rule violations,
and now I feel I this topic is about to be closed.
But let me tell from the beginning:
ThreadCheckingRepaintManager
I was really surprised when I got to know about the smart solution invented by Scott Delap
who created a RepaintManager which finds EDT mistakes, I started to play with it and noticed that
Note: Please take the latest version of the ThreadCheckingRepaintManager from
the SwingHelper project
This solution has the following advantages:
- It definitely helps in finding EDT problems
- You can use it right now, no third-party libs are required
- It is simple and elegant
But from the other side I thought about the following problems
- repaint() method is thread-safe and it is not a mistake to invoke it out of EDT
- There are a lot of methods in Swing which don't produce repaint events, the most obvious example is getters.
If you call e.g. JTextField.setText() from EDT and the same time JTextField.getText() from another thread the result is unpredictable
The first problem is easy to solve:
to skip repaint() invocations the stack trace can be checked, I ended up with the following code:
public class CheckThreadViolationRepaintManager extends RepaintManager {
// it is recommended to pass the complete check
private boolean completeCheck = true;
public boolean isCompleteCheck() {
return completeCheck;
}
public void setCompleteCheck(boolean completeCheck) {
this.completeCheck = completeCheck;
}
public synchronized void addInvalidComponent(JComponent component) {
checkThreadViolations(component);
super.addInvalidComponent(component);
}
public void addDirtyRegion(JComponent component, int x, int y, int w, int h) {
checkThreadViolations(component);
super.addDirtyRegion(component, x, y, w, h);
}
private void checkThreadViolations(JComponent c) {
if (!SwingUtilities.isEventDispatchThread() && (completeCheck || c.isShowing())) {
Exception exception = new Exception();
boolean repaint = false;
boolean fromSwing = false;
StackTraceElement[] stackTrace = exception.getStackTrace();
for (StackTraceElement st : stackTrace) {
if (repaint && st.getClassName().startsWith("javax.swing.")) {
fromSwing = true;
}
if ("repaint".equals(st.getMethodName())) {
repaint = true;
}
}
if (repaint && !fromSwing) {
//no problems here, since repaint() is thread safe
return;
}
exception.printStackTrace();
}
}
}
Some comments about the code:
Initially there was a rule that it is safe to create and use Swing components until they are realized
but this rule is not valid any more, and now it is recommended to interact with Swing from EDT only
That's why completeCheck flag is used - if you test the old program switch it to false,
but new applications should be tested with completeCheck set to true
By the way, there is a chance that an application already uses a custom RepaintManager,
so it would be better to be able to "wrap" a custom RepaintManager to support existing functionality
public class CheckThreadViolationRepaintManager extends RepaintManager {
private final RepaintManager delegatee;
public CheckThreadViolationRepaintManager() {
this(new RepaintManager());
}
public CheckThreadViolationRepaintManager(RepaintManager delegatee) {
if (delegatee == null || delegatee instanceof CheckThreadViolationRepaintManager) {
throw new IllegalArgumentException();
}
this.delegatee = delegatee;
}
public synchronized void addInvalidComponent(JComponent component) {
checkThreadViolations(component);
// use delegatee instead of super for *all* methods
delegatee.addInvalidComponent(component);
}
//other methods skipped
}
Visit the Scott Delap's blog where you can find all information about this technique
CheckThreadViolationRepaintManager is really useful and can detect a lot of EDT mistakes, but... not all of them
As I said above if methods doesn't send repaint events, RepaintManager doesn't work for it
That's why I continued finding the complete solution
My first intention was to find a solution which can easily detect EDT violations and which doesn't need any third party tools or libraries (like CheckThreadViolationRepaintManager works) and that explains my next try:
JVMTI agent
Thanks to Stepan Rutz I got an example of JVMTI which can detect EDT problems via bytecode instrumentation,
the agent can instrument classes from javax.swing package and insert required checking to the beginning of any methods in runtime.
Stepan, I didn't post your code here since I don't have your permission, could you publish a link to the code you sent me ?
I started to work with it and realized this solution has some drawbacks
- It is not a pure java solution, using JNI to invoke java methods from C code is not as clear as that
- Since JVMTI agent uses native code it requires to be compiled for each platform differently
Hence I switched to java.lang.instrument package which allows to create agents without C coding
Anyway it was a interesting experience, and I realized that JVMTI is very powerful and you can get a lot of specific information from JVM e.g. garbage collection or object allocations events tracking
java.lang.instrument.ClassFileTransformer
ClassFileTransformer has the only method, it takes the class and returns modified bytecode,
sounds pretty simple but to use it you need to know the format of java bytecode very well
Fortunately I found the BCEL library which simplifies bytecode transformations and when I started reading manuals
the AWT lead Oleg Sukhodolsky came to see me and I told him about my current task.
He just smiled and the next day I got email from him with the java agent which inserts
if (!java.awt.EventQueue.isDispatchThread()) {
Thread.dumpStack();
}
to the beginning of each method which name start with "set" or "get" for selected classes in javax.swing package
Binary and source code of the agent is here
To compile and run it you need another bytecode manipulation tool the ASM framework
But wait, if I tried ASM framework, why shouldn't I tried AOP,
which is known as a suitable tool for advanced logging of existing applications?
AspectJ
I downloaded AspectJ, spent one evening and voila:
import javax.swing.*;
aspect EdtRuleChecker {
private boolean isStressChecking = true;
public pointcut anySwingMethods(JComponent c):
target(c) && call(* *(..));
public pointcut threadSafeMethods():
call(* repaint(..)) ||
call(* revalidate()) ||
call(* invalidate()) ||
call(* getListeners(..)) ||
call(* add*Listener(..)) ||
call(* remove*Listener(..));
//calls of any JComponent method, including subclasses
before(JComponent c): anySwingMethods(c) &&
!threadSafeMethods() &&
!within(EdtRuleChecker) {
if(!SwingUtilities.isEventDispatchThread() &&
(isStressChecking || c.isShowing()))
{
System.err.println(thisJoinPoint.getSourceLocation());
System.err.println(thisJoinPoint.getSignature());
System.err.println();
}
}
//calls of any JComponent constructor, including subclasses
before(): call(JComponent+.new(..)) {
if (isStressChecking && !SwingUtilities.isEventDispatchThread()) {
System.err.println(thisJoinPoint.getSourceLocation());
System.err.println(thisJoinPoint.getSignature() +
" *constructor*");
System.err.println();
}
}
}
This aspect detects all EDT rule violations in your Swing code and has the only drawback -
to run it you need to download an additional jar file
Conclusion
We are going to make Swing debugging easier in Java 1.7
but good news that it is possible to detect Event Dispatch Thread rule violations in your Swing code right now.
ThreadCheckingRepaintManager can find a lot of problematic methods invocations,
but if you want to be absolutely sure - make your own java agent or simply use aspects.
"If you call e.g.