Delaying ProgressMonitorDialog Open

May 27, 2002

The Problem

Short operations that pop up a ProgressMonitorDialog do not keep the dialog open long enough for it to be useful. Further, the rapid open/close of the dialog is distracting for the user.

We want to show progress in a less invasive way for short running operations, while keeping the operation as is for long running operations. Possible solutions are to have ProgressMonitorDialog use a delay before it opens, or to use ApplicationWindow to run the operation and report the progress on the status line.

The fork Parameter

Currently, a parameter is passed to ProgressMonitorDialog.run to indicate whether or not to fork a background thread on which to run the operation. As a rule of thumb run should be called with fork = true if it is being called from the UI thread, and fork = false otherwise. However, this is not always true. Editor saves are saved on the UI thread with fork = false due to a hack put in late in the 1.0 release cycle. Editors keep the UI responsive by spinning the SWT event loop manually.

In many solutions, calling run with fork = false on the UI thread will cause the ProgressMonitorDialog to *never* open, since the UI is unresponsive during that time. This behaviour is acceptable, provided that there is a workaround for the editor save case, since we want to discourage blocking the UI thread whenever possible.

Deadlock

Eclipse is not currently written to allow progress to be reported to the UI asynchronously. It is easy to cause Eclipse to deadlock if the user is allowed to work while there is an operation running in the background, due to the way that the workspace lock is allocated/freed.

The simplest deadlock to explain occurs when, while in the process of saving a file, a file is deleted. The save is called with fork = false, but is a special case since it spins its own event loop. Save acquires the workspace lock when it starts, and doesn't release it until it is done. The event loop is run while the UI thread holds the workspace lock. During this time, the delete gets processed. Delete is run with fork = true so it starts a new ModalContextThread to run the operation, and then spins the event loop. This time, the event loop is spinning tightly, and *not* working on the save operation. The ModalContextThread that is created almost immediately requests the workspace lock, which is held by the UI thread stuck in the event loop. The delete cannot start until the save is done, but the save cannot finish until the UI thread breaks out of the event loop.

It is not possible to cancel the delete operation since the ModalContextThread is blocked and cannot check isCancelled until it is unblocked. Eclipse is deadlocked, but the event loop is dunning, so the UI appears responsive, with a modal dialog preventing the user from working.

Solutions

Two possible solutions were considered. The call to ProgressMonitorDialog.open could be put on a timer and run after a specified delay. Otherwise, we could avoid spinning the event loop until the delay is over.

Timers

To prevent the user from interacting with the workbench before the window opens, the workbench needs to be disabled manually. The modality of the ProgressMonitorDialog is only in effect while the dialog is open, so it must be simulated. Opening the dialog off-screen and then moving it is not supported on all platforms.

Given the current API, it is not possible to disable the entire workbench from within ProgessMonitorDialog. At that level, there is no notion of the shortcut bar, nor is there a reference to the coolbar. It is possible to work around this problem by adding API to enable/disable the entire window.

Currently, enabling and disabling the various toolbars is done with a call to setEnabled. However on Windows, for a control with keyboard focus, setEnabled(false) followed by setEnabled(true) will lose the keyboard focus. It does not look like SWT supports querying the focus status of a widget to programmatically restore state.

Some operations pop up a dialog to prompt the user for input after the operation has been started. When the ProgressMonitorDialog is opening on a timer, it can open in front of the dialog that is asking for input. Windows gives focus to the ProgressMonitorDialog in this case, which is modal, so the user cannot interact with the other window. It is not always possible to cancel the operation at this point, since most implementations do not check isCancelled while waiting for user input to a modal dialog.

Event Loop

The UI is unresponsive when the event loop is not spinning. This can cause problems if left too long, so the delay here is bounded above in practice.

ProgressMonitorDialog.open must be called from the UI thread, so it must occur during a asyncExec/syncExec, which are only processed after all queued events have been dispatched. It is important to process the open before any other dialog gets opened, to avoid the problems encountered in the first solution.

Unfortunately, any accelerators that are pressed while the event loop is not running are queued until it starts up again. Accelerators are processed before syncExec and asyncExec so it is possible to start another operation while still running the first. The second operation is not queued, and will run in the first operation's event loop. The second operation can pop up a modal dialog, making the ProgressMonitorDialog inaccessible, or could even deadlock Eclipse.

To avoid this case, it would be necessary to disable all accelerators while the event loop is stopped. Since mouse clicks are queued as well, it is necessary to disable the toolbar, menubar, and shortcut bar as well. In other words, it is still necessary to disable the entire workbench, even though the event loop is not running.

ApplicationWindow.run

It is not possible to start running using ApplicationWindow.run and pop up a dialog after a specified delay for longer running operations. This is the case when the save of a small file invokes a complete rebuild for a project. In this case, all progress is reported to the same progress monitor, which would end up in the status bar only.

This solution also has to disable the workbench manually. It shares the setEnabled and keyboard focus problems mentioned above.