JDK-8013812 : SwingWorker done() may be called before other Runnables previously queued on EDT
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 7u13
  • Priority: P3
  • Status: Closed
  • Resolution: Not an Issue
  • Submitted: 2013-04-04
  • Updated: 2014-11-17
  • Resolved: 2013-07-23
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8
8Resolved
Related Reports
Relates :  
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();

}
Comments
There is a mistake in the test. The done() method (as specified) is executed on the EDTafter the doInBackground method is finished. It should not wait other tasks started from the doInBackground method. You must replace SwingUtilities.invokeLater with SwingUtilities.invokeAndWait in the doInBackground method to ensure that the custom runnable is finished.
23-07-2013