The Source for Java Technology Collaboration
User: Password:
Register | Login help    

Search

Online Books:
java.net on MarkMail:


Debugging Swing, the final summary

Posted by alexfromsun on February 16, 2006 at 11:49 AM PST

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:

  1. It definitely helps in finding EDT problems
  2. You can use it right now, no third-party libs are required
  3. It is simple and elegant

But from the other side I thought about the following problems

  1. repaint() method is thread-safe and it is not a mistake to invoke it out of EDT
  2. 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

  1. It is not a pure java solution, using JNI to invoke java methods from C code is not as clear as that
  2. 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.

Related Topics >> Swing      
Comments
Comments are listed in date ascending order (oldest first)

"If you call e.g.

"If you call e.g. JTextField.setText() from EDT and the same time JTextField.getText() from another thread the result is unpredictable " Bad example, I'm pretty sure that set/getText are declared as thread safe and you are allowed to call them from any thread.