The Source for Java Technology Collaboration
User: Password:



Alexander Potochkin

Alexander Potochkin's Blog

Debugging Swing, the summary #1

Posted by alexfromsun on January 12, 2006 at 09:38 AM | Comments (12)

Well, I've got more than 30 comments for the previous blog.

It's high time to summarize them and make up a conclusion.

There are two separate enhancements were suggested in comments:

  1. Make compiler move Swing methods to EDT automatically
  2. Invent something to make finding out-of-EDT methods invocations easier

Making Swing methods threadsafe, simple case

Really, if you call Swing method out from EDT why compiler can't move it to EDT automatically ?

For example we can mark necessary methods with @OnEdt annotaions and...
what's next ?

The compiler could probably insert SwingUtilities.invokeLater automatically:

From

        @OnEdt
        public void removeAll() {
               // implementation skipped...
        }

To

        public void removeAll() {
            if (!SwingUtilities.isEventDispatchThread()) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {                    
                        removeAll();
                    }
                });
                // implementation skipped...
              }              
        }

No need to write InvokeLater(..) yourself anymore, compiler is doing it for us.
Sounds good, but let's think about implementation of this feature.

One of the serious drawback of such solution is a performance:

You'll need an additional inner class for each @OnEdt method,
it will make Swing bigger and slower

The second problem is not so visible,
because this approach works relatively good for simple methods without parameters and return type only

Making Swing methods threadsafe, real case

consider the more usual case:

        @OnEdt
        public Component getComponent(int number) {
             // implementation skipped... 
            return aComponent;
        }

This method takes a parameter and returns a value,
so to make this method thread safe we need:

  1. turn parameter into final int constant to be accessed in the inner class
  2. block the thread and wait for the result,
    so invokeLater() is not good here
  3. it becomes more tricky if methods throws checked exceptions,
    in this case we need to catch them in the inner class and rethrow them properly

The guru of Swing threading and one of the creators of SwingWorker,
Igor Kushnirskiy offers the following scheme to convert the body of EventDispatchThread only method to a thread safe one

        if (!SwingUtilities.isEventDispatchThread()) {
            store all the method arguments in final variables
            Callable callable =
                new Callable() {
                    public T call() throws Exception {
                        return invoke this method;
                    }
                };
            RunnableFuture future = new FutureTask(callable);
            SwingUtilities.invokeLater(future);
            try {
                return future.get();
            } catch (InterruptedException e) {
                //unlikely to happen
              throw new RuntimeException(e);
            } catch (ExecutionException e) {
                //rethrow all exceptions properly
                Throwable cause = e.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                } else if (cause instanceof Error) {
                    throw (Error) cause;
                } else if(cause instaceof one the checked exceptions of the method) {
                    throw (this exception)cause;
                } else {
                    throw new RuntimeException(cause);
                }
             }
         } else {
             return invoke original method body;
         } 

To block the thread during return value calculation
Future
class is used instead of invokeAndWait() but this choice mostly is a matter of style.

Looks much more scary, doesn't it ?

Conclusion

Standard Java doesn't support preprocessors or any other way to insert an additional code into existing method and there are some strong reasons why is it so.

Peter van der Ahe, a java compiler expert expains it in very clear way:
"One of the strong principles of Java is that what you see is what you get. There is nothing fancy going on behind the scenes. Based on this principle, Java doesn't support macros or AOP."

If we generate a bunch of additional code for methods marked with @OnEdt annotation we would compromise this principle.

If java invoked methods on the other thread behind the scene
it might cause difficult to find deadlocks, because it would be not trivial task to underestand threading relations

"What you see is what you get" - the code is 100% controlled by a programmer, no tricky magic, everything is clear

Hence thinking about all listed problems
automatic EDT dispatching doesn't look as good now as it used to.

Check out a good Graham Hamilton's blog about "a multithreaded toolkit dream".

It is becoming clear why SWT, Win32 API, Motif, Xlib and GTK
are not thread-safe libraries as well.

Improving Swing debugging is much more realistic and promising issue,
I'll cover it in the next blog.

Stay tuned !


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Automatic EDT is not realistic. Ok, I trust you on this one as I'm not an expert.
    I'm eager to read your next blog!

    Posted by: imjames on January 12, 2006 at 01:23 PM

  • I wish I could have it automatically logged when a call should have been made on the EDT thread, but was made on a different thread instead.

    Posted by: paulrivers on January 12, 2006 at 01:44 PM

  • SWT faces this same problem; however, due to the ability to start from the ground up, SWT has a potentially more appropriate solution in that it will throw an exception if any thread-sensitive method is invoked out of the event thread.

    Now, that's obviously a contract change that would break a *lot* of Swing applications, but it would be nice to have some form of notification nonetheless. Unfortunately, there isn't much precedent for using logging inside of the JRE libraries, and it would certainly feel invasive if they forcefully logged to java.util.logging or System.out.println if my application made an effort *not* to use those libraries.

    Definitely a frustrating problem, primarily due to backwards compatibility.

    Posted by: rjlorimer on January 12, 2006 at 02:19 PM

  • I didn't mean literally logging to some default logger - I realize I wasn't the clearest, what I meant was I just wish you could set some flag or something and choose to print out messages when stuff that should be on the even dispatching thread is called from a different thread. Maybe you pass something in, maybe it prints it to System.err by default...I don't know, I just want it available. :-)

    Posted by: paulrivers on January 12, 2006 at 02:43 PM

  • To paulrivers:
    When I read first about aspects I thought it was not a good thing, but thinking about this EDT problem I wrote this:


    public aspect TestAspect {

    pointcut swingCode():
    call(* javax.swing.*.*(..)) && !call(* SwingUtilities.*(..));

    before(): swingCode(){
    if (!SwingUtilities.isEventDispatchThread()){
    System.err.println("You call swing in the wrong thread: "+
    " source: "+ thisJoinPoint.getSourceLocation()+
    ", target: "+ thisJoinPoint.toLongString());
    }
    }

    }

    This AspectJ code prints an error whenever you call a method in a javax.swing.* object.
    Of course there are methods that can be called outside of EDT, but that can be coded too.
    With this you dont need to change the JRE library, its small, can be only enabled on development machines etc...
    BTW this is my first AOP code, so Im just glad that it works, so if someone with better knowledge could enchance it it would be cool :).

    Posted by: daniel_f on January 13, 2006 at 01:16 AM

  • Also look here http://today.java.net/pub/a/today/2005/04/19/desktoplive.html where Scott Delap in his book about swing threading shows how to detect calls which should have been on EDT thread.

    Posted by: kundrik on January 13, 2006 at 06:46 AM

  • Im just posting about the automatic EDT thing I just read. There are methods that have been qualified as thread safe in Swing and I use them! It would good not to automatically EDT them. Well, actually auto-EDT may be bad for backward compatibility as well. Though I think it would be interesting to observe some applications that are executed this way... leouser

    Posted by: leouser on January 13, 2006 at 08:54 AM

  • I found this little algorithm online which resembles a bit what is described in desktoplive link.
    I've modified it a little and it's been very helpful detecting thread violations.
    All you need to do is instantiate an object and it will print a stacktrace on every thread violation allowing u to track where the error occurred.
    The point of the class isn't to be run in production environment though.

    import javax.swing.JComponent;
    import javax.swing.RepaintManager;
    import javax.swing.SwingUtilities;

    import sun.awt.AppContext;

    /**
    * A repaint manager that prints a stacktrace to the console when a thread other than the event dispatch
    * thread is calling the paint method. All calls will pass through the method addInvalidComponent and addDirtyRegion.
    * Only one stackt trace will be printed per thread that abuses. Repetitive calls from the same thread are ignored.
    * */
    public class CheckThreadViolationRepaintManager extends RepaintManager {

    /**
    * Used to ensure we only print a stack trace once per abusinh thread.
    */
    private ThreadLocal local = new ThreadLocal();

    /**
    * Will instantiate a CheckingRepaintManager and will assign as the current RepaintManager.
    *
    */
    public CheckThreadViolationRepaintManager() {
    this(true);
    }
    /**
    * Will instantiate a CheckingRepaintManager and will optionally assign it as the current RepaintManager.
    * @param install installe the CheckThreadViolationRepaintManager as the current thread manager
    */
    public CheckThreadViolationRepaintManager(boolean install) {
    if (install) {
    AppContext.getAppContext().put(RepaintManager.class, this);
    System.out.println("installed repaint manager");
    }

    }

    public synchronized void addInvalidComponent(JComponent component) {
    checkThreadViolations();
    super.addInvalidComponent(component);
    }

    public synchronized void addDirtyRegion(JComponent component, int x, int y, int w, int h) {
    checkThreadViolations();
    super.addDirtyRegion(component, x, y, w, h);
    }

    /**
    * Checks if the calling thread is called in the event dispatch thread.
    * If not an exception will be printed to the console.
    *
    *
    */
    private void checkThreadViolations() {
    if (Boolean.TRUE.equals(this.local.get())) {
    return;
    }
    if (!SwingUtilities.isEventDispatchThread()) {
    new Exception().printStackTrace();
    this.local.set(Boolean.TRUE);
    }
    }
    }

    Posted by: filip_pas on January 13, 2006 at 04:00 PM

  • The main thing I want is the ability to turn on assertions that UI methods are called in the EDT. I don't want anything automatic... I just want the ability to have things blow up if I call something the wrong way while I'm in development mode.

    Posted by: reden on January 13, 2006 at 09:20 PM


  • I looks like solution with own RepaintManager will detect only methods causing "addInvalidComponent" or "addDirtyRegion" method calls. What about other Swing methods? I mean that it's possible that some other thread unsafe methods exist that do not cause "addInvalidComponent" or "addDirtyRegion" calls. Am I right?


    To my opinion all public thread unsafe methods in Swing really must have appropriate annotations (like @OnEdit) so that developers could use AOP solution suggested by "daniel_f". It is additionally necessary to check if @OnEdit is set on the called method before dumping stack trace.

    Posted by: maxz1 on January 15, 2006 at 06:21 AM

  • With StackTrace you can open a BeanShell console inside the target process and use the detectBadAwt(); command. It is basically a different implementation of the RepaintManager idea.
    If you run it against NetBeans for example you will see a lot of reports for Swing calls not originating from the EDT.

    Posted by: tmitevski on January 20, 2006 at 01:40 PM

  • You could vote for this RFE if it were still open:

    "Use assertions to warn about AWT/Swing threading problems"

    http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4499547


    Posted by: coxcu on February 03, 2006 at 08:40 AM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds