Details
-
Bug
-
Resolution: Not an Issue
-
P3
-
7u13
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();
}
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
- relates to
-
JDK-6826514 SwingWorker: done() called before doInBackground() returns, when cancelled
- Open