8081474: SwingWorker calls 'done' before the 'doInBackground' is finished#11940
8081474: SwingWorker calls 'done' before the 'doInBackground' is finished#11940prsadhuk wants to merge 25 commits intoopenjdk:masterfrom
Conversation
|
👋 Welcome back psadhukhan! A progress list of the required criteria for merging this PR into |
Webrevs
|
|
Any more comments on this? |
aivanov-jdk
left a comment
There was a problem hiding this comment.
Blocking EDT with cancel is unacceptable.
I propose the following fix (diff to your PR):
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.
| setState(StateValue.STARTED); | ||
| return doInBackground(); | ||
| T ret = doInBackground(); | ||
| setState(StateValue.DONE); |
There was a problem hiding this comment.
What if doInBackground thrown an exception? DONE state will never be reached.
| if (state != StateValue.DONE) { | ||
| do { | ||
| try { | ||
| Thread.sleep(100); | ||
| } catch (InterruptedException e) {} | ||
| } while (state != StateValue.DONE); | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I removed sleep from EDT case and not blocking EDT and guess sleep is now being done for non-EDT case...
If it is to be called from EDT then it should go to "if" block and not to "else" which is what I based my fix on..
Anyway, I appreciate your fix and will see to it..
There was a problem hiding this comment.
Yet having sleep is an indication of busy wait. You could've used CountDownLatch in conjunction with setState to avoid sleep at all.
I removed sleep from EDT case and not blocking EDT and guess sleep is now being done for non-EDT case...
Ah, right. But then if cancel is called on EDT, done is invoked before doInBackground completes, isn't it?
If cancel is called from another thread, the main thread as in the test, it doesn't return until doInBackground completes. With your test case and fix, cancel blocks for 5 seconds.
Either way, the behaviour does not look right.
There was a problem hiding this comment.
It seems the isDone method modification is causing a JCK test to fail and there's no spec for it to challenge JCK test, although logically it seems right that STATE should be DONE but for now, I have modified the fix which satisfies regression test and JCK both..
There was a problem hiding this comment.
isDone() spec mention "Completion may be due to normal termination, an exception, or cancellation -- in all of these cases, this method will return true"
so for cancel state also, the state needs to be in DONE state, in which case we can reinstate the DONE state check in isDone()
There was a problem hiding this comment.
isDone() spec is inherited from Future.isDone() and it is true for how FutureTask is implemented.
jdk/src/java.desktop/share/classes/javax/swing/SwingWorker.java
Lines 565 to 570 in 810c8a2
Can't we change it?
Given what is said in the spec for SwingWorker before, I assume isDone should return true when the status of SwingWorker moves to DONE and it should occur when doInBackground returns as this is defined in the spec:
jdk/src/java.desktop/share/classes/javax/swing/SwingWorker.java
Lines 288 to 293 in 810c8a2
It is also an option. All the suggested approaches set the state to However, the last sentence may not have been guaranteed before either but it was likely satisfied as Hence, a small update to the spec may still be required whatever option we choose. |
| setState(StateValue.DONE); | ||
| return future.cancel(mayInterruptIfRunning); |
There was a problem hiding this comment.
Setting the state to DONE unconditionally doesn't seem right either. What if doInBackground isn't started? What if doInBackground starts after you compare the state is PENDING?
If cancel changes the state, it must do it after future.cancel returns. But then, the state moves to DONE before doInBackground exited which contradicts the spec for DONE.
There was a problem hiding this comment.
OK..I have removed unconditional setting of DONE state..
But then as I mentioned, it would cause JCK failure, so I have reverted isDone() change too...
There was a problem hiding this comment.
OK..I have removed unconditional setting of DONE state.. But then as I mentioned, it would cause JCK failure, so I have reverted isDone() change too...
Should we update the spec for isDone()? I think it's the right way because it makes the behaviour consistent. Doing so requires a CSR which will be the grounds for updating the JCK test.
There was a problem hiding this comment.
I am not sure if it is entirely wrong..I will keep it as it is for now..I dont want to go in CSR route for now..
The present iteration takes care of reproducer and also satisifies JCK..
If in future it is needed, we can take a look..
There was a problem hiding this comment.
Let's see what others have to say.
test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java
Outdated
Show resolved
Hide resolved
test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java
Outdated
Show resolved
Hide resolved
test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java
Outdated
Show resolved
Hide resolved
test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java
Outdated
Show resolved
Hide resolved
|
How the updated code is supposed to work if the worker will have some state updated by the "doInBackground" and used by the "done" method. I guess if the "doInBackground" will be canceled the "done" method may throw an exception? Do we have such usage in our codebase? |
|
The scenario is covered by JCK test and present PR iteration does not show any issue |
What do you mean?
Thus, if Now, Yet no one should have depended on that because it was an implementation detail rather than a contract.
Why is it? This entire scenario is where Yet
I found the
|
I think all of that usages by jconsole will start to throw the "CancellationException" in the "done" method since now we will start to call it. Not sure can it cause some issue or not. |
Why do you think so? The Additionally, the I cannot understand your concern. |
|
Implementation of |
test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java
Outdated
Show resolved
Hide resolved
test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java
Outdated
Show resolved
Hide resolved
test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java
Outdated
Show resolved
Hide resolved
|
@prsadhuk The CSR requirement cannot be removed as there is already a CSR associated with the issue JDK-8081474. Please withdraw the CSR JDK-8301940 and then use the command |
|
/csr unneeded |
test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java
Outdated
Show resolved
Hide resolved
test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java
Outdated
Show resolved
Hide resolved
test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java
Outdated
Show resolved
Hide resolved
test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java
Outdated
Show resolved
Hide resolved
|
I have tried alignment to the extent possible..I am not sure if it's required to be so finicky about it :-) |
Not to the extent possible if it's different from the commonly used style.
It's probably not. However, the code which uses consistent style in the entire project is easier to read, it looks as if it was written by a single person even though it was actually written by a large team of different people. Otherwise, it's like a text with typos, you're still able to comprehend it but you stumble on each occurrence of one. |
In addition to that, in all the cases I have provided suggestions to fix the indentation which can be applied with a single click. |
|
I guess current iteration of the PR takes care of the alignment being suggested.. |
There was a problem hiding this comment.
Should evt.getNewValue() be used to identify the new value of the state property?
The test does not fail if I change the order of the lines in the finally block that reverses the order in which the done method is called and listeners are notified. I expected it to fail. Isn't it the reason why PropertyChangeListener was added?
test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java
Outdated
Show resolved
Hide resolved
aivanov-jdk
left a comment
There was a problem hiding this comment.
I would appreciate if you update the copyright year in SwingWorker.
|
/integrate |
|
Going to push as commit dbb5581.
Your commit was automatically rebased without conflicts. |
SwingWorker done() method spec 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
also says the state should be DONE after doInBackground() returns which is also not done.
Modified the code to honour the specification.
Progress
Issues
Reviewers
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk pull/11940/head:pull/11940$ git checkout pull/11940Update a local copy of the PR:
$ git checkout pull/11940$ git pull https://git.openjdk.org/jdk pull/11940/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 11940View PR using the GUI difftool:
$ git pr show -t 11940Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/11940.diff