Bug 241244 - [Status Handling] StatusManager.handle(status, StatusManager.BLOCK) does not block within Wizard.performFinish
Summary: [Status Handling] StatusManager.handle(status, StatusManager.BLOCK) does not ...
Status: RESOLVED FIXED
Alias: None
Product: Platform
Classification: Eclipse Project
Component: UI (show other bugs)
Version: 3.4   Edit
Hardware: PC Windows XP
: P3 normal (vote)
Target Milestone: 3.5 M3   Edit
Assignee: Szymon Brandys CLA
QA Contact:
URL:
Whiteboard:
Keywords: investigate
Depends on:
Blocks: 219136 501681
  Show dependency tree
 
Reported: 2008-07-17 09:25 EDT by Florian Thienel CLA
Modified: 2016-09-20 10:30 EDT (History)
5 users (show)

See Also:


Attachments
a simple application that demonstrates the problem (60.04 KB, application/octet-stream)
2008-07-17 09:25 EDT, Florian Thienel CLA
no flags Details
Proof of concept (15.95 KB, patch)
2008-07-31 10:55 EDT, Krzysztof Daniel CLA
no flags Details | Diff
mylyn/context/zip (40.77 KB, application/octet-stream)
2008-07-31 10:55 EDT, Krzysztof Daniel CLA
no flags Details
Proof of concept (15.21 KB, patch)
2008-08-01 08:12 EDT, Krzysztof Daniel CLA
no flags Details | Diff
Patch preview (in the middle of work) (26.43 KB, patch)
2008-08-04 10:08 EDT, Krzysztof Daniel CLA
no flags Details | Diff
mylyn/context/zip (62.62 KB, application/octet-stream)
2008-08-04 10:08 EDT, Krzysztof Daniel CLA
no flags Details
Patch RC1 (29.57 KB, patch)
2008-08-05 05:25 EDT, Krzysztof Daniel CLA
no flags Details | Diff
mylyn/context/zip (28.80 KB, application/octet-stream)
2008-08-05 05:25 EDT, Krzysztof Daniel CLA
no flags Details
Patch (28.63 KB, patch)
2008-09-11 07:08 EDT, Krzysztof Daniel CLA
no flags Details | Diff
Test for latest patch (12.54 KB, patch)
2008-09-11 11:27 EDT, Krzysztof Daniel CLA
no flags Details | Diff
Szymon's proposal v01 (3.11 KB, patch)
2008-09-16 04:08 EDT, Szymon Brandys CLA
no flags Details | Diff
Szymon's idea v01 (3.81 KB, patch)
2008-09-16 12:15 EDT, Szymon Brandys CLA
no flags Details | Diff
Szymon's idea v02 (4.34 KB, patch)
2008-09-16 12:26 EDT, Szymon Brandys CLA
no flags Details | Diff
Szymon's proposal v02 (4.90 KB, patch)
2008-09-18 08:49 EDT, Szymon Brandys CLA
no flags Details | Diff
Szymon's proposal v02 (4.90 KB, patch)
2008-09-18 08:54 EDT, Szymon Brandys CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Florian Thienel CLA 2008-07-17 09:25:11 EDT
Created attachment 107731 [details]
a simple application that demonstrates the problem

Build ID: I20080617-2000

Steps To Reproduce:
1. Import the attached demo project
1. Start the application
2. Click on "Finish" in the wizard

A StatusDialog should appear and block the UI before the wizard is closed. Instead, the dialog shortly appears and then the dialog and the wizard are closed automatically.
Comment 1 Krzysztof Daniel CLA 2008-07-17 10:13:57 EDT
Actually if you return false from your performFinish method everything will work correctly.
False means that finish request was refused.

The status dialog does not block the thread and is usually displayed on the most top shell. So if you close your shell (f.e. wizard by specyfying true) the dialog will be closed, either.

In the wizard you should use setErrorMessage to indicate that user made something wrong and override canFinish method, and this will cause finish button to be enabled/disabled.

The real issue here is: StatusManager.handle(status, StatusManager.BLOCK) is not blocking. 
The javadoc of StatusManager.BLOCK says:

A style indicating that the handling should block the calling method until the user has responded. This is generally done using a modal window such as a {@link Dialog}.

So yes, it is a bug and I will try to fix it.
Comment 2 Kevin McGuire CLA 2008-07-17 10:25:41 EDT
Reassigning to Krzysztof for investigation.
Comment 3 Krzysztof Daniel CLA 2008-07-17 11:53:33 EDT
My thoughs:
*. Blocking UI Thread causes the whole application to stop responding - any other status cannot be added until the user closes the dialog. 
*. More than one status can be displayed in the dialog, and each can be from different thread.
*. The dialog can switch modality (reopening with different flag). However it is not an issue as long as dialog can become modal but cannot return to nonmodal state. Also we have to be ensure that a non blocking status arriving to the modal dialog will not block the parent thread.

So:
* we have to ensure that other threads will be able to communicate with UI one even if UI call is blocked. I wonder if it is possible at all.
* alternatively we may consider acceepting fact that the dialog will not process any other status until user reacts to the UI blocking one.
* we have to ensure that WorkbenchErrorHandler will remember which threads are blocked and which are not.
* we can ignore modality switching becasue the dialog will become modal if and only if it receives blocking status adapter. Then no further modality change is possible.

Further investigation still required. This bug is blocking 219136 because we cannot require any synchronic user action until we will have this bug solved.

Thought? Comments?
Comment 4 Kevin McGuire CLA 2008-07-29 13:20:46 EDT
(In reply to comment #3)

I'm still a little confused because I can see in StatusDialogManager.addStatusAdapter() we handle modal, which we determined based on the style state BLOCK.

> * we have to ensure that other threads will be able to communicate with UI one
> even if UI call is blocked. I wonder if it is possible at all.

Once the UI thread is blocked (e.g. with a modal dialog) then nobody else can communicate with it unless you specifically write code to poll from within the UI thread.

> * alternatively we may consider acceepting fact that the dialog will not
> process any other status until user reacts to the UI blocking one.

We have two cases here: those from the UI thread and those from a background thread.  The former case can't occur for the reasons above; the UI thread is busy and no other events can be processed (and status issues) until its done.  

In the latter case, does this not already work?  If I have a status handler modal dialog up and someone in another thread keeps issuing statuses, they show up in the dialog right?

> * we have to ensure that WorkbenchErrorHandler will remember which threads are
> blocked and which are not.

This is only true if you want to honour StatusManager.Block from non-UI threads.  I'm not sure that was ever intended. The javadoc you cited below specifically calls out the use of a Dialog. The intention seems to be to prevent the user from proceeding while the being informed of the status, not of all processing to halt. 

Note it uses the word "should" not "must", which combined with it being a style flag, suggests its only a hint, not a contract.  Thus I think we could only enforce it if its on the UI thread if we so chose.

If you really wanted to block from any thread, I'm thinking a Display.syncExec() which then opens the dialog is enough (javadoc says, "The thread which calls this method is suspended until the runnable completes").  But again I'm not advocating this as I don't think it was the intention.

> * we can ignore modality switching becasue the dialog will become modal if and
> only if it receives blocking status adapter. Then no further modality change is
> possible.

Agree (until dialog is dismissed).

As a final note, I'm wondering about WorkbenchStatusDialog.Manager.addStatusAdapter(), it has a comment midway down:

		// Add the error in the UI thread to ensure thread safety in the
		// dialog

but I don't actually see anything that suggests the work is being done in the UI thread.  I'd have expected to see a Display.syncExec() or asyncExec().
Comment 5 Krzysztof Daniel CLA 2008-07-30 08:00:52 EDT
About final note: I will remove that comment. At the beginning of this method there is an assertion which checks if we are in UI thread.

Because dialog.setBlockOnOpen(true) is not used, the dialog may appear modal but it will return from open() immediately. In consequence the whole routine ends pretty fast and the UI thread is never blocked. Even if you open the dialog from UI thread, other statuses will appear.

> only a hint, not a contract.
This is crucial. We can suggest that particular Status should be blocking or not, but one cannot assume that his hint will be respected, especially if error handler can be easily replaced. 

I see two possibilities here:
1. move the responsibility for blocking to the StatusManager
2. update javadoc to say clearly that we block users from proceedings, not threads, and that this is a hint.
3. wait

ad 1. Seems to be difficult and brings us back to the discussion about UI/non-UI threads. Also, BLOCK is no longer hint, but is a contract, while other flags are hints. I do not like this inconsistency.
ad 2. The simples approach, but it means that we resign from blocking threads.
ad 3. Wait for a couple of months, maybe some new facts will appear or architecture will change.
Comment 6 Krzysztof Daniel CLA 2008-07-30 08:02:01 EDT
I forgot - my preferred is 3.
Comment 7 Kevin McGuire CLA 2008-07-30 14:23:25 EDT
(In reply to comment #5)
> About final note: I will remove that comment. At the beginning of this method
> there is an assertion which checks if we are in UI thread.

Ah yes didn't spot that.

First off, I notice that the javadoc doesn't state this as a requirement (that must be in UI thread).  Second, would it not be better if we allowed from any thread then opened the dialog in a sync/asyncExec?  This allows people to request that status' be processed regardless of the thread, which seems a good thing.

So it seems that we can't actually handle statuses on any thread other than the UI thread, is this right? (ie. there isn't a syncExec() along the call chain that I missed?).

> Because dialog.setBlockOnOpen(true) is not used, the dialog may appear modal
> but it will return from open() immediately. In consequence the whole routine
> ends pretty fast and the UI thread is never blocked. Even if you open the
> dialog from UI thread, other statuses will appear.

Good catch. This is the bug then isn't it?  If BLOCK is used then we should dialog.setBlockOnOpen(true)?

> > only a hint, not a contract.
> This is crucial. We can suggest that particular Status should be blocking or
> not, but one cannot assume that his hint will be respected, especially if error
> handler can be easily replaced. 

Good point but I'd attack it the other way: if we believe its a contract, then  those who provide their own handler must respect it too.  This clearly increases the complexity of their job though (so another argument against).

> I see two possibilities here:
> 1. move the responsibility for blocking to the StatusManager
> 2. update javadoc to say clearly that we block users from proceedings, not
> threads, and that this is a hint.
> 3. wait
> 
> ad 1. Seems to be difficult and brings us back to the discussion about
> UI/non-UI threads. Also, BLOCK is no longer hint, but is a contract, while
> other flags are hints. I do not like this inconsistency.

It can still be hint, we just observe it and try to do the right thing. Otherwise I'm not sure what BLOCK means (why have a  hint if we never do anything based on?).  But see next comment.

> ad 2. The simples approach, but it means that we resign from blocking threads.

First we need to decide what we think the current Javadoc implies.  To me, its ambiguous.  I suppose one can argue that if they used the BLOCK flag then in fact this is what they expected to happen, although I'd find it an odd programming practice on the part of the caller.  

Here's the two interpretations:

Case A) Blocking dialog only if on UI thread
We could open dialog blocking if both BLOCK and on the UI thread. 

Case B) Blocking UI dialog from any thread
A combination of syncExec() and setBlockOnOpen() should work I'd think. And based on above discussion, if non UI thread and non-block then we do asyncExec().

Either way what we believe we're doing is *clarifying* the existing API.

> ad 3. Wait for a couple of months, maybe some new facts will appear or
> architecture will change.

Fooling around with threading and blocking is tricky.  I'd rather do this work sooner than later to give time for usage and feedback.  I'm not sure what new information we'd be hoping for in the few months. 
Comment 8 Krzysztof Daniel CLA 2008-07-31 10:54:22 EDT
> * ie. there isn't a syncExec() along the call chain that I missed?

Yes, WorkbenchErrorHandler was responsible for executing addStatusAdapter in UI Thread.

Something is getting shape, step by step:
For me BLOCK meant that the user should respond to the status before RCP will take any further action. Modal dialog did the thing, because usually reporting an error is the last thing you do. Florian has shown the example when such approach does not work and point that BLOCK should really block the calling thread.

To clarify the API we have to emphasize that BLOCK is only a suggestion and should be used with great care.

I have done some initial work (I'll attach patch) and it looks like we have to block UI thread when we want to use block flag. It is so, because opening a window has to be done in UI thread, and dialog.open is the blocking method.

This means that if any thread uses BLOCK, UI thread will be blocked, and initial thread either, via syncExec.

I have to admit that I do not like this solution at all, because no other thread can report an error. Current situation with corrected javadoc is better than that.

Tomorrow I will try to investigate other approach, with semaphores.
Comment 9 Krzysztof Daniel CLA 2008-07-31 10:55:32 EDT
Created attachment 108860 [details]
Proof of concept

Changing BLOCK behavior invalidates tests.
Comment 10 Krzysztof Daniel CLA 2008-07-31 10:55:36 EDT
Created attachment 108861 [details]
mylyn/context/zip
Comment 11 Krzysztof Daniel CLA 2008-08-01 08:12:51 EDT
Created attachment 108947 [details]
Proof of concept

Another approach - When UI thread is called with BLOCK we simply display call-blocking dialog.

If another thread uses BLOCK, we make him 'wait' and 'notify' when the dialog is closed.

This is not a patch, just an approach presentation. 

I'd like to investigate this direction further.
Comment 12 Krzysztof Daniel CLA 2008-08-04 10:08:49 EDT
Created attachment 109067 [details]
Patch preview (in the middle of work)
Comment 13 Krzysztof Daniel CLA 2008-08-04 10:08:53 EDT
Created attachment 109068 [details]
mylyn/context/zip
Comment 14 Kevin McGuire CLA 2008-08-04 15:36:42 EDT
(In reply to comment #8)

Good analysis I agree with your points.

> I have to admit that I do not like this solution at all, because no other
> thread can report an error. 

That's my concern too. It basically gets us back to the old support where the user gets pestered by a sequence of dialogs appearing and being dismissed (say a runaway process causing errors).

Also the situation is ambiguous.  Did the use of BLOCK mean they didn't want the user to be able to interact with the UI until the error was dealt with, or that the thread be suspended until the user takes some action?  Both makes sense when issued from the UI thread but its hard for me to imagine the latter being the right choice from another thread (e.g. imagine a builder generating errors, what purpose does suspending the process for each one provide?).

As I think more on this, I think the right answer is modal dialog when issued from UI thread and nothing when from another thread, treating it as a hint. If in the future we decide we want blocking from any thread then we can add.  This bug is just about the UI thread case and perhaps we should just fix the obvious and leave the ambiguous.
Comment 15 Krzysztof Daniel CLA 2008-08-05 05:25:28 EDT
Created attachment 109140 [details]
Patch RC1

I am more than suprised because I have spot that even while blocking the UI thread, other threads are able to report errors.

I suspect that this is caused by the dialog#open which runs its own event loop while called in a blocking mode.

This leads to the interesting situation, in which UI thread is blocked (the calling one), but is still running(event loop) - almost like quantum physics :D.

I am happy with current patch. I block every thread that used 'BLOCK' hint. BLOCK generally should be used with great care (see bug 232036).
Comment 16 Krzysztof Daniel CLA 2008-08-05 05:25:32 EDT
Created attachment 109141 [details]
mylyn/context/zip
Comment 17 Kevin McGuire CLA 2008-08-11 15:00:53 EDT
CC'ing Kim.  I discussed this briefly with her since she's really good at these UI + threads kinds of problems.
Comment 18 Krzysztof Daniel CLA 2008-09-11 07:08:14 EDT
Created attachment 112300 [details]
Patch

Now syncExec is used only for blocking calls and the javadoc for BLOCK flag was updated.

I am going to create tests right soon.
Comment 19 Krzysztof Daniel CLA 2008-09-11 11:27:06 EDT
Created attachment 112315 [details]
Test for latest patch

This test is a totally h4x0r but it works... The first attachment is a manual test case.
Comment 20 Szymon Brandys CLA 2008-09-16 04:08:35 EDT
Created attachment 112636 [details]
Szymon's proposal v01
Comment 21 Szymon Brandys CLA 2008-09-16 12:15:52 EDT
Created attachment 112683 [details]
Szymon's idea v01
Comment 22 Szymon Brandys CLA 2008-09-16 12:17:33 EDT
Paul, could you take a look at the Szymon's proposal v01 and comment? It is just a prototype, so don't worry that I made one method public.
Comment 23 Szymon Brandys CLA 2008-09-16 12:21:49 EDT
(In reply to comment #18)
Some remarks:
- we should not change the contract that WorkbenchStatusDialogManager#addStatusAdapter is called from the UI thread
- we should not change the behavior of WorkbenchStatusDialogManager#addStatusAdapter, 'modal' controlled only modality, now there is 'blocking', that blocks control threads if true (that's why Szymon's proposal v01 is not correct too)




Comment 24 Szymon Brandys CLA 2008-09-16 12:26:01 EDT
Created attachment 112686 [details]
Szymon's idea v02

StatusManager#BLOCK javadoc updated.
Comment 25 Paul Webster CLA 2008-09-16 14:18:55 EDT
(In reply to comment #24)
> Created an attachment (id=112686) [details]
> Szymon's idea v02
> 

For the case where you are not on the display thread in org.eclipse.ui.statushandlers.WorkbenchErrorHandler.handle(StatusAdapter, int), wouldn't you do similar code to what was in the UI thread:

Display.getDefault().asyncExec(new Runnable() {
public void run() {
if (!PlatformUI.isWorkbenchRunning()) {
// we are shutting down, so just log
WorkbenchPlugin.log(statusAdapter.getStatus());
return;
}
getStatusDialogManager().addStatusAdapter(statusAdapter, modal);
if (modal) {
Display display = Display.getCurrent();
while (getStatusDialogManager().getShell() != null
&& !getStatusDialogManager().getShell().isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
}
});

Another possibility if you don't want to collapse the if(!modal) to within the asyncExec(*) is to use a syncExec(*) for the modal case and put the "if (!display.readAndDispatch())" loop in the body of the syncExec(*).

Other comments:

1) even as written, shouldn't the modal async have "if (!PlatformUI.isWorkbenchRunning()) {" just like the non-modal one?

2) If you are doing a while "// do nothing but wait" loop, aren't you supposed to have a call that will yield? Thread.sleep() might be too heavy weight, but something like that ... maybe Thread.yield() is appropriate?

PW
Comment 26 Szymon Brandys CLA 2008-09-18 08:49:31 EDT
Created attachment 112890 [details]
Szymon's proposal v02

I added Paul's suggestions + code is polished.
Comment 27 Szymon Brandys CLA 2008-09-18 08:54:41 EDT
Created attachment 112891 [details]
Szymon's proposal v02
Comment 28 Szymon Brandys CLA 2008-09-18 09:02:32 EDT
Bug 247818 needs to be fixed as soon as this patch is released.
Comment 29 Szymon Brandys CLA 2008-09-22 06:04:29 EDT
Released to HEAD.
Comment 30 Kevin McGuire CLA 2008-09-26 10:04:03 EDT
The getShell() call in the patch is a concern:

+ private void showStatusAdapter(StatusAdapter statusAdapter, boolean block) {
...
+	Display display = getStatusDialogManager().getShell().getDisplay();

because getShell() can return null if the dialog is null.  In practice I'm not sure under which conditions this could be true; either the dialog had failed to open, wasn't open yet, or perhaps it could be headless status management.

The while() just after that call checks getShell() != null so we could instead just move the getDisplay() down into the while.


Comment 31 Kevin McGuire CLA 2008-09-26 11:49:20 EDT
Moved the getDisplay into the while where we know shell is not null
Comment 32 Kevin McGuire CLA 2008-09-26 11:49:54 EDT
(Released to HEAD)