Community
Participate
Working Groups
Build: I20040609 1200 If code that runs as a result of dispatching a command cancels, it is now reported in the UI with an error dialog that says, "The command for the key you pressed failed". Cancelation (signalled by OperationCanceledException) should not be reported to the user as a failure. This seems to have only changed recently (since RC1). Here is the log entry that is created: !ENTRY org.eclipse.ui 4 0 Jun 09, 2004 17:48:57.999 !MESSAGE The command for the key you pressed failed !STACK 0 org.eclipse.core.runtime.OperationCanceledException at java.lang.Throwable.<init>(Throwable.java) at org.eclipse.core.runtime.OperationCanceledException.<init>(OperationCanceledException.java:22) at org.eclipse.core.internal.jobs.ThreadJob.joinRun(ThreadJob.java:147) at org.eclipse.core.internal.jobs.ImplicitJobs.begin(ImplicitJobs.java:87) at org.eclipse.core.internal.jobs.JobManager.beginRule(JobManager.java:170) at org.eclipse.core.internal.resources.WorkManager.checkIn(WorkManager.java:95) at org.eclipse.core.internal.resources.Workspace.prepareOperation(Workspace.java:1628) at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:1668) at org.eclipse.jdt.internal.core.JavaModelOperation.runOperation(JavaModelOperation.java:744) at org.eclipse.jdt.internal.core.JavaProject.setRawClasspath(JavaProject.java:2650) at org.eclipse.jdt.core.JavaCore$3.run(JavaCore.java:3433) at org.eclipse.jdt.internal.core.BatchOperation.executeOperation(BatchOperation.java:34) at org.eclipse.jdt.internal.core.JavaModelOperation.run(JavaModelOperation.java:700) at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:1673) at org.eclipse.jdt.core.JavaCore.run(JavaCore.java:3246) at org.eclipse.jdt.core.JavaCore.setClasspathContainer(JavaCore.java:3416) at org.eclipse.pde.internal.core.ModelEntry.updateClasspathContainer(ModelEntry.java:109) at org.eclipse.pde.internal.core.RequiredPluginsInitializer.initialize(RequiredPluginsInitializer.java:40) at org.eclipse.jdt.internal.core.JavaModelManager.initializeContainer(JavaModelManager.java:1248) at org.eclipse.jdt.internal.core.JavaModelManager.initializeAllContainers(JavaModelManager.java:1222) at org.eclipse.jdt.internal.core.JavaModelManager.getClasspathContainer(JavaModelManager.java:833) at org.eclipse.jdt.core.JavaCore.getClasspathContainer(JavaCore.java:1184) at org.eclipse.jdt.internal.core.JavaProject.getResolvedClasspath(JavaProject.java:1888) at org.eclipse.jdt.internal.core.JavaProject.getResolvedClasspath(JavaProject.java:1798) at org.eclipse.jdt.internal.core.search.JavaSearchScope.add(JavaSearchScope.java:78) at org.eclipse.jdt.internal.core.search.JavaWorkspaceScope.initialize(JavaWorkspaceScope.java:80) at org.eclipse.jdt.internal.core.search.JavaSearchScope.<init>(JavaSearchScope.java:52) at org.eclipse.jdt.internal.core.search.JavaWorkspaceScope.<init>(JavaWorkspaceScope.java:31) at org.eclipse.jdt.core.search.SearchEngine.createWorkspaceScope(SearchEngine.java:424) at org.eclipse.jdt.internal.ui.actions.OpenTypeAction.run(OpenTypeAction.java:54) at org.eclipse.jdt.internal.ui.actions.OpenTypeAction.run(OpenTypeAction.java:80) at org.eclipse.ui.internal.PluginAction.runWithEvent(PluginAction.java:276) at org.eclipse.ui.internal.WWinPluginAction.runWithEvent(WWinPluginAction.java:206) at org.eclipse.ui.commands.ActionHandler.execute(ActionHandler.java:141) at org.eclipse.ui.internal.commands.Command.execute(Command.java:132) at org.eclipse.ui.internal.keys.WorkbenchKeyboard.executeCommand(WorkbenchKeyboard.java:469) at org.eclipse.ui.internal.keys.WorkbenchKeyboard.press(WorkbenchKeyboard.java:887) at org.eclipse.ui.internal.keys.WorkbenchKeyboard.processKeyEvent(WorkbenchKeyboard.java:928) at org.eclipse.ui.internal.keys.WorkbenchKeyboard.filterKeySequenceBindings(WorkbenchKeyboard.java:546) at org.eclipse.ui.internal.keys.WorkbenchKeyboard.access$2(WorkbenchKeyboard.java:494) at org.eclipse.ui.internal.keys.WorkbenchKeyboard$1.handleEvent(WorkbenchKeyboard.java:259) at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java) at org.eclipse.swt.widgets.Display.filterEvent(Display.java:713) at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:795) at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:820) at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:805) at org.eclipse.swt.widgets.Control.sendKeyEvent(Control.java:1725) at org.eclipse.swt.widgets.Control.sendKeyEvent(Control.java:1721) at org.eclipse.swt.widgets.Control.WM_CHAR(Control.java:3049) at org.eclipse.swt.widgets.Control.windowProc(Control.java) at org.eclipse.swt.widgets.Display.windowProc(Display.java) at org.eclipse.swt.internal.win32.OS.DispatchMessageW(Native Method) at org.eclipse.swt.internal.win32.OS.DispatchMessage(OS.java:1460) at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:2383) at org.eclipse.ui.internal.Workbench.runEventLoop(Workbench.java:1362) at org.eclipse.ui.internal.Workbench.runUI(Workbench.java:1333) at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:252) at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:141) at org.eclipse.ui.internal.ide.IDEApplication.run(IDEApplication.java:96) at org.eclipse.core.internal.runtime.PlatformActivator$1.run(PlatformActivator.java:334) at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:272) at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:128) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:79) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:41) at java.lang.reflect.Method.invoke(Method.java:386) at org.eclipse.core.launcher.Main.basicRun(Main.java:185) at org.eclipse.core.launcher.Main.run(Main.java:638) at org.eclipse.core.launcher.Main.main(Main.java:622)
The problem is in the commands actionHandler /** * @see IHandler#execute(Map) */ public Object execute(Map parameterValuesByName) throws ExecutionException { if ((action.getStyle() == IAction.AS_CHECK_BOX) || (action.getStyle() == IAction.AS_RADIO_BUTTON)) action.setChecked(!action.isChecked()); try { action.runWithEvent(new Event()); } catch (Exception e) { throw new ExecutionException( "While executing the action, an exception occurred", e); // $NON-NLS-1$ } return null; } As this catches all of them it will also catch cancellations
I am happy to let OperationCancelledExceptions to percolate up from here, but I have one concern. John, you say that this is a recent change, but this code hasn't changed in quite a while. Has something changed in the underlying jobs code that would make this happen?
It was my hunch that this changed recently, but looking at the code, I agree it doesn't look like the commands have changed recently. It turns out that this particular cancelation was a regression (see bug 66437), but it is still possible for it to happen after the JDT fix. I suspect this has not been seen because the large majority of commands will run long running operations in the JFace ModalContext, which converts OperationCanceledException into the checked exception InterruptedException, which must then be caught and handled explicitly by the action. One could argue that action handlers should not be throwing OperationCanceledException, but as a last line of defence I still thing it should be handled by the command infrastructure (perhaps just logged but not with an error dialog to the user?) CCing Nick for a second (or third) opinion on how and by whom cancelation should be handled. I have also logged another bug against JDT for this (bug 66541), since the stack illustrates that the open type command is doing long running work in the UI thread without some form of runnable context (progress dialog, etc).
Neither the JFace action framework nor the commands mechanism have ever had special handling for OperationCanceledException. This needs to be handled by the action itself (JDT's OpenTypeAction). If it's not aware of the possibility of this exception, then it needs to handled lower down in its call chain, e.g. by whoever calls IWorskpace.run, which is spec'ed to throw OperationCanceledException.
The operation canceled excpetion is thrown by a method that doesn't take a progess monitor. So far we only expected operation cancel exceptions or interrupted exceptions in cases where we passed a progress monitor. Following Nick arguments clients now have to wrap all calls with a try/catch OperationCanceledException. Is this really something we want to do ? CC Philippe, since the operation cancel exception is thrown by SearchEngine.createWorkspaceScope.
This was raised and discussed in detail in bug 57594, but I don't know how it was eventually resolved.
The dialog that shows up in this case to the user is scary: The command for the you pressed failed Reason: The command for the key you pressed failed Therefore we should add a band-aid for 3.0. Once the band-aid is in it should be tagged with 3.1.
Erich, are you suggesting a band-aid in the open type dialog, or in the platform command support? Since the JFace ModalContext handles OperationCanceledException, there is some precedent for handling it at the platform level. In 3.0 OperationCanceledException will be more common than before, since we now have a "default progress monitor" that opens a dialog whenever any lock is blocked in the UI thread, and the user is always allowed to cancel it, causing OperationCanceledExceptions that were not previously possible.
Unfortunatly, it is a local band-aid only in the Open Type dialog action, i.e., the OperationCanceledException will be caught so that it doesn't propagate. I understand that this doesn't fully address the problem. This scenario illustrates the more general problem nicely: 1) the OpenTypeAction calls SearchEngine.createWorkspaceScope() -> the API doesn't take a ProgressMonitor -> the signature doesn't declare an exception -> the method returns a non-null search scope. 2) createWorkspaceScope calls a method which takes progress monitor as a side effect. 3) null gets passed 4) the UI is blocked and the user operation is canceled An OperationCanceledException is fired and caught by the Command infrastructure. Given the current API to create a workspace scope I don't see how this problem can be addressed at the platform level. Do you have ideas?
I was just suggesting that when OperationCanceledException is caught and handled (in WorkbenchKeyboard.logException), it could silently log the failure but not open the "scary" dialog for the user. Logging the exception (rather than ignoring it) makes sense because it should not have reached that level and should have been handled higher up in the call chain.
Supressing the error dialog in the case of an OperationCanceledException in WorkbenchKeyboard is the better band-aid and I'm favor of doing this. CC-ing Michael.
Created attachment 12306 [details] Another cancelation reported as error in RC2
Erich, the stack trace attached from John is an operation canceled exception thrown when the package explorer content provider fetches children from the Java model. The only hack I can think of for 3.0 is to catch this exception and return an empty array of children. The problem with this is that this project will never have children then unless the user open/closes the project or the package explorer. Any opinion ? IMO we need a better story for this post 3.0 since putting "random" band-aids into your code doesn't seem to be the right solution. As createWorkbenchScope getPackageFragmentRoots doesn't take a progess monitor.
I find it strange that OperationCanceledException is thrown at all by the infrastructure. Previously, this would only be thrown by the operation itself, after explicitly checking: if (monitor.isCanceled()) throw new OperationCanceledException(); So even if it ran the operation in a progress dialog (or other runnable context) that allowed cancel, and the user hit cancel, OperationCanceledException would only be thrown if the operation checked monitor.isCanceled() and decided to throw it.
We discussed this and as far as I can remember the problem is as follows: - blocking dialog is up - user presses the cancel button - since the actual thread/operation to be canceled is blocked it can't check isCanceled(), so the infra structure decides to throw the exception "on behalf" of the operation. But I agree that this is strange and we should definitelly come up with a better solution here. One idea would be that the caller of run(Operation...) has to provide a cancel handler which gets control when the user presses the cancel button.
Created attachment 12337 [details] Patch for the OpenTypeAction.
Re comment #14 - the API being called in both of these cases is IWorkspace.run. This method takes a progress monitor and can throw OperationCanceledException. The implementation of IWorkspace.run will throw OperationCanceledException when all of the following conditions are true: 1) An operation is running in the UI thread 2) The operation has become blocked by activity in another thread. 3) The progress monitor is null. 4) The user has clicked cancel in the progress dialog that we open in this situation. This is new in 3.0 (since M4) - the old behavior was lengthy hangs with no painting of the UI. Most of these cases end up being involved with classpath computation, which presumably only began to perform locking operations with the introduction of classpath containers. The longer term solution is to avoid long locking operations when in the UI thread, or within API calls that have no progress monitor parameter. This has been discussed at length in bug 57594 and bug 57181. I was only suggesting a workaround to handle this situation a bit better - I know it is not the right answer.
Fix released for OpenTypeAction. Reviewed by Martin Aeschlimann.
The operation canceled exception thrown when the user expnads a node in the tree has to be handled by JFace. The only change JDT/UI could do is to catch the exception in the content provider and then returning an empty array. But this would mean that the "node" is dead. Meaning although it has children it can never be expanded again. JFace could detect this case in expand node and could still keep the dummy child element so that the node can be expanded again. Moving to Platform/UI for consideration.
doug please investigate what we can do here regarding comment #19
I believe Dirk is referring to IStructuredContentProvider.getElements(). Unfortunately, this method is called by more than just StructuredViewer.getRawChildren(). It is called by the following methods: + StructuredViewer.getRawChildren(Object) + CheckedTreeSelectionDialog.createSelectionButtons(Composite) + CheckedTreeSelectionDialog.evaluateIfTreeEmpty(Object) + ElementTreeSelectionDialog.evaluateIfTreeEmpty(Object) + ListSelectionDialog.okPressed() + ResourceTreeAndListGroup.aboutToOpen() (and several other methods) + CheckboxTreeAndListGroup.aboutToOpen() (and several other methods) + NewProgressViewer.contentProviderGetRoots(Object) + ActionSelectAll.run() This is a lot of places to try to defend against OperationCanceledExceptions, when (ultimately) it is the structured content provider that knows whether this kind of exception can be thrown. I think the "right" solution, might be to create an IStructuredContentProvider2, which allows for a concept of cancellation. Deferring the remainder of this fix from RC3. After 3.0, we will consider this fix. Changing to the "Viewers" component, and moving back to Tod (current owner of viewers).
Douglas, wouldn't it be enought to fix this bug to handle the operation canceled exception in AbstractTreeViewer.createChildren ?
From my perspective, that would be fixing one particular place where things could go wrong, while leaving the others broken. This seems wrong. Unless you think that IStructuredContentViewers used in those other cases will never use the jobs infrastruction...? But then again, this in't my area of expertise, so I might be missing something here.
It seems like a losing battle to handle cancelation in the viewer framework. I would expect it to just percolate up to the event loop and be handled by the workbench advisor's exception handler - allowing the app to decide how they want to handle cancelation.
It would be possible to make viewers be aware of cancelation, either via new API or having it handle OperationCanceledException (although we generally try to minimize the dependencies on runtime from JFace). Just having it percolate up to the event loop may leave the viewer in an inconsistent state. For example, in AbstractTreeViewer.handleTreeExpand, it calls createChildren. This deletes any dummy element (needed to show the +), then asks for the child elements from the content provider, then creates the corresponding SWT items. If the call to the content provider fails, either due to an OperationCanceledException or some other exception, then the parent node will be expanded but will have no children. Fortunately, looking at the way createChildren is implemented, it should ask the content provider again if the parent node is collapsed and then expanded again.
We (Nick, John, Doug and myself) have discussed this and have come to the consensus that allowing an OperationCancelledException through is a programming error as Nick says. However we think that it would be best that the commands support uses the exception handling from the WorkbenchAdvisor rather than its own. Forwarding to Doug for that work.
Can you please advise what a content provider should do in this situation to avoid that the viewer ends up in a dead state.
The root of the problem in these JDT cases is the classpath container initialization that locks the workspace when calling seemingly read-only Java model methods. This is unexpected from the point of view of a UI-based caller, so they do not fork the call into a background thread. and can easily result in the UI thread being locked unexpectedly. In another bug I suggested the possibility of JDT core persisting the value of classpath containers between sessions, removing the need to perform this complex container initialization at the start of every session. Then JDT UI could then safely call these methods without fear of locking or cancelation. From the JFace viewer point of view, OperationCanceledException is just another runtime exception. As long as viewers are able to recover gracefully from runtime exceptions (as comment #25 suggests), then I don't see what else they need to do.
Comment #26 gives the impression that Platform/UI decided to not handle the case of a canceled getChildren call in the viewers. If this isn't the case then skip the rest of this comment. IMO we have the following "general" situation: - methods can throw an OperationCancelException. This is especially true if the method takes a progress monitor. - if a content provider calls such a method it must deal with the operation cancel exception - due to the current implementation of the Tree Viewer there is nothing the content provider can do to avoid that the node to be expanded gets dead. So we need some additional API to signal such a situation to the Viewer. I see two possibilities: - the content provider API takes a progress monitor and is allowed to throw an OperationCancelException or an InterruptedException. - we get some additional API on a viewer to inform it about the cancelation of a getChildren/getElement call Even if JDT/Core would fork the call into the background then the only difference would be the type of the exception. We would get an Interrupted exception not a OperationCancelException. But the content provider would still need to signal the cancelation to the viewer.
Without introducing new API, it may be possible to force the viewer to "fix" itself after a cancelation, e.g. using setExpanded and/or refresh(Object). This may need to be done outside the expand callback. Note that refresh will drop the children if the parent node is collapsed, causing it to request the children again the next time it's expanded. However, I think we really need to step back and think about how to really make the viewer framework able to handle long running operations and also cancelation, and simplify this for the programmer.
At the very least, the key binding architecture shouldn't handle the exception itself. It should pass it through the WorkbenchAdvisor. I'll do that piece of the work to start with. It might be appropriate to open a separate bug to deal with the OperationCanceledException in general. I'm not sure, but this seems like a design flaw. RuntimeExceptions can interrupt the flow of execution (leaving partially initialized data). So, doesn't this mean that any place we call out to third-party plug-ins (from UI code) we need to defend against RuntimeExceptions? I mean, this was always the case, but the OperationCanceledException seems like it might make this a little easier to trigger.
I've decided not to make any changes to the command architecture regarding how it deals with exceptions. I had a previous request to ensure that a dialog opens if a command fails (Bug 52138), and I still believe that this is the right thing to do. Unfortunately, the WorkbenchAdvisor does not currently do this, nor do I feel that it is necessarily the right thing for the WorkbenchAdvisor to do. There might be something that can be done by the JFace framework to better handle OperationCanceledExceptions. In particular, there is a sub-thread in this bug talking about viewers. I would recommend that, if this is still important, a separate bug be opened.
Open bug 92347 to track the problem with the stale viewer in the present of runtime exceptions.