Skip to main content

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 >>

Comments

A simple question. If repaint() is thread safe, why the ...

A simple question.

If repaint() is thread safe, why the related "if" includes also a check on NOT being "fromSwing"?

What would be the problem if both repaint and fromSwing are true?

Thank you.

Use AspectJ to redirect work to the EDT? I realize this is ...

Use AspectJ to redirect work to the EDT?

I realize this is an old article, but very helpful. As this article seems to provide the most definitive answer on how to detect EDT rule violations, I thought I would ask my question here...hopefully people are still reading this article.

Question: Would it be possible to modify the aspect such that code that is not running in the EDT is redirected to run within the EDT? I.e., by using invokeAndWait()?

I suspect the answer is probably 'not really', most likely because we can't be sure of the state of the application leading up to the aspect being triggered - for example, synchronization semantics in a multi-threaded application might break if we start executing code in the EDT. That's just a guess, though, I'm not really certain.

The reason for my interest is the following case: Consider a large, Swing-based application that is (incorrectly) coded in such a way that, say, 80% of the stuff that should take place within the EDT is instead executed in another application thread. My concern is that if we start fixing such problems in piecemeal fashion, bit-by-bit, then we could end up in a situation (as we slowly work toward total resolution of the EDT issues) where there is closer to a 50/50 split between work being done by the EDT vs. an application thread; this could (I speculate) exacerbate runtime problems, leading to more frequent deadlocks, etc. (particularly on multi-core machines.) I.e., a 50/50 split between work done in the EDT vs another application thread may be more prone to deadlock than, say, an 80/20 split.

Do you think that is a valid concern (50/50 split being more problematic)? If it is a valid concern, then we essentially have to either fix everything all at once, or, if possible, use some AspectJ magic to redirect work to the EDT (which would allow us to properly fix the code over time)...hence my question above.

Thanks.

Getting Stacktrace Info

I've found it useful to include stacktrace information when printing the EDT violations. Just add the following to the before() aspect advice:

new Exception().printStackTrace()

"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.