mirror of
https://github.com/JetBrains/intellij-sdk-code-samples.git
synced 2025-07-27 16:57:49 +08:00
Threading Model (revamped General Threading Rules) and Background Processes (#1359)
This commit is contained in:
parent
cdfe1f0002
commit
c20bf65681
4
ijs.tree
4
ijs.tree
@ -65,8 +65,8 @@
|
||||
<toc-element topic="fundamentals.md" accepts-web-file-names="reference_guide.html,architectural_overview.html">
|
||||
<toc-element toc-title="Component Model"/>
|
||||
<toc-element topic="disposers.md"/>
|
||||
<toc-element topic="general_threading_rules.md" toc-title="Threading">
|
||||
<toc-element toc-title="Background Tasks"/>
|
||||
<toc-element topic="general_threading_rules.md">
|
||||
<toc-element topic="background_processes.md"/>
|
||||
</toc-element>
|
||||
<toc-element topic="kotlin_coroutines.md">
|
||||
<toc-element topic="coroutine_scopes.md"/>
|
||||
|
4
images/icons/green_checkmark.svg
Normal file
4
images/icons/green_checkmark.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<!-- Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<polygon fill="#59A869" points="13.789 2.09 15.535 3.837 6.292 13.08 1.95 8.738 3.698 6.99 6.293 9.585"/>
|
||||
</svg>
|
After Width: | Height: | Size: 326 B |
4
images/icons/green_checkmark_dark.svg
Normal file
4
images/icons/green_checkmark_dark.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<!-- Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<polygon fill="#499C54" points="13.789 2.09 15.535 3.837 6.292 13.08 1.95 8.738 3.698 6.99 6.293 9.585"/>
|
||||
</svg>
|
After Width: | Height: | Size: 326 B |
4
images/icons/red_cross.svg
Normal file
4
images/icons/red_cross.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<!-- Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<path d="M8,9.414,4.207,13.207,2.793,11.793,6.586,8,2.793,4.207,4.207,2.793,8,6.586l3.793-3.793,1.414,1.414L9.414,8l3.793,3.793-1.414,1.414Z" fill="#db5860" fill-rule="evenodd"/>
|
||||
</svg>
|
After Width: | Height: | Size: 379 B |
4
images/icons/red_cross_dark.svg
Normal file
4
images/icons/red_cross_dark.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<!-- Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<path d="M8,9.414,4.207,13.207,2.793,11.793,6.586,8,2.793,4.207,4.207,2.793,8,6.586l3.793-3.793,1.414,1.414L9.414,8l3.793,3.793-1.414,1.414Z" fill="#c75450" fill-rule="evenodd" />
|
||||
</svg>
|
After Width: | Height: | Size: 380 B |
@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<!DOCTYPE labels SYSTEM "https://resources.jetbrains.com/writerside/1.0/labels-list.dtd">
|
||||
<labels xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://resources.jetbrains.com/writerside/1.0/labels.xsd">
|
||||
@ -75,4 +76,5 @@
|
||||
<secondary-label id="unavailable" name="Unavailable">
|
||||
This item is marked as unavailable.
|
||||
</secondary-label>
|
||||
<primary-label id="obsolete-2024.1" name="Obsolete Since 2024.1" short-name="Obsolete" color="red"/>
|
||||
</labels>
|
||||
|
196
topics/basics/architectural_overview/background_processes.md
Normal file
196
topics/basics/architectural_overview/background_processes.md
Normal file
@ -0,0 +1,196 @@
|
||||
<!-- Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
|
||||
# Background Processes
|
||||
|
||||
<link-summary>Background process is a computation executed on a background thread with the possibility of interrupting it and tracking its progress.</link-summary>
|
||||
|
||||
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](#progress-api) that allows for cancelling tasks and tracking their progress
|
||||
- [`Application.executeOnPooledThread()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/Application.java) methods for running simple background tasks that don't need cancellation or progress tracking
|
||||
|
||||
## Progress API
|
||||
<primary-label ref="obsolete-2024.1"/>
|
||||
|
||||
> Plugins targeting 2024.1+ should use [Kotlin coroutines](kotlin_coroutines.md), which is a more performant solution and provides the cancellation mechanism out of the box.
|
||||
>
|
||||
> See [](coroutine_execution_contexts.md) for coroutine-based APIs to use in different contexts.
|
||||
>
|
||||
{style="warning"}
|
||||
|
||||
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`](%gh-ic%/platform/core-api/src/com/intellij/openapi/progress/ProgressManager.java) – provides methods to execute and manage background processes
|
||||
- [`ProgressIndicator`](%gh-ic%/platform/core-api/src/com/intellij/openapi/progress/ProgressIndicator.java) – 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`](%gh-ic%/platform/core-api/src/com/intellij/openapi/progress/EmptyProgressIndicator.java) – invisible (ignores text/fraction-related methods), used only for cancellation tracking.
|
||||
Remembers its creation [modality state](general_threading_rules.md#invoking-operations-on-edt-and-modality).
|
||||
- [`ProgressIndicatorBase`](%gh-ic%/platform/analysis-impl/src/com/intellij/openapi/progress/util/ProgressIndicatorBase.java) – 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`](%gh-ic%/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressWindow.java) – visible progress, either modal or background.
|
||||
Usually not created directly but instantiated internally inside `ProgressManager.run` methods.
|
||||
- [`ProgressWrapper`](%gh-ic%/platform/core-impl/src/com/intellij/openapi/progress/util/ProgressWrapper.java) – wraps an existing progress indicator, usually to fork another thread with the same cancellation policy.
|
||||
Use [`SensitiveProgressWrapper`](%gh-ic%/platform/core-impl/src/com/intellij/concurrency/SensitiveProgressWrapper.java) to allow that separate thread's indicator to be canceled independently of the main thread.
|
||||
|
||||
- [`Task`](%gh-ic%/platform/core-api/src/com/intellij/openapi/progress/Task.java) - 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:
|
||||
|
||||
<tabs group="languages">
|
||||
<tab title="Kotlin" group-key="kotlin">
|
||||
|
||||
```kotlin
|
||||
object : Task.Backgroundable(project, "Synchronizing data", true) {
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
// operation
|
||||
}
|
||||
}
|
||||
.setCancelText("Stop loading")
|
||||
.queue()
|
||||
```
|
||||
</tab>
|
||||
<tab title="Java" group-key="java">
|
||||
|
||||
```java
|
||||
new Task.Backgroundable(project, "Synchronizing data", true) {
|
||||
public void run(ProgressIndicator indicator) {
|
||||
// operation
|
||||
}
|
||||
}
|
||||
.setCancelText("Stop loading")
|
||||
.queue();
|
||||
```
|
||||
</tab>
|
||||
</tabs>
|
||||
|
||||
`ProgressManager` also allows running runnables and computables not wrapped within `Task` with several `run*()` methods.
|
||||
Example:
|
||||
|
||||
<tabs group="languages">
|
||||
<tab title="Kotlin" group-key="kotlin">
|
||||
|
||||
```kotlin
|
||||
ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||
ThrowableComputable {
|
||||
// operation
|
||||
},
|
||||
"Synchronizing data", true, project
|
||||
)
|
||||
```
|
||||
</tab>
|
||||
<tab title="Java" group-key="java">
|
||||
|
||||
```java
|
||||
ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||
() -> {
|
||||
// operation
|
||||
},
|
||||
"Synchronizing data", true, project
|
||||
);
|
||||
```
|
||||
</tab>
|
||||
</tabs>
|
||||
|
||||
### 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 <control>Find Usages</control> 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](#requesting-cancellation), then the call to `checkCanceled()` throws an instance of a special unchecked [`ProcessCanceledException`](%gh-ic%/platform/util/base/src/com/intellij/openapi/progress/ProcessCanceledException.java) (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 <control>Plugin DevKit | Code | 'ProcessCanceledException' handled incorrectly</control> (2023.3).
|
||||
|
||||
All code working with [PSI](psi.md) 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 <control>Plugin DevKit | Code | Cancellation check in loops</control> (2023.1).
|
||||
|
||||
> Throwing PCE from `checkCanceled()` can be disabled in the [internal mode](enabling_internal.md) for development (for example, while debugging the code) by invoking:
|
||||
> - <ui-path>Tools | Internal Actions | Skip Window Deactivation Events</ui-path> (2023.2+)
|
||||
> - <ui-path>Tools | Internal Actions | Disable ProcessCanceledException</ui-path> (pre-2023.2)
|
||||
>
|
||||
{style="note" title="Disabling ProcessCanceledException"}
|
||||
|
||||
### 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)`).
|
||||
|
||||
<include from="snippets.md" element-id="missingContent"/>
|
||||
|
||||
## `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](enabling_internal.md):
|
||||
|
||||
<tabs>
|
||||
<tab title="2023.2+">
|
||||
|
||||
<ui-path>Tools | Internal Actions | Skip Window Deactivation Events</ui-path>
|
||||
|
||||
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.
|
||||
|
||||
</tab>
|
||||
|
||||
<tab title="Earlier Versions">
|
||||
|
||||
<ui-path>Tools | Internal Actions | Disable ProcessCanceledException</ui-path>
|
||||
|
||||
Action disabling throwing `ProcessCanceledException`.
|
||||
|
||||
</tab>
|
||||
</tabs>
|
@ -58,7 +58,7 @@ suspend fun readDataFromFile(): Data {
|
||||
## EDT Dispatcher
|
||||
|
||||
The [`Dispatchers.EDT`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/coroutines.kt) dispatcher is used for executing UI actions on the Swing Event Dispatch Thread.
|
||||
`Dispatchers.EDT` dispatches onto EDT within the context [modality state](general_threading_rules.md#modality-and-invokelater).
|
||||
`Dispatchers.EDT` dispatches onto EDT within the context [modality state](general_threading_rules.md#invoking-operations-on-edt-and-modality).
|
||||
|
||||
### `Dispatchers.Main` vs. `Dispatchers.EDT`
|
||||
|
||||
|
@ -95,7 +95,7 @@ Code executed via the Progress API
|
||||
([`ProgressManager`](%gh-ic%/platform/core-api/src/com/intellij/openapi/progress/ProgressManager.java),
|
||||
[`ProgressIndicator`](%gh-ic%/platform/core-api/src/com/intellij/openapi/progress/ProgressIndicator.java), etc.)
|
||||
is executed in a progress indicator context.
|
||||
See the [running background processes](general_threading_rules.md#background-processes-and-processcanceledexception) section for details.
|
||||
See the [](background_processes.md#progress-api) section for details.
|
||||
|
||||
> Executing code under progress indicator is obsolete since 2024.1.
|
||||
> It is advised to use Kotlin coroutines in new code.
|
||||
@ -108,7 +108,7 @@ See the [running background processes](general_threading_rules.md#background-pro
|
||||
### Cancellation Check
|
||||
{#progress-indicator-cancellation-check}
|
||||
|
||||
- `ProgressManager.checkCanceled()` - as described in the [](general_threading_rules.md#background-processes-and-processcanceledexception) section
|
||||
- `ProgressManager.checkCanceled()` - as described in the [](background_processes.md#cancellation) section
|
||||
|
||||
### Progress Reporting
|
||||
{#progress-indicator-progress-reporting}
|
||||
|
@ -16,7 +16,7 @@
|
||||
the Threading section:
|
||||
</p>
|
||||
<list>
|
||||
<li><a href="general_threading_rules.md#read-write-lock">Read-Write Lock</a></li>
|
||||
<li><a href="general_threading_rules.md#readers-writer-lock">Read-Write Lock</a></li>
|
||||
<li><a href="general_threading_rules.md#read-action-cancellability">Read Action Cancellability</a></li>
|
||||
</list>
|
||||
|
||||
|
@ -145,7 +145,7 @@ suspend fun doSomething() {
|
||||
|
||||
## Changing Modality State
|
||||
|
||||
Avoid [changing modality state](general_threading_rules.md#modality-and-invokelater) in the middle of a running coroutine:
|
||||
Avoid [changing modality state](general_threading_rules.md#invoking-operations-on-edt-and-modality) in the middle of a running coroutine:
|
||||
|
||||
```kotlin
|
||||
cs.launch {
|
||||
|
@ -1,42 +1,210 @@
|
||||
<!-- Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
|
||||
# General Threading Rules
|
||||
# Threading Model
|
||||
|
||||
<!-- short link: https://jb.gg/ij-platform-threading -->
|
||||
|
||||
<link-summary>Threading rules for reading and writing to IntelliJ Platform data models, running and canceling background processes, and avoiding UI freezes.</link-summary>
|
||||
|
||||
In the IntelliJ Platform, code is executed on one of two thread types:
|
||||
- [Event Dispatch Thread](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html) (EDT) – also known as the UI thread. It is used for updating the UI and performing changes in the IDE data model. Operations performed on EDT must be fast.
|
||||
- background threads – used for performing costly operations.
|
||||
> It is highly recommended that readers unfamiliar with Java threads go through the official [Java Concurrency](https://docs.oracle.com/javase/tutorial/essential/concurrency/index.html) tutorial before reading this section.
|
||||
|
||||
## Read-Write Lock
|
||||
The IntelliJ Platform is a highly concurrent environment.
|
||||
Code is executed in many threads simultaneously.
|
||||
In general, as in a regular [Swing](https://docs.oracle.com/javase%2Ftutorial%2Fuiswing%2F%2F/index.html) application, threads can be categorized into two main groups:
|
||||
- [Event Dispatch Thread](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html) (EDT) – also known as the UI thread.
|
||||
Its main purpose is handling UI events (such as reacting to clicking a button or updating the UI), but the platform uses it also for writing data.
|
||||
EDT executes events taken from the Event Queue.
|
||||
Operations performed on EDT must be as fast as possible to not block other events in the queue and freeze the UI.
|
||||
There is only one EDT in the running application.
|
||||
- background threads (BGT) – used for performing long-running and costly operations, or background tasks
|
||||
|
||||
> [Thread Access Info](https://plugins.jetbrains.com/plugin/16815-thread-access-info) plugin visualizes Read/Write Access and Thread information in the debugger.
|
||||
It is possible to switch between BGT and EDT in both directions.
|
||||
Operations can be scheduled to execute on EDT from BGT (and EDT) with `invokeLater()` methods (see the rest of this page for details).
|
||||
Executing on BGT from EDT can be achieved with [background processes](background_processes.md).
|
||||
|
||||
In general, code-related data structures in the IntelliJ Platform are covered by a single [readers-writer (RW) lock](https://w.wiki/7dBy).
|
||||
|
||||
Access to the model must be performed in a read or write action for the following subsystems:
|
||||
|
||||
- [](psi.md)
|
||||
- [](virtual_file_system.md) (VFS)
|
||||
- [Project root model](project_structure.md).
|
||||
|
||||
> Threading model has changed in 2023.3, make sure to select the correct version in the tabs below.
|
||||
> Plugins targeting versions 2024.1+ should use [coroutine dispatchers](coroutine_dispatchers.md) for switching between threads.
|
||||
>
|
||||
{title="2023.3 Threading Model Changes" style="warning"}
|
||||
{style="warning"}
|
||||
|
||||
### Read Access
|
||||
## Readers-Writer Lock
|
||||
|
||||
The IntelliJ Platform data structures (such as [Program Structure Interface](psi.md), [Virtual File System](virtual_file_system.md), or [Project root model](project_structure.md)) aren't thread-safe.
|
||||
Accessing them requires a synchronization mechanism ensuring that all threads see the data in a consistent and up-to-date state.
|
||||
This is implemented with a single application-wide [readers-writer (RW) lock](https://w.wiki/7dBy) that must be acquired by threads requiring reading or writing to data models.
|
||||
|
||||
If a thread requires accessing a data model, it must acquire one of the locks:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="33%">Read Lock</td>
|
||||
<td width="33%">Write Intent Lock</td>
|
||||
<td width="33%">Write Lock</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Allows a thread for reading data.</td>
|
||||
<td>Allows a thread for reading data and potentially upgrade to the write lock.</td>
|
||||
<td>Allows a thread for reading and writing data.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Can be acquired from any thread concurrently with other read locks and write intent lock.</td>
|
||||
<td>Can be acquired from any thread concurrently with read locks.</td>
|
||||
<td>Can be acquired only from EDT concurrently with a write intent lock acquired on EDT.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Can't be acquired if write lock is held on another thread.</td>
|
||||
<td>Can't be acquired if another write intent lock or write lock is held on another thread.</td>
|
||||
<td>Can't be acquired if any other lock is held on another thread.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
The following table shows compatibility between locks in a simplified form:
|
||||
|
||||
<table style="both">
|
||||
<tr>
|
||||
<td width="25%"></td>
|
||||
<td width="25%">Read Lock</td>
|
||||
<td width="25%">Write Intent Lock</td>
|
||||
<td width="25%">Write Lock</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Read Lock</td>
|
||||
<td><img src="green_checkmark.svg" alt="+"/></td>
|
||||
<td><img src="green_checkmark.svg" alt="+"/></td>
|
||||
<td><img src="red_cross.svg" alt="-"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Write Intent Lock</td>
|
||||
<td><img src="green_checkmark.svg" alt="+"/></td>
|
||||
<td><img src="red_cross.svg" alt="-"/></td>
|
||||
<td><img src="red_cross.svg" alt="-"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Write Lock</td>
|
||||
<td><img src="red_cross.svg" alt="-"/></td>
|
||||
<td><img src="red_cross.svg" alt="-"/></td>
|
||||
<td><img src="red_cross.svg" alt="-"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
The described lock characteristics conclude the following:
|
||||
- multiple threads can read data at the same time
|
||||
- once a thread acquires the write lock, no other threads can read or write data
|
||||
|
||||
Acquiring and releasing locks explicitly in code would be verbose and error-prone and must never be done by plugins.
|
||||
The IntelliJ Platform enables write intent lock implicitly on EDT (see [](#locks-and-edt) for details) and provides an [API for accessing data under read or write locks](#accessing-data).
|
||||
|
||||
### Locks and EDT
|
||||
|
||||
Although acquiring all types of locks can be, in theory, done from any threads, the platform implicitly acquires write intent lock and allows acquiring the write lock only on EDT.
|
||||
It means that **writing data can be done only on EDT**.
|
||||
|
||||
> It is known that writing data only on EDT has negative consequences of potentially freezing the UI.
|
||||
> There is an in-progress effort to [allow writing data from any thread](https://youtrack.jetbrains.com/issue/IJPL-53).
|
||||
> See the [historical reason](#why-write-actions-are-currently-allowed-only-on-edt) for this behavior in the current platform versions.
|
||||
|
||||
The scope of implicitly acquiring the write intent lock on EDT differs depending on the platform version:
|
||||
|
||||
<tabs group="threading">
|
||||
|
||||
<tab title="2023.3 and later" group-key="newThreading">
|
||||
<tab title="2023.3+" group-key="newThreading">
|
||||
|
||||
Write intent lock is acquired automatically when operation is invoked on EDT with [`Application.invokeLater()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/Application.java).
|
||||
|
||||
</tab>
|
||||
|
||||
<tab title="Earlier versions" group-key="oldThreading">
|
||||
|
||||
Write intent lock is acquired automatically when operation is invoked on EDT with methods such as:
|
||||
- [`Application.invokeLater()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/Application.java),
|
||||
- [`SwingUtilities.invokeLater()`](https://docs.oracle.com/javase/8/docs/api/javax/swing/SwingUtilities.html#invokeLater-java.lang.Runnable-),
|
||||
- [`UIUtil.invokeAndWaitIfNeeded()`](%gh-ic%/platform/util/ui/src/com/intellij/util/ui/UIUtil.java),
|
||||
- [`EdtInvocationManager.invokeLaterIfNeeded()`](%gh-ic%/platform/util/src/com/intellij/util/ui/EdtInvocationManager.java),
|
||||
- and other similar methods
|
||||
|
||||
It is recommended to use `Application.invokeLater()` if the operation is supposed to write data.
|
||||
Use other methods for pure UI operations.
|
||||
|
||||
</tab>
|
||||
|
||||
</tabs>
|
||||
|
||||
## Accessing Data
|
||||
|
||||
The IntelliJ Platform provides a simple API for accessing data under read or write locks in a form of read and write actions.
|
||||
|
||||
Read and write actions allow executing a piece of code under a lock, automatically acquiring it before an action starts, and releasing it after the action is finished.
|
||||
|
||||
> Always try to wrap only the required operations into read/write actions, minimizing the time of holding locks.
|
||||
> If the read operation itself is long, consider using one of [read action cancellability techniques](#read-action-cancellability) to avoid blocking the write lock and EDT.
|
||||
>
|
||||
{style="warning" title="Minimize Locking Scopes"}
|
||||
|
||||
### Read Actions
|
||||
|
||||
#### API
|
||||
{#read-actions-api}
|
||||
|
||||
- [`Application.runReadAction()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/Application.java):
|
||||
<tabs group="languages">
|
||||
<tab title="Kotlin" group-key="kotlin">
|
||||
|
||||
```kotlin
|
||||
val psiFile = ApplicationManager.application.runReadAction {
|
||||
// read and return PsiFile
|
||||
}
|
||||
```
|
||||
</tab>
|
||||
<tab title="Java" group-key="java">
|
||||
|
||||
```java
|
||||
PsiFile psiFile = ApplicationManager.getApplication()
|
||||
.runReadAction((Computable<PsiFile>)() -> {
|
||||
// read and return PsiFile
|
||||
});
|
||||
```
|
||||
</tab>
|
||||
</tabs>
|
||||
|
||||
- [`ReadAction`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/ReadAction.java) `run()` or `compute()`:
|
||||
<tabs group="languages">
|
||||
<tab title="Kotlin" group-key="kotlin">
|
||||
|
||||
```kotlin
|
||||
val psiFile = ReadAction.compute<PsiFile, Throwable> {
|
||||
// read and return PsiFile
|
||||
}
|
||||
```
|
||||
</tab>
|
||||
<tab title="Java" group-key="java">
|
||||
|
||||
```java
|
||||
PsiFile psiFile = ReadAction.compute(() -> {
|
||||
// read and return PsiFile
|
||||
});
|
||||
```
|
||||
</tab>
|
||||
</tabs>
|
||||
|
||||
- Kotlin [`runReadAction()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/actions.kt):
|
||||
```kotlin
|
||||
val psiFile = runReadAction {
|
||||
// read and return PsiFile
|
||||
}
|
||||
```
|
||||
Note that this API is obsolete since 2024.1.
|
||||
Plugins implemented in Kotlin and targeting versions 2024.1+ should use suspending [`readAction()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/coroutines.kt).
|
||||
See also [](coroutine_read_actions.topic).
|
||||
|
||||
#### Rules
|
||||
{#read-actions-rules}
|
||||
|
||||
<tabs group="threading">
|
||||
|
||||
<tab title="2023.3+" group-key="newThreading">
|
||||
|
||||
Reading data is allowed from any thread.
|
||||
|
||||
Read operations need to be wrapped in a read action (RA) if not invoked via `Application.invokeLater()`.
|
||||
|
||||
If invoked from a background thread or from EDT but via `SwingUtilities.invokeLater()`, it must be explicitly wrapped in a read action (RA).
|
||||
Reading data on EDT invoked with `Application.invokeLater()` doesn't require an explicit read action, as the write intent lock allowing to read data is [acquired implicitly](#locks-and-edt).
|
||||
|
||||
</tab>
|
||||
|
||||
@ -44,150 +212,340 @@ If invoked from a background thread or from EDT but via `SwingUtilities.invokeLa
|
||||
|
||||
Reading data is allowed from any thread.
|
||||
|
||||
Reading data from EDT doesn't require any special effort.
|
||||
|
||||
However, read operations performed from any other thread must be wrapped in a read action (RA).
|
||||
Reading data on EDT doesn't require an explicit read action, as the write intent lock allowing to read data is [acquired implicitly](#locks-and-edt).
|
||||
|
||||
</tab>
|
||||
|
||||
</tabs>
|
||||
|
||||
The corresponding objects aren't guaranteed to survive between several consecutive read actions.
|
||||
As a rule of thumb, whenever starting a read action, check if the PSI/VFS/project/module is still valid.
|
||||
In all other cases, it is required to wrap read operation in a read action with one of the [API](#read-actions-api) methods.
|
||||
|
||||
#### Read Action (RA) API
|
||||
##### Objects Validity
|
||||
|
||||
- [`Application.runReadAction()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/Application.java)
|
||||
- [`ReadAction`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/ReadAction.java) `run()` or `compute()`
|
||||
The read objects aren't guaranteed to survive between several consecutive read actions.
|
||||
Whenever starting a read action, check if the PSI/VFS/project/module is still valid.
|
||||
Example:
|
||||
```kotlin
|
||||
val virtualFile = runReadAction { // read action 1
|
||||
// read a virtual file
|
||||
}
|
||||
// do other time-consuming work...
|
||||
val psiFile = runReadAction { // read action 2
|
||||
if (virtualFile.isValid()) { // check if the virtual file is valid
|
||||
PsiManager.getInstance(project).findFile(virtualFile)
|
||||
} else null
|
||||
}
|
||||
```
|
||||
|
||||
### Write Access
|
||||
Between executing first and second read actions, another thread could invalidate the virtual file:
|
||||
|
||||
Writing data is only allowed from EDT, and write operations always need to be wrapped in a write action (WA).
|
||||
```mermaid
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
dateFormat X
|
||||
%% do not remove trailing space in axisFormat:
|
||||
axisFormat
|
||||
section Thread 1
|
||||
read action 1 : 0, 1
|
||||
time-consuming work : done, 1, 4
|
||||
read action 2 : 4, 5
|
||||
section Thread 2
|
||||
delete virtual file : crit, 2, 3
|
||||
```
|
||||
|
||||
Modifying the model is only allowed from write-safe contexts, including user actions and `SwingUtilities.invokeLater()` calls from them (see [](#modality-and-invokelater)).
|
||||
### Write Actions
|
||||
|
||||
You may not modify PSI, VFS, or project model from inside UI renderers or `SwingUtilities.invokeLater()` calls.
|
||||
#### API
|
||||
{#write-actions-api}
|
||||
|
||||
#### Write Action (WA) API
|
||||
- [`Application.runWriteAction()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/Application.java):
|
||||
<tabs group="languages">
|
||||
<tab title="Kotlin" group-key="kotlin">
|
||||
|
||||
- [`Application.runWriteAction()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/Application.java)
|
||||
- [`WriteAction`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/WriteAction.java) `run()` or `compute()`
|
||||
```kotlin
|
||||
ApplicationManager.application.runWriteAction {
|
||||
// write data
|
||||
}
|
||||
```
|
||||
</tab>
|
||||
<tab title="Java" group-key="java">
|
||||
|
||||
## Modality and `invokeLater()`
|
||||
```java
|
||||
ApplicationManager.getApplication().runWriteAction(() -> {
|
||||
// write data
|
||||
});
|
||||
```
|
||||
</tab>
|
||||
</tabs>
|
||||
|
||||
To pass control from a background thread to EDT, instead of the standard `SwingUtilities.invokeLater()`, plugins should use `ApplicationManager.getApplication().invokeLater()`.
|
||||
The latter API allows specifying the _modality state_ ([`ModalityState`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/ModalityState.java)) for the call, that is, the stack of modal dialogs under which the call is allowed to execute:
|
||||
- [`WriteAction`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/WriteAction.java) `run()` or `compute()`:
|
||||
<tabs group="languages">
|
||||
<tab title="Kotlin" group-key="kotlin">
|
||||
|
||||
### `ModalityState.nonModal()`/`NON_MODAL`
|
||||
```kotlin
|
||||
WriteAction.run<Throwable> {
|
||||
// write data
|
||||
}
|
||||
```
|
||||
</tab>
|
||||
<tab title="Java" group-key="java">
|
||||
|
||||
The operation will be executed after all modal dialogs are closed.
|
||||
If any of the open (unrelated) projects displays a per-project modal dialog, the action will be performed after the dialog is closed.
|
||||
```java
|
||||
WriteAction.run(() -> {
|
||||
// write data
|
||||
});
|
||||
```
|
||||
</tab>
|
||||
</tabs>
|
||||
|
||||
### `ModalityState.stateForComponent()`
|
||||
- Kotlin [`runWriteAction()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/actions.kt):
|
||||
```kotlin
|
||||
runWriteAction {
|
||||
// write data
|
||||
}
|
||||
```
|
||||
Note that this API is obsolete since 2024.1.
|
||||
Plugins implemented in Kotlin and targeting versions 2024.1+ should use suspending [`writeAction()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/coroutines.kt).
|
||||
|
||||
The operation can be executed when the topmost shown dialog is the one that contains the specified component or is one of its parent dialogs.
|
||||
#### Rules
|
||||
{#write-actions-rules}
|
||||
|
||||
### None Specified
|
||||
<tabs group="threading">
|
||||
|
||||
`ModalityState.defaultModalityState()` will be used.
|
||||
This is the optimal choice in most cases that uses the current modality state when invoked from EDT.
|
||||
It has special handling for background processes started with `ProgressManager`: `invokeLater()` from such a process may run in the same dialog that the process started.
|
||||
<tab title="2023.3+" group-key="newThreading">
|
||||
|
||||
### `ModalityState.any()`
|
||||
Writing data is only allowed on EDT invoked with `Application.invokeLater()`.
|
||||
|
||||
The operation will be executed as soon as possible regardless of modal dialogs.
|
||||
Note that modifying PSI, VFS, or project model is prohibited from such runnables.
|
||||
|
||||
If EDT activity needs to access a [file-based index](indexing_and_psi_stubs.md) (for example, it is doing any project-wide PSI analysis, resolves references, or performs other tasks depending on indexes), use `DumbService.smartInvokeLater()`.
|
||||
That way, it is run after all possible indexing processes have been completed.
|
||||
|
||||
## Background Processes and `ProcessCanceledException`
|
||||
|
||||
Background progresses are managed by [`ProgressManager`](%gh-ic%/platform/core-api/src/com/intellij/openapi/progress/ProgressManager.java), which has plenty of methods to execute the given code with a modal (dialog), non-modal (visible in the status bar), or invisible progress.
|
||||
In all cases, the code is executed on a background thread, which is associated with a [`ProgressIndicator`](%gh-ic%/platform/core-api/src/com/intellij/openapi/progress/ProgressIndicator.java) object.
|
||||
The current thread's indicator can be retrieved any time via `ProgressIndicatorProvider.getGlobalProgressIndicator()`.
|
||||
|
||||
For visible progresses, threads can use `ProgressIndicator` to notify the user about the current status: for example, set text or visual fraction of the work done.
|
||||
|
||||
Progress indicators also provide the means to handle cancellation of background processes, either by the user (pressing the <control>Cancel</control> button) or from code (for example, when the current operation becomes obsolete due to some changes in the project).
|
||||
The progress can be marked as canceled by calling `ProgressIndicator.cancel()`.
|
||||
The process reacts to this by calling `ProgressIndicator.checkCanceled()` (or `ProgressManager.checkCanceled()` if no indicator instance is available in the current context).
|
||||
This call throws a special unchecked [`ProcessCanceledException`](%gh-ic%/platform/util/base/src/com/intellij/openapi/progress/ProcessCanceledException.java) (PCE) if the background process has been canceled.
|
||||
|
||||
All code working with [PSI](psi.md) or in other kinds of background processes must be prepared for PCE being thrown at any point.
|
||||
This exception must never be logged but rethrown, and it will be handled in the infrastructure that started the process.
|
||||
Use inspection <control>Plugin DevKit | Code | 'ProcessCanceledException' handled incorrectly</control> (2023.3).
|
||||
|
||||
The `checkCanceled()` should be called 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 <control>Plugin DevKit | Code | Cancellation check in loops</control> (2023.1).
|
||||
|
||||
### Disabling `ProcessCanceledException`
|
||||
|
||||
Throwing PCE from `ProgressIndicator.checkCanceled()` can be disabled for development (for example, while debugging the code) by invoking:
|
||||
|
||||
<tabs>
|
||||
<tab title="2023.2 and later">
|
||||
|
||||
<ui-path>Tools | Internal Actions | Skip Window Deactivation Events</ui-path>
|
||||
Write operations must always be wrapped in a write action with one of the [API](#write-actions-api) methods.
|
||||
|
||||
</tab>
|
||||
|
||||
<tab title="Earlier Versions">
|
||||
<tab title="Earlier versions" group-key="oldThreading">
|
||||
|
||||
<ui-path>Tools | Internal Actions | Disable ProcessCanceledException</ui-path>
|
||||
Writing data is only allowed on EDT.
|
||||
|
||||
Write operations must always be wrapped in a write action with one of the [API](#write-actions-api) methods.
|
||||
|
||||
Modifying the model is only allowed from write-safe contexts, including user actions and `SwingUtilities.invokeLater()` calls from them (see [](#invoking-operations-on-edt-and-modality)).
|
||||
|
||||
Modifying PSI, VFS, or project model from inside UI renderers or `SwingUtilities.invokeLater()` calls is forbidden.
|
||||
|
||||
</tab>
|
||||
|
||||
</tabs>
|
||||
|
||||
These actions are available only if [Internal Mode is enabled](enabling_internal.md).
|
||||
> [Thread Access Info](https://plugins.jetbrains.com/plugin/16815-thread-access-info) plugin visualizes Read/Write Access and Thread information in the debugger.
|
||||
|
||||
## Invoking Operations on EDT and Modality
|
||||
|
||||
Operations that write data on EDT should be invoked with `Application.invokeLater()` because it allows specifying the _modality state_ ([`ModalityState`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/ModalityState.java)) for the scheduled operation.
|
||||
This is not supported by `SwingUtilities.invokeLater()` and similar APIs.
|
||||
|
||||
> Note that `Application.invokeLater()` must be used to write data in versions 2023.3+.
|
||||
>
|
||||
{style="warning"}
|
||||
|
||||
`ModalityState` represents the stack of active modal dialogs and is used in calls to `Application.invokeLater()` to ensure the scheduled runnable can execute within the given modality state, meaning when the same set of modal dialogs or a subset is present.
|
||||
|
||||
To better understand what problem `ModalityState` solves, consider the following scenario:
|
||||
1. A user action is started.
|
||||
2. In the meantime, another operation is scheduled on EDT with `SwingUtilities.invokeLater()` (without modality state support).
|
||||
3. The action from 1. now shows a dialog asking a <control>Yes</control>/<control>No</control> question.
|
||||
4. While the dialog is shown, the operation from 2. is now processed and does changes to the data model, which invalidates PSI.
|
||||
5. The user clicks <control>Yes</control> or <control>No</control> in the dialog, and it executes some code based on the answer.
|
||||
6. Now, the code to be executed as the result of the user's answer has to deal with the changed data model it was not prepared for. For example, it was supposed to execute changes in the PSI that might be already invalid.
|
||||
|
||||
```mermaid
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
dateFormat X
|
||||
%% do not remove trailing space in axisFormat:
|
||||
axisFormat
|
||||
section EDT
|
||||
1. Start action : 0, 2
|
||||
3. Show dialog : 2, 3
|
||||
4. Modify data : crit, active, 3, 4
|
||||
5. Answer dialog : 4, 5
|
||||
6. Work on invalid data : crit, 5, 7
|
||||
section BGT
|
||||
2. invokeLater() : crit, active, 1, 2
|
||||
```
|
||||
|
||||
Passing the modality state solves this problem:
|
||||
1. A user action is started.
|
||||
2. In the meantime, another operation is scheduled on EDT with `Application.invokeLater()` (supporting modality state).
|
||||
The operation is scheduled with `ModalityState.defaultModalityState()` (see the table below for other helper methods).
|
||||
3. The action from 1. now shows a dialog asking a <control>Yes</control>/<control>No</control> question.
|
||||
This adds a modal dialog to the modality state stack.
|
||||
4. While the dialog is shown, the scheduled operation waits as it was scheduled with a "lower" modality state than the current state with an additional dialog.
|
||||
5. The user clicks <control>Yes</control> or <control>No</control> in the dialog, and it executes some code based on the answer.
|
||||
6. The code is executed on data in the same state as before the dialog was shown.
|
||||
7. The operation from 1. is executed now without interfering with the user's action.
|
||||
|
||||
```mermaid
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
dateFormat X
|
||||
%% do not remove trailing space in axisFormat:
|
||||
axisFormat
|
||||
section EDT
|
||||
1. Start action : 0, 2
|
||||
3. Show dialog : 2, 3
|
||||
5. Answer dialog : 3, 4
|
||||
6. Work on correct data : 4, 6
|
||||
7. Modify data : active, 6, 7
|
||||
section BGT
|
||||
2. invokeLater() : active, 1, 2
|
||||
4. Wait for dialog close : done, 2, 4
|
||||
```
|
||||
|
||||
The following table presents methods providing useful modality states to be passed to `Application.invokeLater()`:
|
||||
|
||||
| [`ModalityState`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/ModalityState.java) | Description |
|
||||
|------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| <p>`defaultModalityState()`</p><p>_Used if none specified_</p> | <p>If invoked from EDT, it uses the `ModalityState.current()`.</p><p>If invoked from a background process started with `ProgressManager`, the operation can be executed in the same dialog that the process started.</p><p>**This is the optimal choice in most cases.**</p> |
|
||||
| `current()` | The operation can be executed when the modality state stack doesn't grow since the operation was scheduled. |
|
||||
| `stateForComponent()` | The operation can be executed when the topmost shown dialog is the one that contains the specified component or is one of its parent dialogs. |
|
||||
| <p>`nonModal()` or</p><p>`NON_MODAL`</p> | The operation will be executed after all modal dialogs are closed. If any of the open (unrelated) projects displays a per-project modal dialog, the operation will be performed after the dialog is closed. |
|
||||
| `any()` | The operation will be executed as soon as possible regardless of modal dialogs (the same as with `SwingUtilities.invokeLater()`). It can be used for scheduling only pure UI operations. Modifying PSI, VFS, or project model is prohibited.<p>**Don't use it unless absolutely needed.**</p> |
|
||||
|
||||
> If EDT activity needs to access a [file-based index](indexing_and_psi_stubs.md) (for example, it is doing any project-wide PSI analysis, resolves references, or performs other tasks depending on indexes), use [`DumbService.smartInvokeLater()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/project/DumbService.kt).
|
||||
> This API also supports `ModalityState` and runs the operation after all possible indexing processes have been completed.
|
||||
>
|
||||
{style="note"}
|
||||
|
||||
## Read Action Cancellability
|
||||
|
||||
Background threads shouldn't take plain read actions for a long time.
|
||||
The reason is that if EDT needs a write action (for example, the user types something), it must be acquired as soon as possible.
|
||||
Otherwise, the UI will freeze until all background threads have released their read actions.
|
||||
BGT shouldn't hold read locks for a long time.
|
||||
The reason is that if EDT needs a write action (for example, the user types something in the editor), it must be acquired as soon as possible.
|
||||
Otherwise, the UI will freeze until all BGTs have released their read actions.
|
||||
The following diagram presents this problem:
|
||||
|
||||
The best-known approach is to cancel background read actions whenever there is a write action about to occur and restart that background read action later from scratch.
|
||||
Editor highlighting, code completion, Goto Class/File/… actions all work like this.
|
||||
```mermaid
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
dateFormat X
|
||||
%% do not remove trailing space in axisFormat:
|
||||
axisFormat
|
||||
section BGT
|
||||
very long read action : 0, 4
|
||||
section EDT
|
||||
write action (waiting for the lock) : done, 1, 4
|
||||
write action (executing) : 4, 5
|
||||
UI freeze : crit, 1, 5
|
||||
```
|
||||
|
||||
To achieve that, the lengthy background operation is started with a `ProgressIndicator`, and a dedicated listener cancels that indicator when a write action is initiated.
|
||||
The next time the background thread calls `checkCanceled()`, a PCE is thrown, and the thread should stop its operation (and finish the read action) as soon as possible.
|
||||
Sometimes, it is required to run a long read action, and it isn't possible to speed it up.
|
||||
In such a case, the recommended approach is to cancel the read action whenever there is a write action about to occur and restart that read action later from scratch:
|
||||
|
||||
There are two recommended ways of doing this:
|
||||
```mermaid
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
dateFormat X
|
||||
%% do not remove trailing space in axisFormat:
|
||||
axisFormat
|
||||
section BGT
|
||||
very long read action : 0, 1
|
||||
very long read action (2nd attempt) : 2, 5
|
||||
RA canceled : milestone, crit, 0, 2
|
||||
RA restarted from scratch : milestone, 2, 2
|
||||
section EDT
|
||||
write action : 1, 2
|
||||
```
|
||||
|
||||
- If on EDT, call [`ReadAction.nonBlocking()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/ReadAction.java) which returns [`NonBlockingReadAction`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/NonBlockingReadAction.java) (NBRA)
|
||||
- If already in a background thread, use [`ProgressManager.runInReadActionWithWriteActionPriority()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/progress/ProgressManager.java) in a loop, until it passes or the whole activity becomes obsolete.
|
||||
In this case, the EDT won't be blocked and the UI freeze is avoided.
|
||||
The total execution time of the read action will be longer due to multiple attempts, but not affecting the UI responsiveness is more important.
|
||||
|
||||
In both approaches, always check at the start of each read action if the objects are still valid, and if the whole operation still makes sense (for example, not canceled by the user, the project isn't closed, and similar).
|
||||
The canceling approach is widely used in various areas of the IntelliJ Platform: editor highlighting, code completion, "go to class/file/…" actions all work like this.
|
||||
Read the [](background_processes.md) section for more details.
|
||||
|
||||
### Cancellable Read Actions API
|
||||
|
||||
> Plugins targeting versions 2024.1+ should use Write Allowing Read Actions available in the [Kotlin Coroutines Read Actions API](coroutine_read_actions.topic#coroutine-read-actions-api).
|
||||
>
|
||||
{style="warning"}
|
||||
|
||||
To run a cancellable read action, use one of the available APIs:
|
||||
|
||||
- [`ReadAction.nonBlocking()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/ReadAction.java) which returns [`NonBlockingReadAction`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/NonBlockingReadAction.java) (NBRA). NBRA handles restarting the action out-of-the-box.
|
||||
- [`ReadAction.computeCancellable()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/ReadAction.java) which computes the result immediately in the current thread or throws an exception if there is a running or requested write action.
|
||||
|
||||
In both cases, when a read action is started and a write action occurs in the meantime, the read action is marked as canceled.
|
||||
Read actions must [check for cancellation](background_processes.md#handling-cancellation) often enough to trigger actual cancellation.
|
||||
Although the cancellation mechanism may differ under the hood ([Progress API](background_processes.md#progress-api) or [Kotlin Coroutines](kotlin_coroutines.md)), the cancellation handling rules are the same in both cases.
|
||||
|
||||
Always check at the start of each read action if the [objects are still valid](#objects-validity), and if the whole operation still makes sense.
|
||||
With `ReadAction.nonBlocking()`, use `expireWith()` or `expireWhen()` for that.
|
||||
|
||||
If the activity has to access a [file-based index](indexing_and_psi_stubs.md) (for example, it is doing any project-wide PSI analysis, resolves references, or performs other tasks depending on indexes), use `ReadAction.nonBlocking(…).inSmartMode()`.
|
||||
> If NBRA needs to access a [file-based index](indexing_and_psi_stubs.md) (for example, it is doing any project-wide PSI analysis, resolves references, or performs other tasks depending on indexes), use `ReadAction.nonBlocking(…).inSmartMode()`.
|
||||
>
|
||||
{style="note"}
|
||||
|
||||
## Avoiding UI Freezes
|
||||
|
||||
#### Don't Perform Long Operations in EDT
|
||||
### Don't Perform Long Operations on EDT
|
||||
|
||||
In particular, don't traverse [](virtual_file_system.md), parse [PSI](psi.md), resolve [references](psi_references.md) or query [indexes/stubs](indexing_and_psi_stubs.md).
|
||||
In particular, don't traverse [VFS](virtual_file_system.md), parse [PSI](psi.md), resolve [references,](psi_references.md) or query [indexes](indexing_and_psi_stubs.md).
|
||||
|
||||
There are still some cases when the platform itself invokes such expensive code (for example, resolve in `AnAction.update()`), but these are being worked on.
|
||||
Meanwhile, try to speed up what you can in your plugin as it will be generally beneficial and also improve background highlighting performance.
|
||||
Meanwhile, try to speed up what you can in your plugin as it will be generally beneficial and will also improve background highlighting performance.
|
||||
|
||||
#### Action Update
|
||||
|
||||
For implementations of [`AnAction`](%gh-ic%/platform/editor-ui-api/src/com/intellij/openapi/actionSystem/AnAction.java), plugin authors should specifically
|
||||
review the documentation of `AnAction.getActionUpdateThread()` in the [](basic_action_system.md) section as it describes how threading works for actions.
|
||||
|
||||
Write actions currently have to happen on EDT.
|
||||
To speed them up, as much as possible should be moved out of the write action into a preparation step which can be then invoked in the background (for example, using `ReadAction.nonBlocking()`, see above).
|
||||
#### Minimize Write Actions Scope
|
||||
|
||||
#### Event Listeners
|
||||
Write actions currently [have to happen on EDT](#locks-and-edt).
|
||||
To speed them up, as much as possible should be moved out of the write action into a preparation step which can be then invoked in the [background](background_processes.md) or inside an [NBRA](#cancellable-read-actions-api).
|
||||
|
||||
#### Slow Operations on EDT Assertion
|
||||
|
||||
Some of the long operations are reported by [`SlowOperations.assertSlowOperationsAreAllowed()`](%gh-ic%/platform/core-api/src/com/intellij/util/SlowOperations.java).
|
||||
According to its Javadoc, they must be moved to BGT.
|
||||
This can be achieved with the techniques mentioned in the Javadoc, [background processes](background_processes.md), [`Application.executeOnPooledThread()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/application/Application.java), or [coroutines](kotlin_coroutines.md) (recommended for plugins targeting 2024.1+).
|
||||
Note that the assertion is enabled in IDE EAP versions, [internal mode](enabling_internal.md), or [development instance](ide_development_instance.md), and regular users don't see them in the IDE.
|
||||
This will change in the future, so fixing these exceptions is required.
|
||||
|
||||
### Event Listeners
|
||||
|
||||
Listeners mustn't perform any heavy operations.
|
||||
Ideally, they should only clear some caches.
|
||||
|
||||
It is also possible to schedule background processing of events.
|
||||
In such cases, be prepared that some new events might be delivered before the background processing starts – and thus the world might have changed by that moment or even in the middle of background processing.
|
||||
Consider using [`MergingUpdateQueue`](%gh-ic%/platform/ide-core/src/com/intellij/util/ui/update/MergingUpdateQueue.java) and `ReadAction.nonBlocking()` (see [](#read-action-cancellability)) to mitigate these issues.
|
||||
Consider using [`MergingUpdateQueue`](%gh-ic%/platform/ide-core/src/com/intellij/util/ui/update/MergingUpdateQueue.java) and [NBRA](#cancellable-read-actions-api) to mitigate these issues.
|
||||
|
||||
#### VFS Events
|
||||
### VFS Events
|
||||
|
||||
Massive batches of VFS events can be pre-processed in the background, see [`AsyncFileListener`](%gh-ic%/platform/core-api/src/com/intellij/openapi/vfs/AsyncFileListener.java) (2019.2 or later).
|
||||
Massive batches of VFS events can be pre-processed in the background with [`AsyncFileListener`](%gh-ic%/platform/core-api/src/com/intellij/openapi/vfs/AsyncFileListener.java).
|
||||
|
||||
## FAQ
|
||||
|
||||
### How to check whether the current thread is the EDT/UI thread?
|
||||
|
||||
Use `Application.isDispatchThread()`.
|
||||
|
||||
### Why write actions are currently allowed only on EDT?
|
||||
|
||||
Reading data model was often performed on EDT to display results in the UI.
|
||||
The IntelliJ Platform is more than 20 years old, and in its beginnings Java didn't offer features like generics and lambdas.
|
||||
Code that acquired read locks was very verbose.
|
||||
For convenience, it was decided that reading data can be done on EDT without read locks (even implicitly acquired).
|
||||
|
||||
The consequence of this was that writing had to be allowed only on EDT to avoid read/write conflicts.
|
||||
The nature of EDT provided this possibility out-of-the-box due to being a single thread.
|
||||
Event queue guaranteed that reads and writes were ordered and executed one by one and couldn't interweave.
|
||||
|
||||
<include from="snippets.md" element-id="missingContent"/>
|
||||
|
@ -282,7 +282,7 @@ Registration in <path>plugin.xml</path>:
|
||||
{style="warning" title="Correct Service Retrieval"}
|
||||
|
||||
Getting a service doesn't need a read action and can be performed from any thread.
|
||||
If a service is requested from several threads, it will be initialized in the first thread, and other threads will be blocked until it is fully initialized.
|
||||
If a service is requested from several [threads](general_threading_rules.md), it will be initialized in the first thread, and other threads will be blocked until it is fully initialized.
|
||||
|
||||
<tabs group="languages">
|
||||
<tab title="Java" group-key="java">
|
||||
|
@ -78,7 +78,7 @@ If there is some code that needs to be executed after the refresh is complete, t
|
||||
* [`RefreshQueue.createSession()`](%gh-ic%/platform/analysis-api/src/com/intellij/openapi/vfs/newvfs/RefreshQueue.java)
|
||||
* [`VirtualFile.refresh()`](%gh-ic%/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java)
|
||||
|
||||
In some cases, synchronous refreshes can cause deadlocks, depending on which locks are held by the thread invoking the refresh operation.
|
||||
In some cases, synchronous refreshes can cause deadlocks, depending on which [locks](general_threading_rules.md#readers-writer-lock) are held by the thread invoking the refresh operation.
|
||||
|
||||
## Virtual File System Events
|
||||
|
||||
|
@ -12,6 +12,13 @@ See [GitHub Changelog](https://github.com/JetBrains/intellij-sdk-docs/commits/ma
|
||||
|
||||
## 2024
|
||||
|
||||
### July
|
||||
{july-24}
|
||||
|
||||
Threading Model
|
||||
:
|
||||
Revamp the [Threading Model](general_threading_rules.md) page (formerly _General Threading Rules_) and add a new page describing [background processes](background_processes.md), including cancellation and progress tracking.
|
||||
|
||||
### June
|
||||
{june-24}
|
||||
|
||||
|
@ -102,7 +102,7 @@ myPanel.add(browser.getComponent());
|
||||
#### Loading Documents
|
||||
|
||||
To load a document in the browser, use one of [`JBCefBrowserBase.load*()`](%gh-ic%/platform/platform-api/src/com/intellij/ui/jcef/JBCefBrowserBase.java) methods.
|
||||
Methods loading documents can be called from both EDT and background threads.
|
||||
Methods loading documents can be called from both [EDT and background threads](general_threading_rules.md).
|
||||
It is possible to set an initial URL (passed to constructor or builder) that will be loaded when browser is created and initialized.
|
||||
|
||||
### Browser Client
|
||||
|
@ -66,7 +66,7 @@ Messages.showInfoMessage(roots.toString(), "Library Info");
|
||||
|
||||
### Creating a Library
|
||||
To create a library, perform the following steps:
|
||||
* Get a [write action](general_threading_rules.md#read-write-lock)
|
||||
* Get a [write action](general_threading_rules.md#write-actions)
|
||||
* Obtain the library table to which you want to add the library. Use one of the following, depending on the library level:
|
||||
* `LibraryTablesRegistrar.getInstance().getLibraryTable()`
|
||||
* `LibraryTablesRegistrar.getInstance().getLibraryTable(Project)`
|
||||
@ -80,13 +80,13 @@ You can find an example of using these APIs in the [project_model](%gh-sdk-sampl
|
||||
|
||||
### Adding Contents or Modifying a Library
|
||||
To add or change the roots of a library, you need to perform the following steps:
|
||||
* Get a [write action](general_threading_rules.md#read-write-lock)
|
||||
* Get a [write action](general_threading_rules.md#write-actions)
|
||||
* Get a **modifiable model** for the library, using `Library.getModifiableModel()`
|
||||
* Use methods such as `Library.ModifiableModel.addRoot()` to perform the necessary changes
|
||||
* Commit the model using `Library.ModifiableModel.commit()`.
|
||||
|
||||
### Adding a Library Dependency to a Module
|
||||
Use `ModuleRootModificationUtil.addDependency(Module, Library)` from under a [write action](general_threading_rules.md#read-write-lock).
|
||||
Use `ModuleRootModificationUtil.addDependency(Module, Library)` from under a [write action](general_threading_rules.md#write-actions).
|
||||
|
||||
### Checking Belonging to a Library
|
||||
The [`ProjectFileIndex`](%gh-ic%/platform/projectModel-api/src/com/intellij/openapi/roots/ProjectFileIndex.java) interface implements a number of methods you can use to check whether the specified file belongs to the project library classes or library sources.
|
||||
|
@ -109,7 +109,7 @@ See [SDK](sdk.md) for more details.
|
||||
|
||||
Utility classes used for modifying the project structure can be found in the package [`projectModel-impl.openapi`](%gh-ic%/platform/projectModel-impl/src/com/intellij/openapi).
|
||||
Its [`roots`](%gh-ic%/platform/projectModel-impl/src/com/intellij/openapi/roots) subpackage contains instances and utilities intended for work with project and module source roots, including [`ModuleRootModificationUtil`](%gh-ic%/platform/projectModel-api/src/com/intellij/openapi/roots/ModuleRootModificationUtil.java) and [`ProjectRootUtil`](%gh-ic%/platform/projectModel-impl/src/com/intellij/openapi/projectRoots/impl/ProjectRootUtil.java).
|
||||
Project structure changes need to be performed in [write action](general_threading_rules.md#read-write-lock).
|
||||
Project structure changes need to be performed in [write action](general_threading_rules.md#write-actions).
|
||||
|
||||
Refer to the [project_model](%gh-sdk-samples-master%/project_model/src/main/java/org/intellij/sdk/project/model/ModificationAction.java) code sample to learn how project structure modification can be implemented.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user