IntelliJ Platform Plugin SDK Help

Background Processes

Background process is a time-consuming computation usually executed on a background thread. The IntelliJ Platform executes background processes widely and provides two main ways to run them by plugins:

Progress API

The Progress API allows running processes on BGT with a modal (dialog), non-modal (visible in the status bar), or invisible progress. It also allows for process cancellation and progress tracking (as a fraction of work done or textual).

The key classes are:

  • ProgressManager – provides methods to execute and manage background processes

  • ProgressIndicator – an object associated with a running process. It allows cancelling the process and optionally tracking its progress. The current thread's indicator can be retrieved any time via ProgressManager.getProgressIndicator().

    There are many ProgressIndicator implementations and the most commonly used are:

    • EmptyProgressIndicator – invisible (ignores text/fraction-related methods), used only for cancellation tracking. Remembers its creation modality state.

    • ProgressIndicatorBase – invisible but can be made visible by subclassing. Stores text/fraction and allows retrieving them and possibly show in the UI. Non-modal by default.

    • ProgressWindow – visible progress, either modal or background. Usually not created directly but instantiated internally inside ProgressManager.run methods.

    • ProgressWrapper – wraps an existing progress indicator, usually to fork another thread with the same cancellation policy. Use SensitiveProgressWrapper to allow that separate thread's indicator to be canceled independently of the main thread.

  • Task - encapsulates an operation to perform. See Task's inner subclasses for backgroundable, modal and other base task classes.

Starting

Background processes encapsulated within Task should be run with queueing them. Example:

object : Task.Backgroundable(project, "Synchronizing data", true) { override fun run(indicator: ProgressIndicator) { // operation } } .setCancelText("Stop loading") .queue()
new Task.Backgroundable(project, "Synchronizing data", true) { public void run(ProgressIndicator indicator) { // operation } } .setCancelText("Stop loading") .queue();

ProgressManager also allows running runnables and computables not wrapped within Task with several run*() methods. Example:

ProgressManager.getInstance().runProcessWithProgressSynchronously( ThrowableComputable { // operation }, "Synchronizing data", true, project )
ProgressManager.getInstance().runProcessWithProgressSynchronously( () -> { // operation }, "Synchronizing data", true, project );

Cancellation

The most important feature of Progress API is the ability to cancel a process if the result of the computation gets irrelevant. Cancellation can be performed either by a user (pressing a cancel button) or from code when the current operation becomes obsolete due to some changes in the project. Examples:

  • Cancelling the search for symbol usages (cancellation by user):

    1. The user triggers the Find Usages action in a large project.

    2. Results are being calculated and gradually presented to the user.

    3. The user sees the place they were interested in or realizes that they don't need these results anymore.

    4. The user clicks the cancel button in the status bar, and the operation is canceled.

  • Code completion (cancellation from code):

    1. The user types a letter in the editor.

    2. Computation of results for code completion is started.

    3. User types another letter.

    4. The computation started in 2. is now outdated and is canceled to start computation for the new input.

Being prepared for cancellation requests in plugin code is crucial for saving CPU resources and responsiveness of the IDE.

Requesting Cancellation

The process can be marked as canceled by calling ProgressIndicator.cancel(). This method is called by the infrastructure that started the process, for example, when the mentioned cancel button is clicked, or by code responsible for invoking code completion.

The cancel() method marks the process as canceled, and it is up to the running operation to actually cancel itself. See the section below for handling cancellation.

Handling Cancellation

The cancellation is handled in the running process code by calling ProgressIndicator.checkCanceled(), or ProgressManager.checkCanceled(), if no indicator instance is available in the current context.

If the process was marked as canceled, then the call to checkCanceled() throws an instance of a special unchecked ProcessCanceledException (PCE) and the actual cancellation happens. This exception doesn't represent any error and is only used to handle cancellation for convenience. It allows canceling processes deeply in the call stack, without the need to handle cancellation on each level.

PCE is handled by the infrastructure that started the process and must never be logged or swallowed. In case of catching it for some reason, it must be rethrown. Use inspection Plugin DevKit | Code | 'ProcessCanceledException' handled incorrectly (2023.3).

All code working with PSI or in other kinds of background processes must be prepared for PCE being thrown at any point.

The checkCanceled() should be called by the running operation often enough to guarantee the process's smooth cancellation. PSI internals have a lot of checkCanceled() calls inside. If a process does lengthy non-PSI activity, insert explicit checkCanceled() calls so that it happens frequently, for example, on each Nth loop iteration. Use inspection Plugin DevKit | Code | Cancellation check in loops (2023.1).

Tracking Progress

Displaying progress to the user is achieved with ProgressIndicator or ProgressManager, if no indicator instance is available in the current context.

To report progress, use the following methods:

  • setText(String) – sets text above the progress bar

  • setText2(String) – sets text under the progress bar

  • setFraction(double) – sets the progress fraction: a number between 0.0 (nothing) and 1.0 (all) reflecting the ratio of work that has already been done. Only works for determinate indicator. The fraction should provide the user with a rough estimation of the time left. If this is impossible, consider making the progress indeterminate.

  • setIndeterminate(boolean) – marks the progress indeterminate (for processes that can't estimate the amount of work to be done) or determinate (for processes that can display the fraction of the work done using setFraction(double)).

ProcessCanceledException and Debugging

Sometimes, a PCE is thrown from checkCanceled() in the code inspected by a plugin developer during a debugging session. If the developer tries to step over a line and this line throws PCE (potentially from a deep call frame), the next place where the debugger stops is a catch/finally block intercepting the exception. This greatly breaks the developer's workflow as the analysis must be started over. This situation can be avoided by enabling an action available in the internal mode:

Tools | Internal Actions | Skip Window Deactivation Events

Action disabling window deactivation events. This helps avoid PCEs thrown as a result of deactivating the IDE development instance window. For example, when the IDE window is deactivated, it closes the completion popup, which, in turn, cancels the completion process.

Tools | Internal Actions | Disable ProcessCanceledException

Action disabling throwing ProcessCanceledException.

Last modified: 30 July 2024