RFR: 8081474: SwingWorker calls 'done' before the 'doInBackground' is finished [v3]
Alexey Ivanov
aivanov at openjdk.org
Sun Jan 29 15:00:19 UTC 2023
On Fri, 13 Jan 2023 04:03:55 GMT, Prasanta Sadhukhan <psadhukhan at openjdk.org> wrote:
>> SwingWorker done() method [spec ](https://github.com/openjdk/jdk/blob/master/src/java.desktop/share/classes/javax/swing/SwingWorker.java#L452) says "Executed on the Event Dispatch Thread after the doInBackground method is finished"
>> but there's no mechanism in place to honor that claim.
>> The [spec](https://github.com/openjdk/jdk/blob/master/src/java.desktop/share/classes/javax/swing/SwingWorker.java#L289)
>> also says the state should be DONE after doInBackground() returns which is also not done.
>>
>> Modified the code to honour the specification.
>
> Prasanta Sadhukhan has updated the pull request incrementally with one additional commit since the last revision:
>
> Remove blocking thread sleep for EDT
Blocking EDT with `cancel` is unacceptable.
I propose the following fix ([**diff to your PR**](https://github.com/openjdk/jdk/compare/pr/11940...aivanov-jdk:prasanta/8081474-SwingWorker-done)):
diff --git a/src/java.desktop/share/classes/javax/swing/SwingWorker.java b/src/java.desktop/share/classes/javax/swing/SwingWorker.java
index 6acca76d859..9da72fe2f0a 100644
--- a/src/java.desktop/share/classes/javax/swing/SwingWorker.java
+++ b/src/java.desktop/share/classes/javax/swing/SwingWorker.java
@@ -301,18 +301,16 @@ public abstract class SwingWorker<T, V> implements RunnableFuture<T> {
new Callable<T>() {
public T call() throws Exception {
setState(StateValue.STARTED);
- T ret = doInBackground();
- setState(StateValue.DONE);
- return ret;
+ try {
+ return doInBackground();
+ } finally {
+ setState(StateValue.DONE);
+ doneEDT();
+ }
}
};
- future = new FutureTask<T>(callable) {
- @Override
- protected void done() {
- doneEDT();
- }
- };
+ future = new FutureTask<T>(callable);
state = StateValue.PENDING;
propertyChangeSupport = new SwingWorkerPropertyChangeSupport(this);
doProcess = null;
@@ -566,7 +564,7 @@ public abstract class SwingWorker<T, V> implements RunnableFuture<T> {
* {@inheritDoc}
*/
public final boolean isDone() {
- return future.isDone();
+ return future.isDone() && state == StateValue.DONE;
}
/**
@@ -753,13 +751,6 @@ public abstract class SwingWorker<T, V> implements RunnableFuture<T> {
if (SwingUtilities.isEventDispatchThread()) {
doDone.run();
} else {
- if (state != StateValue.DONE) {
- do {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {}
- } while (state != StateValue.DONE);
- }
doSubmit.add(doDone);
}
}
or diff to master:
diff --git a/src/java.desktop/share/classes/javax/swing/SwingWorker.java b/src/java.desktop/share/classes/javax/swing/SwingWorker.java
index 0d86075be3f..9da72fe2f0a 100644
--- a/src/java.desktop/share/classes/javax/swing/SwingWorker.java
+++ b/src/java.desktop/share/classes/javax/swing/SwingWorker.java
@@ -301,18 +301,16 @@ public abstract class SwingWorker<T, V> implements RunnableFuture<T> {
new Callable<T>() {
public T call() throws Exception {
setState(StateValue.STARTED);
- return doInBackground();
+ try {
+ return doInBackground();
+ } finally {
+ setState(StateValue.DONE);
+ doneEDT();
+ }
}
};
- future = new FutureTask<T>(callable) {
- @Override
- protected void done() {
- doneEDT();
- setState(StateValue.DONE);
- }
- };
-
+ future = new FutureTask<T>(callable);
state = StateValue.PENDING;
propertyChangeSupport = new SwingWorkerPropertyChangeSupport(this);
doProcess = null;
@@ -566,7 +564,7 @@ public abstract class SwingWorker<T, V> implements RunnableFuture<T> {
* {@inheritDoc}
*/
public final boolean isDone() {
- return future.isDone();
+ return future.isDone() && state == StateValue.DONE;
}
/**
This approach ensures `cancel` completes quickly and `done` is called only after `doInBackground` exits.
I also suggest adding a condition to the test which verifies `cancel` doesn't take too long as well as ensuring `done` was called before exiting from `main`.
The `isDone` method has the same problem: it returns `true` before the background task has completed, that's why I modified it so that it returns `true` only when the state is `DONE` too.
I believe the time of `sleep` to simulate work can be reduced for automatic testing, however, I agree a few seconds' delay is better for debugging. That's why the time could be configurable in constants.
src/java.desktop/share/classes/javax/swing/SwingWorker.java line 305:
> 303: setState(StateValue.STARTED);
> 304: T ret = doInBackground();
> 305: setState(StateValue.DONE);
What if doInBackground thrown an exception? `DONE` state will never be reached.
src/java.desktop/share/classes/javax/swing/SwingWorker.java line 762:
> 760: } catch (InterruptedException e) {}
> 761: } while (state != StateValue.DONE);
> 762: }
In the previous iteration, using `sleep` for waiting was *the concern*, you're still using `sleep`.
This is not going to work because it makes `cancel` wait until `DONE` state is reached, which is not what one would expect, especially taking into account that `cancel` is likely called from EDT to cancel the background operation and blocking EDT is not acceptable.
test/jdk/javax/swing/SwingWorker/TestSwingWorker.java line 32:
> 30: import javax.swing.SwingWorker;
> 31:
> 32: public class TestSwingWorker {
I believe it was decided to give test classes a meaningful names but `TestSwingWorker` isn't better than `bug8081474`. The latter has an advantage though, it's unique.
`DoneBeforeDoInBackground` seems a better name which describes the test purpose, don't you think?
test/jdk/javax/swing/SwingWorker/TestSwingWorker.java line 47:
> 45: }
> 46: }
> 47: catch (InterruptedException ex) {
Suggestion:
} catch (InterruptedException ex) {
`catch` should be on the same line as the closing `brace` of the previous block.
-------------
Changes requested by aivanov (Reviewer).
PR: https://git.openjdk.org/jdk/pull/11940
More information about the client-libs-dev
mailing list