Search code examples
javaswingcode-analysisevent-dispatch-thread

Find event dispatch thread violations


We all know we should do all GUI related tasks from the event dispatch thread and that weird bugs can be introduced otherwise - I try to remember this rule but I must admit I've noticed a couple of places recently where I haven't.

Is there a way to identify all the violations of this rule so they can be fixed? I've seen that there's a relevant findbugs rule here but it doesn't seem to catch all cases for me. Even throwing an exception whenever a violation occurs would be nice so I can fix it (or catch the exception and log the warning in case a user runs into a related issue.)

What approaches do people generally take with this?


Solution

  • One approach is to install a custom repaint manager which detects and logs when painting is performed on a thread other than the EDT. We use this approach on our project, adapted from the following blog: http://weblogs.java.net/blog/alexfromsun/archive/2006/02/debugging_swing.html. This will not detect all classes of EDT thread violations, but it's definitely much better than nothing.

    Update:

    As a commenter pointed out, the linked web page is no longer available. Here is my code adapted from the blog posting:

    import javax.swing.JComponent;
    import javax.swing.RepaintManager;
    import javax.swing.SwingUtilities;
    import org.apache.log4j.Logger;
    import sun.awt.AppContext;
    
    public class DetectEdtViolationRepaintManager extends RepaintManager {
    
      private static final Logger LOGGER = Logger.getLogger(
        DetectEdtViolationRepaintManager.class);
    
      /**
       * Used to ensure we only print a stack trace once per abusing thread.  May
       * be null if the option is disabled.
       */
      private ThreadLocal alreadyWarnedLocal;
    
      /**
       * Installs a new instance of DetectEdtViolationRepaintManager which does not
       * warn repeatedly, as the current repaint manager.
       */
      public static void install() {
        install(false);
      }
    
      /**
       * Installs a new instance of DetectEdtViolationRepaintManager as the current
       * repaint manager.
       * @param warnRepeatedly whether multiple warnings should be logged for each
       *        violating thread
       */
      public static void install(boolean warnRepeatedly) {
        AppContext.getAppContext().put(RepaintManager.class, 
          new DetectEdtViolationRepaintManager(warnRepeatedly));
        LOGGER.info("Installed new DetectEdtViolationRepaintManager");
      }
    
      /**
       * Creates a new instance of DetectEdtViolationRepaintManager.
       * @param warnRepeatedly whether multiple warnings should be logged for each
       *        violating thread
       */
      private DetectEdtViolationRepaintManager(boolean warnRepeatedly) {
        if (!warnRepeatedly) {
          this.alreadyWarnedLocal = new ThreadLocal();
        }
      }
    
      /**
       * {@inheritDoc}
       */
      public synchronized void addInvalidComponent(JComponent component) {
        checkThreadViolations();
        super.addInvalidComponent(component);
      }
    
      /**
       * {@inheritDoc}
       */
      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 (alreadyWarnedLocal != null && Boolean.TRUE.equals(alreadyWarnedLocal.get())) {
          return;
        }
        if (!SwingUtilities.isEventDispatchThread()) {
          if (alreadyWarnedLocal != null) {
            alreadyWarnedLocal.set(Boolean.TRUE);
          }
          LOGGER.warn("painting on non-EDT thread", new Exception());
        }
      }
    }