Back to JetBrains

UI Freezes in JetBrains IDE Plugins and How to Avoid Them

JetBrainsMarch 20, 202653m
Topics44
Introduction to UI Freezes in JetBrains IDE Plugins0:10Understanding the Event Dispatch Thread3:31Background Thread Solutions for Simple Cases5:32Cryptic Freeze Reports Beyond EDT6:30Package Checker Plugin Freeze Example6:30IntelliJ Read-Write Lock Concurrency Model8:01Long Read Actions Blocking Write Access10:32Thread Dump Analysis Tools11:30AI-Powered Freeze Analysis13:31Run Read Action Usage15:00Plugin-Related Freezes16:02Thread Dump Collection and Reporting18:01Plugin Slowdown Notifications19:00Additional Freeze Causes20:32Service Initialization Freezes21:31Non-Cancellable Long Read Actions22:00Read-Write Lock Blocking Visualization24:02Simplified Problem Code24:30Non-Blocking Read Action Solution25:32Handling Long Computations27:00Proper Cancellation with ProgressManager28:01ProcessCanceledException29:32Coroutine Read Action API30:02Key Principle: Background Does Not Equal Safe30:32Critical Takeaway31:30AI Assistance for Platform Development31:30Reaching Out to Plugin Vendors for Freeze Issues33:10Build Script Assistance from JetBrains Team33:31Tools and Experience for Freeze Analysis34:00Claude Skill for Analyzing Thread Dumps34:30Using the Claude Skill with Thread Dumps35:30Sharing the Claude Skill37:00Analyzing Thread Dumps Independently37:32Sharing Specialized Skills38:01Learning from Skill Files38:32Support Resources for Plugin Developers39:31Handling State Changes Between Read Actions41:00Eventually Consistent Approaches44:32Bridging Blocking and Suspending Code45:30Compute Detached as Last Resort47:01Automatic Notifications for Freeze Reports48:01Restart Behavior for Non-Blocking Read Actions49:32Limited Offer for Freeze Investigation51:00Available Assistance Offers52:02
In a Nutshell

UI freezes in JetBrains IDEs occur when long-running read actions or EDT operations block the write lock needed for UI updates. The fix requires using non-blocking read actions (`readAction.nonBlocking()`), splitting work into small cancellable chunks with `ProgressManager.checkCanceled()` checks, and ensuring background threads don't hold read locks during expensive computations. A long non-cancellable read action freezes the entire IDE.

AI-Generated Notes

These notes were generated by AI and may contain inaccuracies.

Patrick introduces Yuri from the platform team to discuss UI freezes in the JetBrains platform, particularly in plugins. The discussion covers freezes that occur even on high-end hardware like beefy MacBooks or PCs, where the UI becomes unresponsive to mouse and keyboard events for brief but annoying periods.

The core issue is the event dispatch thread (UI thread) becoming frozen while performing expensive operations. A common example shown in their internal system called Den demonstrates networking code being executed directly on the dispatch thread, which blocks event processing entirely.

Please never ever ever ever go internet from dispatch thread

The real problem is not that plugins attempt network operations, but that they execute them immediately on the UI thread, blocking everything. The UI thread handles all events and the event queue, so when it performs expensive work like network calls, it cannot dispatch events, repaint, or respond to user input.

The straightforward solution for dispatch thread issues is moving such code to background threads using threads, coroutines, callbacks, futures, or asynchronous frameworks. Many developers already use background threads for project analysis, code loading, reading, and processing.

Newer freeze reports present cryptic stack traces that are difficult to understand and are not obviously related to the event dispatch thread. These freezes don't show the cause directly, requiring deeper analysis.

Yuri presents a real example from their Package Checker plugin. The dispatch thread shows locks, waiting, LockSupport, and Unsafe.park operations. Unsafe.park is JVM internal machinery for waiting on synchronization objects.

IntelliJ IDEA's concurrency model is built on a single read-write lock. Multiple threads can read in parallel, but only one thread can write at a time. Writers have exclusive access. This ensures consistency and visibility of changes across readers once a write operation completes.

When readers perform expensive work like long file loops, writers (including the dispatch thread) cannot acquire the write lock in a timely manner. The dispatch thread waits for the read lock to be released.

IntelliJ IDEA provides built-in tools for analyzing thread dumps. Using "Analyze Stack Trace" or "Analyze Thread Dump" from the menu or Shift+Shift shortcut reveals threads grouped by similar traces. The Event Queue thread appears waiting on condition for the write lock.

The platform team implemented an additional analysis tab that provides a verdict explaining what went wrong. In the example, it identifies "long read action" as the cause, showing the specific class holding the read lock during extended computation.

The key line identified is runReadAction. This is commonly used in plugins to access the IntelliJ model, files, and indexes from background threads. The computation holds the read lock for its entire duration without yielding.

UI freezes can originate from third-party plugins, not just the platform itself. All plugins run in the same process as a monolithic application. Users on Reddit have reported freezes traced back to third-party plugins rather than JetBrains code.

Thread dumps are automatically created when freezes occur and stored in the logs directory. Support teams request these dumps for analysis. IntelliJ can involve AI to analyze dumps and identify problematic plugins.

When the system identifies a plugin causing freezes, it displays a yellow banner notification naming the specific plugin (e.g., "Python plugin might be slowing things down"). Users can report the issue with all details or mute notifications for the current major version.

Beyond EDT and read action issues, freezes can result from:

  • Using I/O under read action
  • Heavy service initialization
  • Client code waiting for singleton service instances

Service initialization occurs outside read actions during code loading. If initialization is lengthy or complex, client code waiting for the singleton instance becomes stuck.

The problematic read action is both long-running and non-cancellable. When users click buttons expecting UI responses, actions requiring the write lock cannot proceed because the read action will not yield to pending write requests.

The dispatch thread attempts to acquire the write lock while background threads hold the read lock during heavy computation. All incoming write requests wait because readers have priority and the current read action cannot be interrupted.

The problematic pattern is readAction.compute() wrapping long computations. The solution requires non-blocking mechanics that allow write actions to interrupt and restart computations when needed.

The fix replaces readAction.compute() with readAction.nonBlocking(), which supports restarting actions. However, these actions must be idempotent - they should produce the same output for the same input without side effects or state changes.

For batch processing or visiting many files with expensive computations, work should be split into small chunks. Each chunk performs a read action for a limited time, allowing write actions to interleave. The outer loop processes items one by one rather than holding a single long read action.

Cancellation in JVM and IntelliJ does not happen automatically. Code must explicitly check for cancellation using ProgressManager.checkCanceled() in loop iterations. Without these checks, there is no point where the code can respond to cancellation requests.

When checkCanceled() detects cancellation, it throws ProcessCanceledException to stop the computation. This is the mechanism for implementing proper cancellation rather than thread interruption.

For coroutine-based code, the readAction function provides non-blocking behavior by default, preventing blocking of incoming write actions during background operations.

Background execution does not guarantee UI responsiveness. Long computations on read locks still block write actions. Solutions include:

  • Using non-blocking read actions
  • Splitting work into chunks
  • Checking cancellation in loops

This allows write actions to proceed and keeps the UI responsive.

A long non-cancellable read action can freeze the IDE

Yuri uses a specialized AI agent configured with IntelliJ platform code and SDK documentation to analyze exceptions and provide detailed answers about platform internals.

When investigating freezes, it can be difficult to determine whether the problem originates from a plugin. If the freeze is suspected to be caused by a specific plugin, reach out to the plugin vendor with a clear message indicating the freeze and the identified cause. This allows the vendor to address the issue.

Jacob is currently offering to fix Gradle build scripts for other plugins. For urgent issues, contacting the team can result in them reviewing the problem and providing assistance.

The team has tools and experience to analyze freezes. Before abandoning a freeze issue that seems unsolvable, reach out for help. The team can provide hints and guidance for resolving the problem.

A specialized skill was created for Claude Code that understands freezes in IntelliJ IDEA plugins. This skill identifies common problems including IO operations, networking, read-write lock contention, and log patterns. It recognizes typical phrases that appear in thread dumps such as "dispatch thread is blocked by IO," "service initialization waits for IO," "we compute file type in VFS listener," "we do some expensive operations on background in VFS listener," and "we do heavy class loading."

When the skill processes a thread dump, it identifies the root cause. For example, when analyzing a thread dump, it determined that the dispatch thread UI thread was blocked while trying to upgrade to a write lock. It identified that the thread was waiting for another thread performing a read action that was non-cancellable and would not yield or restart automatically.

The skill also highlights additional potential problems or secondary causes. It identifies the probable plugin responsible, such as "package checker," and points to specific involved frames. The skill suggests fixes, such as using a non-blocking read action, which would make the read action properly cancellable.

After the webinar, the skill will be shared on GitHub and announced via tweet. This allows developers to use the skill for analyzing unclear thread dumps from users or investigating freezes independently.

Thread dumps visible in logs can be analyzed using the "Analyze Stack Trace" action or the Claude skill. This makes it possible to understand what happened without external assistance.

The skill is specialized and not about the IntelliJ Community itself. It may be shared via gist on GitHub and referenced in a tweet for easy copying and customization.

Skill files should be read rather than used directly in AI agents. These files contain detailed explanations of problems and approaches that are more comprehensive than typical documentation. The files are in markdown format and contain real knowledge about approaching problems because detailed explanations are required for the agent.

Tools are available to help with freeze analysis. The forum at platform.jetbrains.com is available for plugin developers. A distinctive feature of this forum is that the development team participates—actual IntelliJ platform and IntelliJ IDEA engineers who wrote the code are present to assist, not just support engineers.

When a long operation is split into multiple read actions, state can change between actions. Two approaches exist for handling this:

  • Use methods called "read and write action" or "read and background write action." These attempt to perform a single long read action followed immediately by a write action. If cancellation occurs or changes happen between the read and write phases, the read portion restarts. This functions similarly to a database transaction and works when the read action duration is stable and predictable.

  • For longer operations, check modification trackers such as PSI modification tracker and modification count to verify that PSI has not changed between read and write phases.

  • For extremely long operations, model blocking progress may be necessary. This blocks incoming read actions during the operation, providing safety. The complete blocking progress approach displays a centered progress bar indicating a long computation is in progress.

For very long project analysis where consistency is required, model progress should be used. In many cases, consistency is not required, and work can be split into chunks. Complete blocking progress is the final solution when other approaches fail.

Suspend APIs function like a virus—once adopted, they should be used consistently rather than switching back to blocking code. When blocking cancellable code must run, a special call is available on the IntelliJ platform to bridge these contexts. This must be used with care, as it is easy to incorrectly block everything. Network calls are not truly cancellable even under this method.

The "compute detached" method serves as a last resort for performing IO and network operations under read actions. It does not properly cancel—instead, it abandons the computation and proceeds with other work. This approach is not ideal but can work in specific situations.

Exception reporting capabilities exist for plugins, allowing subscription to custom exception reports. Freezes may not yet be submitted through this custom reporting mechanism. Documentation on exception reporting is available in the documentation, community documentation, and marketplace docs. Improvements are planned to automatically submit freezes from users.

When multiple non-blocking synchronous read actions run sequentially and the IDE interrupts the second read action, only the active read action restarts—the one that was running when the write action occurred. Previous read actions are not restarted, which is why splitting work into chunks is beneficial. Only the active piece restarts, preserving results from previous read actions.

A limited offer is available through the forum. Plugin developers can provide their plugin ID to receive all thread dumps and problems identified for their plugin, along with fix suggestions.

Jacob offers Gradle plugin build script fixes. Yuri offers to investigate freezes. These resources are available to plugin developers experiencing issues.

Keep JetBrains in your library

Save the videos and channels worth coming back to, and find them again in one place.