Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8013812

SwingWorker done() may be called before other Runnables previously queued on EDT

    XMLWordPrintable

Details

    Description

      FULL PRODUCT VERSION :
      java version " 1.7.0_13 "
      Java(TM) SE Runtime Environment (build 1.7.0_13-b20)
      Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Windows 7 64bits
      Mac OS 10.7.5

      A DESCRIPTION OF THE PROBLEM :
      There's a race condition in SwingWorker, where sometimes Runnables sent to the EDT during doInBackground() may be executed AFTER the done() method.

      The problem lies in the implementation of SwingWorker where the event-coalescing algorithm designed for SwingWorker.publish() is also used to post the Runnable that invokes done() on the EDT.


      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      (see reproducer code)

      - during the doInBackground() method, call SwingUtilities.invokeLater() to have some Runnable R1 called on the Event Dispatcher Thread
      - implement done() on your SwingWorker

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      I expect R1 to be executed on the EDT always before SwingWorker.done() (on the EDT too), since R1 is sent to the EventQueue BEFORE doInBackground() returns.
      ACTUAL -
      Depending on timing conditions, sometimes SwingWorker.done() is called BEFORE R1.

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      No crash in JRE, but can lead to nasty bugs in real applications.

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      import java.beans.PropertyChangeEvent;
      import java.beans.PropertyChangeListener;

      import javax.swing.SwingUtilities;
      import javax.swing.SwingWorker;
      import javax.swing.SwingWorker.StateValue;

      public class TestBugSwingWorker {

      public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
      for (int i = 0; i < 100; i++) {
      doInEdt(i);
      }
      }
      });

      }

      private static void doInEdt(final int swId) {

      SwingWorker<Void, String> worker = new SwingWorker<Void, String>() {
      private boolean myInvokeLaterWasCalled = false;

      @Override
      protected Void doInBackground() throws Exception {
      Thread.sleep(1000/30);
      SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
      myInvokeLaterWasCalled = true;
      System.out.println(swId + " : should be #2 : event posted to EDT during doInBackground " );
      }
      });
      return null;
      }

      @Override
      protected void done() {
      System.out.println(swId + " : should be #3 : done() called on SwingWorker " );
      if (!myInvokeLaterWasCalled) {
      System.out.println( " BOOM!! " );
      System.exit(1);
      }
      }

      };

      worker.addPropertyChangeListener(new PropertyChangeListener() {

      @Override
      public void propertyChange(PropertyChangeEvent evt) {
      if ( " state " .equals(evt.getPropertyName())) {
      switch ((StateValue) evt.getNewValue()) {
      case STARTED:
      System.out.println(swId + " : should be #1 : SwingWorker state==STARTED " );
      break;
      case DONE:
      System.out.println(swId + " : should be #4 : SwingWorker state==DONE " );
      break;
      default:
      break;

      }
      }

      }
      });

      worker.execute();
      }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Create a subclass of SwingWorker like SwingWorkerTempFix below, and modify all usages of SwingWorker to use this subclass instead of SwingWorker directly.

      import javax.swing.SwingUtilities;
      import javax.swing.SwingWorker;

      public abstract class SwingWorkerTempFix<T, V> extends SwingWorker<T, V> {

      @Override
      protected final T doInBackground() throws Exception {
      try {
      return doInBackground2();
      } finally {
      SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
      done2();
      }
      });
      }
      }

      @Override
      protected final void done() {
      super.done();
      }

      protected abstract T doInBackground2() throws Exception;

      protected abstract void done2();

      }

      Attachments

        Issue Links

          Activity

            People

              malenkov Sergey Malenkov (Inactive)
              webbuggrp Webbug Group
              Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: