Skip to content

Commit 1341a35

Browse files
committed
Avoid flashing shutdown dialog when save is fast
Replace the CancelableProgressMonitorJobsDialog used during workspace shutdown with a plain ProgressMonitorJobsDialog that opens only after IProgressService#getLongOperationTime has elapsed. During a fast save only a busy cursor is shown, which fixes issue 1269. The previous implementation eagerly opened the dialog and wired a cancel button whose only effect was to clear a sub-task label. Its CancelableProgressMonitorWrapper relied on hard-coded work-amount math to toggle cancellation around the history-pruning phase, an assumption that drifted out of sync with SaveManager. That plumbing is removed along with its now-unused messages, since cancelling shutdown is no longer offered.
1 parent 233d793 commit 1341a35

4 files changed

Lines changed: 100 additions & 108 deletions

File tree

bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/IDEWorkbenchAdvisor.java

Lines changed: 41 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import org.eclipse.core.runtime.IStatus;
4343
import org.eclipse.core.runtime.MultiStatus;
4444
import org.eclipse.core.runtime.Platform;
45-
import org.eclipse.core.runtime.ProgressMonitorWrapper;
4645
import org.eclipse.core.runtime.Status;
4746
import org.eclipse.core.runtime.jobs.ISchedulingRule;
4847
import org.eclipse.core.runtime.jobs.Job;
@@ -58,13 +57,10 @@
5857
import org.eclipse.jface.util.Policy;
5958
import org.eclipse.jface.window.Window;
6059
import org.eclipse.swt.SWT;
61-
import org.eclipse.swt.events.SelectionAdapter;
62-
import org.eclipse.swt.events.SelectionEvent;
60+
import org.eclipse.swt.custom.BusyIndicator;
6361
import org.eclipse.swt.graphics.Resource;
64-
import org.eclipse.swt.widgets.Composite;
6562
import org.eclipse.swt.widgets.Display;
6663
import org.eclipse.swt.widgets.Listener;
67-
import org.eclipse.swt.widgets.Shell;
6864
import org.eclipse.ui.PlatformUI;
6965
import org.eclipse.ui.application.IWorkbenchConfigurer;
7066
import org.eclipse.ui.application.IWorkbenchWindowConfigurer;
@@ -83,6 +79,7 @@
8379
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
8480
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
8581
import org.eclipse.ui.internal.ide.undo.WorkspaceUndoMonitor;
82+
import org.eclipse.ui.internal.progress.ProgressManagerUtil;
8683
import org.eclipse.ui.internal.progress.ProgressMonitorJobsDialog;
8784
import org.eclipse.ui.progress.IProgressService;
8885
import org.eclipse.ui.statushandlers.AbstractStatusHandler;
@@ -464,119 +461,61 @@ public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
464461
}
465462
}
466463

467-
protected static class CancelableProgressMonitorWrapper extends
468-
ProgressMonitorWrapper {
469-
private double total = 0;
470-
private final ProgressMonitorJobsDialog dialog;
471-
472-
CancelableProgressMonitorWrapper(IProgressMonitor monitor,
473-
ProgressMonitorJobsDialog dialog) {
474-
super(monitor);
475-
this.dialog = dialog;
476-
}
477-
478-
@Override
479-
public void internalWorked(double work) {
480-
super.internalWorked(work);
481-
total += work;
482-
updateProgressDetails();
483-
}
484-
485-
@Override
486-
public void worked(int work) {
487-
super.worked(work);
488-
total += work;
489-
updateProgressDetails();
490-
}
491-
492-
@Override
493-
public void beginTask(String name, int totalWork) {
494-
super.beginTask(name, totalWork);
495-
subTask(IDEWorkbenchMessages.IDEWorkbenchAdvisor_preHistoryCompaction);
496-
}
497-
498-
private void updateProgressDetails() {
499-
if (!isCanceled() && Math.abs(total - 4.0) < 0.0001 /* right before history compacting */) {
500-
subTask(IDEWorkbenchMessages.IDEWorkbenchAdvisor_cancelHistoryPruning);
501-
dialog.setCancelable(true);
502-
}
503-
if (Math.abs(total - 5.0) < 0.0001 /* history compacting finished */) {
504-
subTask(IDEWorkbenchMessages.IDEWorkbenchAdvisor_postHistoryCompaction);
505-
dialog.setCancelable(false);
506-
}
507-
}
508-
}
509-
510-
protected static class CancelableProgressMonitorJobsDialog extends
511-
ProgressMonitorJobsDialog {
512-
513-
public CancelableProgressMonitorJobsDialog(Shell parent) {
514-
super(parent);
515-
}
516-
517-
@Override
518-
protected void createButtonsForButtonBar(Composite parent) {
519-
super.createButtonsForButtonBar(parent);
520-
registerCancelButtonListener();
521-
}
522-
523-
public void registerCancelButtonListener() {
524-
cancel.addSelectionListener(new SelectionAdapter() {
525-
@Override
526-
public void widgetSelected(SelectionEvent e) {
527-
subTaskLabel.setText(""); //$NON-NLS-1$
528-
}
529-
});
530-
}
531-
}
532-
533464
/**
534465
* Disconnect from the core workspace.
535466
*
467+
* Shows the progress dialog only if the save operation takes longer than
468+
* the {@link IProgressService#getLongOperationTime() long operation time};
469+
* otherwise a busy cursor is shown while the save runs on a worker thread.
470+
* This avoids a flashing dialog for fast shutdowns.
471+
*
536472
* Locks workspace in a background thread, should not be called while
537473
* holding any workspace locks.
538474
*/
539475
protected void disconnectFromWorkspace() {
540-
// save the workspace
541476
final MultiStatus status = new MultiStatus(IDEWorkbenchPlugin.IDE_WORKBENCH, 1,
542477
IDEWorkbenchMessages.ProblemSavingWorkbench);
543-
try {
544-
final ProgressMonitorJobsDialog p = new CancelableProgressMonitorJobsDialog(
545-
null);
546478

547-
final boolean applyPolicy = ResourcesPlugin.getWorkspace()
548-
.getDescription().isApplyFileStatePolicy();
479+
final ProgressMonitorJobsDialog dialog = new ProgressMonitorJobsDialog(null);
480+
dialog.setOpenOnRun(false);
549481

550-
IRunnableWithProgress runnable = monitor -> {
551-
try {
552-
if (applyPolicy) {
553-
monitor = new CancelableProgressMonitorWrapper(monitor, p);
554-
}
482+
IRunnableWithProgress runnable = monitor -> {
483+
try {
484+
status.merge(((Workspace) ResourcesPlugin.getWorkspace()).save(true, true, monitor));
485+
} catch (CoreException e) {
486+
status.merge(e.getStatus());
487+
}
488+
};
555489

556-
status.merge(((Workspace) ResourcesPlugin.getWorkspace()).save(true, true, monitor));
557-
} catch (CoreException e) {
558-
status.merge(e.getStatus());
559-
}
560-
};
490+
final Display display = PlatformUI.getWorkbench().getDisplay();
491+
final int longOperationTime = PlatformUI.getWorkbench().getProgressService().getLongOperationTime();
492+
final Runnable openDialog = () -> {
493+
if (ProgressManagerUtil.safeToOpen(dialog, null)) {
494+
dialog.open();
495+
}
496+
};
497+
display.timerExec(longOperationTime, openDialog);
561498

562-
p.run(true, false, runnable);
563-
} catch (InvocationTargetException e) {
564-
status
565-
.merge(new Status(IStatus.ERROR,
566-
IDEWorkbenchPlugin.IDE_WORKBENCH, 1,
567-
IDEWorkbenchMessages.InternalError, e
568-
.getTargetException()));
569-
} catch (InterruptedException e) {
570-
status.merge(new Status(IStatus.ERROR,
571-
IDEWorkbenchPlugin.IDE_WORKBENCH, 1,
572-
IDEWorkbenchMessages.InternalError, e));
499+
try {
500+
BusyIndicator.showWhile(display, () -> {
501+
try {
502+
dialog.run(true, false, runnable);
503+
} catch (InvocationTargetException e) {
504+
status.merge(new Status(IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH, 1,
505+
IDEWorkbenchMessages.InternalError, e.getTargetException()));
506+
} catch (InterruptedException e) {
507+
status.merge(new Status(IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH, 1,
508+
IDEWorkbenchMessages.InternalError, e));
509+
}
510+
});
511+
} finally {
512+
display.timerExec(-1, openDialog);
573513
}
514+
574515
if (!status.isOK()) {
575-
ErrorDialog.openError(null,
576-
IDEWorkbenchMessages.ProblemsSavingWorkspace, null, status,
516+
ErrorDialog.openError(null, IDEWorkbenchMessages.ProblemsSavingWorkspace, null, status,
577517
IStatus.ERROR | IStatus.WARNING);
578-
IDEWorkbenchPlugin.log(
579-
IDEWorkbenchMessages.ProblemsSavingWorkspace, status);
518+
IDEWorkbenchPlugin.log(IDEWorkbenchMessages.ProblemsSavingWorkspace, status);
580519
}
581520
}
582521

bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/IDEWorkbenchMessages.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,6 @@ public class IDEWorkbenchMessages extends NLS {
3737
// package: org.eclipse.ui.ide
3838

3939
public static String IDEWorkbenchAdvisor_noPerspective;
40-
public static String IDEWorkbenchAdvisor_cancelHistoryPruning;
41-
public static String IDEWorkbenchAdvisor_preHistoryCompaction;
42-
public static String IDEWorkbenchAdvisor_postHistoryCompaction;
4340

4441
public static String IDE_noFileEditorSelectedUserCanceled;
4542
public static String IDE_noFileEditorFound;

bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/messages.properties

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,6 @@
4141
# package: org.eclipse.ui.ide
4242

4343
IDEWorkbenchAdvisor_noPerspective=No perspectives are open. To open a perspective, press this button:
44-
IDEWorkbenchAdvisor_cancelHistoryPruning=Cancel to skip compacting local history.
45-
IDEWorkbenchAdvisor_preHistoryCompaction=Saving workbench state.
46-
IDEWorkbenchAdvisor_postHistoryCompaction=Disconnecting from workspace.
4744

4845
IDE_noFileEditorSelectedUserCanceled = No editor selected, operation canceled by user.
4946
IDE_noFileEditorFound = No editor found to edit the file resource.

tests/org.eclipse.ui.ide.application.tests/src/org/eclipse/ui/internal/ide/application/IDEWorkbenchAdvisorTest.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class IDEWorkbenchAdvisorTest {
4141
private static final String PLUGIN_ID = "org.eclipse.ui.ide.application.tests";
4242
private Display display = null;
4343
private ISchedulingRule rule;
44+
private final IWorkspace workspace = ResourcesPlugin.getWorkspace();
4445

4546
private static final class SaveHook implements ISaveParticipant, Closeable {
4647
public ISaveContext saving = null;
@@ -200,6 +201,64 @@ public void postShutdown() {
200201
}
201202
}
202203

204+
/**
205+
* Workbench shutdown should complete cleanly when the workspace save takes
206+
* longer than the progress service's long operation time. In that case the
207+
* progress dialog is expected to open after the delay, but the save must
208+
* still run to completion and the save hooks must fire.
209+
*
210+
* Regression test for issue 1269 (flashing shutdown dialog).
211+
*/
212+
@Test
213+
public void testShutdownWithSlowSave() throws CoreException {
214+
try (SaveHook saveHook = new SaveHook()) {
215+
// Exceeds the default IProgressService#getLongOperationTime (800ms),
216+
// so the new code path schedules and opens the progress dialog.
217+
final long sleepMillis = 1200L;
218+
workspace.addSaveParticipant(PLUGIN_ID + ".slow", new ISaveParticipant() {
219+
@Override
220+
public void saving(ISaveContext context) {
221+
try {
222+
Thread.sleep(sleepMillis);
223+
} catch (InterruptedException e) {
224+
Thread.currentThread().interrupt();
225+
}
226+
}
227+
228+
@Override
229+
public void prepareToSave(ISaveContext context) {
230+
}
231+
232+
@Override
233+
public void doneSaving(ISaveContext context) {
234+
}
235+
236+
@Override
237+
public void rollback(ISaveContext context) {
238+
}
239+
});
240+
try {
241+
IDEWorkbenchAdvisor advisor = new IDEWorkbenchAdvisor() {
242+
@Override
243+
public void postStartup() {
244+
super.postStartup();
245+
display.asyncExec(() -> PlatformUI.getWorkbench().close());
246+
}
247+
};
248+
int returnCode = PlatformUI.createAndRunWorkbench(display, advisor);
249+
assertEquals(PlatformUI.RETURN_OK, returnCode);
250+
dispatchDisplay();
251+
252+
assertNotNull(saveHook.prepareToSave);
253+
assertNotNull(saveHook.saving);
254+
assertNotNull(saveHook.doneSaving);
255+
assertNull(saveHook.rollback);
256+
} finally {
257+
workspace.removeSaveParticipant(PLUGIN_ID + ".slow");
258+
}
259+
}
260+
}
261+
203262
/**
204263
* Workbench shutdown should disconnect workspace if it is not locked
205264
*

0 commit comments

Comments
 (0)