We at JetBrains have been investigating a number of UI freezes and noticed that several of them are caused by the JMix plugin. These freezes are quite severe and impact overall IDE responsiveness.
In most cases, the root cause appears to be the use of non-cancellable ReadAction on background threads. This pattern can easily lead to long UI freezes waiting for a write lock. There’s a detailed explanation of the problem and its implications here:
Here you can also checkout our SKILL for Claude Code that helps in UI freeze analysis
In this thread, I’ll share specific examples along with explanations and the corresponding thread stacks.
If helpful, we can also provide raw freeze reports including full thread dumps and metadata.
Sampled time: 15300ms, sampling rate: 100ms, GC time: 1537ms (5%), Class loading: 0%, CPU load: 14%
Analysis
Cause
The EDT is blocked waiting to acquire a write lock (to run PsiManagerImpl.dropPsiCaches triggered by TypeScriptServiceRestarter.restartServices). The write lock is blocked by a background thread holding a * non-cancellable read action*.
Inside the read action, uses Kotlin Analysis API (SymbolLightModifierList.getAnnotations) which triggers FIR lazy
resolution → KotlinPackageIndexUtils.packageExists → index access → JS stub index update
During JS file indexing, builds PSI stubs (JSFileStubBuilder, JSVariableStubFactory) — this is the hot frame
Jmix Studio (io.jmix.studio / com.haulmont.jmixstudio)
EntityPropertiesEditorProvider.accept uses ReadAction.compute (non-cancellable) to check if a file is a Jmix entity.
This invokes Kotlin Analysis API which triggers FIR resolution and index queries, causing a long-running non-cancellable
read action that prevents write actions from proceeding.
Fix
Replace ReadAction.compute in EntityPropertiesEditorProvider.accept with a cancellable alternative ( ReadAction.nonBlocking().executeSynchronously() or smartReadAction coroutine API), or avoid triggering full Kotlin
FIR analysis / index updates from within a file editor provider check.
Sampled time: 3200ms, sampling rate: 100ms, GC time: 1467ms (6%), Class loading: 0%, CPU load: 65%
Analysis
Cause
The EDT is blocked waiting to acquire a write lock (to run ModuleRootModificationUtil.modifyModel). The write lock
is blocked by a background thread holding a non-cancellable read action via DumbService.runReadActionInSmartMode.
Threads Participating
EDT (---------- EDT:):
State: TIMED_WAITING — waiting in SuvorovProgress.dispatchEventsUntilComputationCompletes
Trying to acquire write lock via NestedLocksThreadingSupport.prepareWriteFromWriteIntentBlocking → ApplicationImpl.runWriteAction → WriteAction.run
Holds non-cancellable read action via DumbService.runReadActionInSmartMode
Inside the read action, searches for inheritors of GUI components via JavaClassInheritorsSearcher
JavaDirectInheritorsSearcher.calculateDirectSubClasses uses a nested ReadAction.compute
Within that: PsiSearchHelperImpl.getUseScope → CubaScopeEnlarger.getAdditionalUseScope → CubaWindows.isScreenController → InheritanceUtil.isInheritor → class lookup → Kotlin stub index access → RegisteredIndexes.waitUntilIndicesAreInitialized (waiting for index init)
Jmix Studio (io.jmix.studio / com.haulmont.jmixstudio)
ComponentLibrary.scheduleInit initializes the GUI component library under a non-cancellable DumbService.runReadActionInSmartMode. During search, CubaScopeEnlarger performs class hierarchy checks requiring
stub index access, which in turn waits for index initialization — all holding the read lock.
Fix
ComponentLibrary.scheduleInit should not use DumbService.runReadActionInSmartMode; use cancellable read actions ( ReadAction.nonBlocking()) or ensure index-dependent work runs outside a read action context.
Sampled time: 6800ms, sampling rate: 100ms, GC time: 1184ms (3%), Class loading: 0%, CPU load: 12%
Analysis
Cause
The EDT is blocked waiting to acquire a write lock (to fire VirtualFileManagerImpl.notifyPropertyChanged). The
write lock is blocked by a background thread holding a non-cancellable read action via ActionsKt.runReadAction.
Threads Participating
EDT (---------- EDT:):
State: TIMED_WAITING — waiting in SuvorovProgress.dispatchEventsUntilComputationCompletes
Trying to acquire write lock via NestedLocksThreadingSupport.prepareWriteFromWriteIntentBlocking → ApplicationImpl.runWriteAction
Jmix Studio (io.jmix.studio / com.haulmont.jmixstudio)
StatManager.collectAndSendStats runs FlowViewLoader.loadScreens under a non-cancellable runReadAction. During XML
file processing, it needs EncodingManager which may not be initialized yet, causing the service init to block inside
the read action, preventing the EDT’s write action from proceeding.
Fix
Move stat collection (StatManager.collectAndSendStats) out of a read action, or make it cancellable
Sampled time: 2200ms, sampling rate: 100ms, GC time: 356ms (2%), Class loading: 0%, CPU load: 17%
Analysis
Cause
The EDT is blocked waiting to acquire a write lock (to run CoroutinesKt.edtWriteAction). The write lock is blocked
by a background thread holding a non-cancellable read action via ActionsKt.runReadAction.
Threads Participating
EDT (---------- EDT:):
State: TIMED_WAITING — waiting in SuvorovProgress.dispatchEventsUntilComputationCompletes
Trying to acquire write lock via NestedLocksThreadingSupport.prepareWriteFromWriteIntentBlocking → ApplicationImpl.runWriteAction
Triggered by: CoroutinesKt.edtWriteAction
Blocking thread ("ApplicationImpl pooled thread 5"):
Jmix Studio (io.jmix.studio / com.haulmont.jmixstudio)
JmixProjectStartupActivity.markJmixSystemDirsAsExcluded calls JmixModuleImpl.getDependencies inside a
non-cancellable runReadAction. The topological sort is recursive and each level acquires a new read action. Inside the
read action, class reference resolution triggers the Scala plugin’s class lookup, which runs through Scala collections
string operations — all while holding the non-cancellable read lock that prevents the EDT write action.
Fix
Make the read action in JmixModuleImpl.getDependencies cancellable (ReadAction.nonBlocking() or coroutine APIs)
Avoid recursive non-cancellable read actions in JmixModuleTopologicalSort.doSort
Sampled time: 7100ms, sampling rate: 100ms, GC time: 306ms (3%), Class loading: 0%, CPU load: 47%
Analysis
Cause
The EDT is blocked waiting to acquire a write-intent lock (acquireWriteIntentPermit). The write-intent lock is
blocked by multiple background threads holding non-cancellable read actions via ApplicationImpl.runReadAction.
Threads Participating
EDT (---------- EDT:):
State: TIMED_WAITING — waiting in SuvorovProgress.dispatchEventsUntilComputationCompletes
Trying to acquire write-intent lock via NestedLocksThreadingSupport$ComputationState.acquireWriteIntentPermit
Jmix Studio (io.jmix.studio / com.haulmont.jmixstudio)
EntityUtil.ra wraps entity annotation search in a non-cancellable ApplicationImpl.runReadAction. Inside, AnnotatedElementsSearcher triggers index updates that perform file type detection using wildcard regex matching — a
non-cancellable CPU-bound operation. Multiple coroutine workers run this concurrently, saturating the thread pool and
starving the EDT of the write-intent lock.
Fix
Replace non-cancellable runReadAction in EntityUtil.ra with a cancellable alternative (ReadAction.nonBlocking()
or coroutine readAction {})
Sampled time: 23900ms, sampling rate: 100ms, GC time: 175ms (0%), Class loading: 0%, CPU load: 20%
The stack is from the thread that was blocking EDT
Analysis
Cause
The EDT is BLOCKED (Java monitor) on a VFS directory data lock (VfsData$DirectoryData) owned by a background
thread. The background thread holds the VFS directory lock while waiting for disk IO ( DiskQueryRelay.accessDiskWithCheckCanceled → ArchiveFileSystem.getAttributes). Both threads are executing HProjectUtils.isClassAvailableInLibraries → VirtualDirectoryImpl.findChild.
Threads Participating
EDT (---------- EDT:):
State: BLOCKED — waiting on com.intellij.openapi.vfs.newvfs.impl.VfsData$DirectoryData@2a0f182a owned by "DefaultDispatcher-worker-8"
Holds the VFS DirectoryData lock while awaiting: PersistentFSImpl.findChildInfo → ArchiveFileSystem.getAttributes → DiskQueryRelay.accessDiskWithCheckCanceled → FutureTask.get (waiting for disk
IO)
Jmix Studio (io.jmix.studio / com.haulmont.jmixstudio)
HProjectUtils.isClassAvailableInLibraries is called both from JmixAiToolWindowFactory.shouldBeAvailable (background
coroutine at project open) and from JmixRunManagerListener.runConfigurationAdded (EDT via invokeLater). Both calls
use ReadAction.compute and access VirtualDirectoryImpl.findChild. When the background thread holds the VFS DirectoryData lock while waiting for archive attribute IO, the EDT’s own findChild call blocks on the same Java
monitor — causing the freeze.
The presence of DiskQueryRelay in the background thread indicates the IO is already properly delegated, but the VFS
directory lock is still held during the async wait, which causes the EDT contention.
Fix
Do not call HProjectUtils.isClassAvailableInLibraries (or any VFS access) from the EDT; defer to a background thread
via ReadAction.nonBlocking()
JmixRunManagerListener.runConfigurationAdded should not invoke Jmix project checks synchronously on the EDT; use AppUIExecutor.onUiThread().inSmartMode() or a coroutine-based approach
The DiskQueryRelay approach in ArchiveFileSystem is appropriate for background threads, but the outer VFS
directory lock must not be held during the IO wait — this requires fixing VFS internals or avoiding archive-backed
library lookups in findChild paths called from EDT