Skip to content

IProgressService.run(fork, cancelable, runnable) behaves inconsistently #4202

Description

@fedejeanne

I noticed an inconsistent behavior while working on #4086.

Summary

org.eclipse.ui.progress.IProgressService.run(boolean fork, boolean cancelable, IRunnableWithProgress runnable) does not consistently honor the "Always run in background" preference (Window/Preferences > General). Depending on the fork/cancelable combination passed in, the preference is silently ignored and a modal progress dialog pops up anyway — even though the user explicitly asked for operations to run in the background.

There are two other confusing (but arguably "working as documented") behaviors around thread-affinity and the fork=false contract that make the overall API very easy to misuse. A demo view (ProgressServiceView, see below) makes all of this directly observable/reproducible.

A screen recording demonstrating the bug and the fix using this view is attached to this issue.

Screen recording in 2025-06

Image


Screen recording in 2026-09 I-BUILD I20260722-2300 (likely M2)

Image


Environment

  • Eclipse Platform UI (org.eclipse.ui.workbench), class org.eclipse.ui.internal.progress.ProgressManager
  • Reproduced via a new example view: org.eclipse.ui.examples.jobs.views.ProgressServiceView (See linked PR: Fix/progress service run #4203)

Root cause

ProgressManager.run(...) branches on !fork || !cancelable to decide how to run the operation:

  • When fork == false or cancelable == false, it goes through ProgressMonitorJobsDialog directly, which correctly calls shouldRunInBackground() and suppresses the modal dialog when the preference is enabled.
  • When fork == true && cancelable == true (the most common call shape — e.g. simply run(true, true, runnable)), it delegates to busyCursorWhile(runnable), which never checks the preference and always schedules the dialog-opening job.

Net effect: with "Always run in background" enabled, run(true, true, runnable) still shows a modal progress dialog. The preference is only honored for some fork/cancelable combinations, not all of them, even though nothing in the IProgressService or IRunnableContext javadoc suggests the preference should only apply to some combinations.

Reproduction steps (via ProgressServiceView)

NOTE: ProgressServiceView is added in the linked PR: #4203

  1. Open ProgressServiceView (Window > Show View > Other... > Progress Examples > Progress Service).
  2. Check "Always run in background" (or set it under Preferences > General — the view's checkbox stays in sync either way).
  3. Check both fork and cancelable, set a duration long enough to exceed the short-operation threshold (e.g. 3000 ms), click Run.
  4. Observed (buggy) behavior: a modal progress dialog appears anyway, blocking interaction with the rest of the workbench, even though the user asked for background execution.
  5. Expected behavior: no dialog should appear; the UI shouldn't freeze, showing the operation ran in the background exactly like the other fork/cancelable combinations already do correctly.

Other confusing (non-bug, but easy-to-misuse) behaviors surfaced by the same view

These are not being changed — they match documented contracts — but they explain why this API is easy to get wrong in practice, and are demonstrated by dedicated buttons in ProgressServiceView:

  • Calling IProgressService.run(...) from a non-UI thread throws a raw SWT "invalid thread access" exception. This matches IProgressService's class javadoc ("All methods on the progress service must be called from the UI thread"), but the exception itself is generic/SWT-level and gives no hint about why. The "Run from non-UI thread" button in the view reproduces this.

  • run(false, cancelable, runnable) shows no progress feedback at all and freezes the UI for the duration of the runnable. This matches the documented contract of the underlying ProgressMonitorDialog#run(boolean, boolean, IRunnableWithProgress) implementation (which ProgressManager delegates to): its javadoc explicitly states that when fork == false, "the runnable will run in the UI thread and it is the runnable's responsibility to call Display.readAndDispatch() to ensure UI responsiveness." The class-level javadoc reinforces this: "not forking the process will result in it running in the UI which may starve the UI... It is recommended that fork is set to true in most cases." Note that IRunnableContext#run's own javadoc (the interface) only says the current thread is used when fork is false — it does not itself mention event pumping; the explicit responsibility is documented on the concrete ProgressMonitorDialog implementation. The view has two side-by-side buttons: "fork=false (naive — will freeze)", which freezes the UI heartbeat, and "fork=false (pumping events — correct)", which keeps the UI responsive by pumping events inside the runnable — showing that the "freeze" is a caller responsibility, not something the framework can fix on the callee side.

Community

  • I understand reporting an issue to this OSS project does not mandate anyone to fix it. Other contributors may consider the issue, or not, at their own convenience. The most efficient way to get it fixed is that I fix it myself and contribute it back as a good quality patch to the project.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions