Summary
Cursive's cursive.files.FileContentWatcherService registers a ProjectRootManager beforeRootsChange listener that, when a roots/model change fires, calls stopDocumentProcessing which invokes kotlinx.coroutines.runBlocking { ... } directly on the AWT Event Dispatch Thread (EDT).
runBlocking parks the EDT until a coroutine running on Dispatchers.Default completes. This violates the IntelliJ Platform rule that the EDT must never be blocked — and especially must never block awaiting a coroutine on another dispatcher. The result is a full, input-ignoring IDE UI freeze on every project open and on every module roots/library change (e.g. Maven/Gradle import/sync) while the Cursive plugin is enabled.
The freeze was confirmed sustained — the EDT was observed stuck in this exact runBlocking across four consecutive JetBrains auto-captured freeze thread dumps spanning roughly 76 seconds, not a momentary sampling artifact.
Environment
| Item |
Value |
| IDE |
IntelliJ IDEA Ultimate, build IU-261.25134.95 (2026.1) |
| JBR / JVM |
Java 25.0.3 |
| Cursive plugin version |
2026.1-261 (bundled as plugins/clojure-plugin/lib/cursive.jar) |
| OS |
macOS (Darwin 24.6.0) |
| Rendering |
Apple / OpenGL Java2D pipeline |
| Notable state |
13 project windows open simultaneously at time of freeze |
Steps to Reproduce
- Have the Cursive (Clojure) plugin enabled.
- Open a project. (Reproduced reliably by opening a project.)
- Observe the IDE during the model/roots-update phase — for example while the status bar shows "Downloading repository indexes" during a Maven/Gradle import/sync.
The IDE UI freezes and ignores all input during this phase.
Notes on the trigger:
beforeRootsChange fires whenever module roots/libraries change — notably during Maven/Gradle project import/sync (the model-update / "Downloading repository indexes" phase) and on project open.
- Deleting
.idea/ and .iml files before reopening does not help, because the listener is registered globally by the plugin and is independent of any per-project configuration.
Expected Behavior
A roots/model change (project open, Maven/Gradle sync) should not block the EDT. The IDE should remain fully responsive to user input while Cursive tears down or restarts its document-processing work.
Actual Behavior
The EDT (AWT-EventQueue-0) parks inside kotlinx.coroutines.runBlocking, called synchronously from Cursive's beforeRootsChange listener, until a Dispatchers.Default coroutine completes. The entire IDE UI freezes and ignores all input for the duration of the block.
Root Cause Analysis
The defect is in cursive.files.FileContentWatcherService:
- It registers a
ProjectRootManager beforeRootsChange listener (setupDocumentListeners$1.beforeRootsChange, watcher.kt:172).
- That listener calls
stopDocumentProcessing (watcher.kt:151), which invokes kotlinx.coroutines.runBlocking { ... } directly on the EDT.
runBlocking parks the EDT (BlockingCoroutine.joinBlocking) until a Dispatchers.Default coroutine completes.
The coroutine the EDT is waiting on is cursive.files.FileContentWatcherService$startDocumentProcessing$1 (watcher.kt:142) — a ProducerCoroutine running on Dispatchers.Default, in state SUSPENDED. Because the EDT cannot proceed until that producer is torn down, and the producer runs on a contended dispatcher, the EDT stays parked.
The beforeRootsChange event itself is delivered synchronously via the platform MessageBus, dispatched from the workspace-model commit (WorkspaceModelImpl.onBeforeChanged → ProjectRootManagerImpl.fireBeforeRootsChanged → ProjectRootManagerComponent.fireBeforeRootsChangeEvent), so any blocking inside the listener directly blocks the thread that fired it — here, the EDT.
EDT stack trace (captured on AWT-EventQueue-0, state TIMED_WAITING on kotlinx.coroutines.BlockingCoroutine)
"AWT-EventQueue-0" prio=0 tid=0x0 nid=0x0 waiting on condition
java.lang.Thread.State: TIMED_WAITING
on kotlinx.coroutines.BlockingCoroutine@3631388b
at java.base@25.0.3/jdk.internal.misc.Unsafe.park(Native Method)
at java.base@25.0.3/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:271)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:121)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$BuildersKt__BuildersKt(Builders.kt:87)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:55)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:50)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at cursive.files.FileContentWatcherService.stopDocumentProcessing(watcher.kt:151)
at cursive.files.FileContentWatcherService.access$stopDocumentProcessing(watcher.kt:44)
at cursive.files.FileContentWatcherService$setupDocumentListeners$1.beforeRootsChange(watcher.kt:172)
at com.intellij.util.messages.impl.MessageBusImplKt.invokeMethod(MessageBusImpl.kt:831)
at com.intellij.util.messages.impl.MessageBusImplKt.invokeListener(MessageBusImpl.kt:775)
at com.intellij.util.messages.impl.MessageBusImplKt.deliverMessage(MessageBusImpl.kt:514)
at com.intellij.util.messages.impl.MessageBusImplKt.pumpWaiting(MessageBusImpl.kt:493)
at com.intellij.util.messages.impl.MessagePublisher.invoke(MessageBusImpl.kt:556)
at jdk.proxy2/jdk.proxy2.$Proxy269.beforeRootsChange(Unknown Source)
at com.intellij.openapi.roots.impl.ProjectRootManagerComponent.fireBeforeRootsChangeEvent(ProjectRootManagerComponent.kt:264)
at com.intellij.openapi.roots.impl.ProjectRootManagerImpl.fireBeforeRootsChanged(ProjectRootManagerImpl.kt:430)
at com.intellij.openapi.roots.impl.ProjectRootManagerImpl$BatchSession.beforeRootsChanged(ProjectRootManagerImpl.kt:127)
at com.intellij.workspaceModel.ide.impl.legacyBridge.project.ModuleRootListenerBridgeImpl.fireBeforeRootsChanged(ModuleRootListenerBridgeImpl.kt:42)
at com.intellij.workspaceModel.ide.impl.legacyBridge.module.LegacyProjectModelListenersBridge.beforeChanged(LegacyProjectModelListenersBridge.kt:50)
at com.intellij.workspaceModel.ide.impl.WorkspaceModelImpl.onBeforeChanged$lambda$0$0(WorkspaceModelImpl.kt:493)
...
The coroutine being awaited (separately reported in the same dumps) is cursive.files.FileContentWatcherService$startDocumentProcessing$1 (watcher.kt:142), a ProducerCoroutine on Dispatchers.Default, state SUSPENDED.
Frequency / Severity
Severity: high — full IDE UI freeze. The IDE becomes completely unresponsive to input on every project open / roots change while the plugin is enabled.
Evidence that this is a sustained hang and not a one-off sampling blip:
- The EDT was observed stuck in this exact
runBlocking across four consecutive thread dumps taken 15 seconds apart, spanning roughly 76 seconds (14:50:13, 14:50:29, 14:50:44, 14:51:29 on 2026-06-15).
idea.log shows repeated "400–560 ms to grab EDT" warnings in the lead-up to the freeze.
Aggravating factor: with 13 open projects, each running Cursive's full activity set on Dispatchers.Default, the Default dispatcher is under contention. The coroutine the EDT blocks on is therefore slow to be scheduled, lengthening each stall.
Workaround
Disable the Cursive / Clojure plugin. This was confirmed to eliminate the freeze, tying the hang directly to Cursive.
Suggested Fix
(Offered as a suggestion.) Do not call runBlocking on the EDT inside beforeRootsChange. Any of the following would resolve the EDT block:
- (a) Make
stopDocumentProcessing non-blocking — launch the teardown on a background coroutine and let the document-processing producer be cancelled asynchronously rather than awaited on the EDT.
- (b) Move the teardown out of the synchronous root-change callback entirely.
- (c) If synchronous teardown is genuinely required, ensure it completes without dispatching to and awaiting
Dispatchers.Default from the EDT.
Appendix: Source thread-dump files
All four files are in the directory:
~/Library/Logs/JetBrains/IntelliJIdea2026.1/threadDumps-Dispatchers.Default-20260615-144713-IU-261.25134.95/
The following sequential dumps all show the same EDT block in the exact runBlocking described above:
threadDump-20260615-145013-1781509813764.txt (14:50:13)
threadDump-20260615-145029-1781509829105.txt (14:50:29)
threadDump-20260615-145044-1781509844381.txt (14:50:44)
threadDump-20260615-145129-1781509889818.txt (14:51:29)
Let me know if you need the thread dump files
Summary
Cursive's
cursive.files.FileContentWatcherServiceregisters aProjectRootManagerbeforeRootsChangelistener that, when a roots/model change fires, callsstopDocumentProcessingwhich invokeskotlinx.coroutines.runBlocking { ... }directly on the AWT Event Dispatch Thread (EDT).runBlockingparks the EDT until a coroutine running onDispatchers.Defaultcompletes. This violates the IntelliJ Platform rule that the EDT must never be blocked — and especially must never block awaiting a coroutine on another dispatcher. The result is a full, input-ignoring IDE UI freeze on every project open and on every module roots/library change (e.g. Maven/Gradle import/sync) while the Cursive plugin is enabled.The freeze was confirmed sustained — the EDT was observed stuck in this exact
runBlockingacross four consecutive JetBrains auto-captured freeze thread dumps spanning roughly 76 seconds, not a momentary sampling artifact.Environment
plugins/clojure-plugin/lib/cursive.jar)Steps to Reproduce
The IDE UI freezes and ignores all input during this phase.
Notes on the trigger:
beforeRootsChangefires whenever module roots/libraries change — notably during Maven/Gradle project import/sync (the model-update / "Downloading repository indexes" phase) and on project open..idea/and.imlfiles before reopening does not help, because the listener is registered globally by the plugin and is independent of any per-project configuration.Expected Behavior
A roots/model change (project open, Maven/Gradle sync) should not block the EDT. The IDE should remain fully responsive to user input while Cursive tears down or restarts its document-processing work.
Actual Behavior
The EDT (
AWT-EventQueue-0) parks insidekotlinx.coroutines.runBlocking, called synchronously from Cursive'sbeforeRootsChangelistener, until aDispatchers.Defaultcoroutine completes. The entire IDE UI freezes and ignores all input for the duration of the block.Root Cause Analysis
The defect is in
cursive.files.FileContentWatcherService:ProjectRootManagerbeforeRootsChangelistener (setupDocumentListeners$1.beforeRootsChange,watcher.kt:172).stopDocumentProcessing(watcher.kt:151), which invokeskotlinx.coroutines.runBlocking { ... }directly on the EDT.runBlockingparks the EDT (BlockingCoroutine.joinBlocking) until aDispatchers.Defaultcoroutine completes.The coroutine the EDT is waiting on is
cursive.files.FileContentWatcherService$startDocumentProcessing$1(watcher.kt:142) — aProducerCoroutinerunning onDispatchers.Default, in stateSUSPENDED. Because the EDT cannot proceed until that producer is torn down, and the producer runs on a contended dispatcher, the EDT stays parked.The
beforeRootsChangeevent itself is delivered synchronously via the platformMessageBus, dispatched from the workspace-model commit (WorkspaceModelImpl.onBeforeChanged→ProjectRootManagerImpl.fireBeforeRootsChanged→ProjectRootManagerComponent.fireBeforeRootsChangeEvent), so any blocking inside the listener directly blocks the thread that fired it — here, the EDT.EDT stack trace (captured on
AWT-EventQueue-0, stateTIMED_WAITINGonkotlinx.coroutines.BlockingCoroutine)The coroutine being awaited (separately reported in the same dumps) is
cursive.files.FileContentWatcherService$startDocumentProcessing$1(watcher.kt:142), aProducerCoroutineonDispatchers.Default, stateSUSPENDED.Frequency / Severity
Severity: high — full IDE UI freeze. The IDE becomes completely unresponsive to input on every project open / roots change while the plugin is enabled.
Evidence that this is a sustained hang and not a one-off sampling blip:
runBlockingacross four consecutive thread dumps taken 15 seconds apart, spanning roughly 76 seconds (14:50:13, 14:50:29, 14:50:44, 14:51:29 on 2026-06-15).idea.logshows repeated "400–560 ms to grab EDT" warnings in the lead-up to the freeze.Aggravating factor: with 13 open projects, each running Cursive's full activity set on
Dispatchers.Default, theDefaultdispatcher is under contention. The coroutine the EDT blocks on is therefore slow to be scheduled, lengthening each stall.Workaround
Disable the Cursive / Clojure plugin. This was confirmed to eliminate the freeze, tying the hang directly to Cursive.
Suggested Fix
(Offered as a suggestion.) Do not call
runBlockingon the EDT insidebeforeRootsChange. Any of the following would resolve the EDT block:stopDocumentProcessingnon-blocking — launch the teardown on a background coroutine and let the document-processing producer be cancelled asynchronously rather than awaited on the EDT.Dispatchers.Defaultfrom the EDT.Appendix: Source thread-dump files
All four files are in the directory:
The following sequential dumps all show the same EDT block in the exact
runBlockingdescribed above:threadDump-20260615-145013-1781509813764.txt(14:50:13)threadDump-20260615-145029-1781509829105.txt(14:50:29)threadDump-20260615-145044-1781509844381.txt(14:50:44)threadDump-20260615-145129-1781509889818.txt(14:51:29)Let me know if you need the thread dump files