Skip to content

Fix bug ALT_11006#34

Open
Tsyklop wants to merge 8 commits intomainfrom
bug/ALT-11006
Open

Fix bug ALT_11006#34
Tsyklop wants to merge 8 commits intomainfrom
bug/ALT-11006

Conversation

@Tsyklop
Copy link
Copy Markdown
Collaborator

@Tsyklop Tsyklop commented Apr 17, 2026

Old background task replaced by new courutines.

Copy link
Copy Markdown
Contributor

@meanmail meanmail left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

The PR migrates JDK preselection in JdkLanguageSettings from the legacy runInBackground (ProgressManager-based background task) to Kotlin coroutines. It also adds request invalidation via AtomicInteger, guard checks for disposed UI components, and a syncSdksModelWithJdkTable helper.

Critical Issue

All heavy work now runs on EDT

The coroutine is launched with Dispatchers.EDT and all blocking operations (sdksModel.reset(), addBundledJdkIfNeeded, syncSdksModelWithJdkTable, prewarmSdkValidation) execute on the Event Dispatch Thread. The old code explicitly ran these in a background thread via runInBackground with the comment: "Reset SDK model off-EDT to avoid IllegalStateException from synchronous progress on EDT".

Dispatchers.EDT dispatches onto the Swing EDT. Since these are blocking (non-suspend) calls, they will block the UI thread — potentially causing freezes, especially sdksModel.reset(project) and VFS operations in prewarmSdkValidation.

The background work should use Dispatchers.Default or Dispatchers.IO, with only the UI updates (finishPreselectJdk) dispatched to EDT via withContext(Dispatchers.EDT).

Other Issues

See inline comments.

Comment thread intellij-plugin/hs-jvm-core/src/org/hyperskill/academy/jvm/JdkLanguageSettings.kt Outdated
Comment thread intellij-plugin/hs-jvm-core/src/org/hyperskill/academy/jvm/JdkLanguageSettings.kt Outdated
Comment thread intellij-plugin/hs-jvm-core/src/org/hyperskill/academy/jvm/JdkLanguageSettings.kt Outdated
Comment thread intellij-plugin/hs-jvm-core/src/org/hyperskill/academy/jvm/JdkLanguageSettings.kt Outdated
Comment thread AGENTS.md Outdated
@meanmail
Copy link
Copy Markdown
Contributor

meanmail commented Apr 20, 2026

Why Dispatchers.IO/Default didn't work (and why EDT is not the fix)

Root cause

I dug into ProjectSdksModel source code and found that different methods have different threading requirements:

reset(project) — safe on any thread

Simple synchronous operation: clears the internal map, clones SDKs from ProjectJdkTable. No ProgressManager, no EDT assertions. Can run on Dispatchers.IO.

addSdk(SdkType, String, Callback)requires EDT

This is the overload called from addBundledJdkIfNeeded via model.addSdk(JavaSdk.getInstance(), jdkPath, null). Internally it calls setupSdk():

// ProjectSdksModel.java, setupSdk method
ProgressManager.getInstance()
  .runProcessWithProgressSynchronously(
    () -> { sdkType.setupSdkPaths(newJdk, this); },
    "Configuring SDK", true, null);

runProcessWithProgressSynchronously shows a modal progress dialog — this requires EDT. On a background thread it will either deadlock or fail.

addSdk(Sdk) (the other overload) — safe on any thread

This just calls doAdd() which clones the SDK and fires events. No EDT requirement.

Why the old code "worked" with runInBackground

The old code called addBundledJdkIfNeeded from a background thread, but swallowed all exceptions:

try { addBundledJdkIfNeeded(sdksModel) }
catch (_: Throwable) { // best-effort; ignore failures }

So bundled JDK addition was likely silently failing in the old code too.

The fix: split work by threading requirements

uiScope.launch {
  // Step 1: heavy work on IO (no ProgressManager needed)
  val result = withContext(Dispatchers.IO) {
    if (project != null) sdksModel.reset(project)
    syncSdksModelWithJdkTable(sdksModel)
    val suitableJdk = findSuitableJdk(...) ?: findSuitableJdkFromTable(...)
    // ... build PreselectJdkResult
  }

  // Step 2: addBundledJdkIfNeeded NEEDS EDT
  // because addSdk(SdkType, String, Callback) -> setupSdk -> runProcessWithProgressSynchronously
  addBundledJdkIfNeeded(sdksModel)

  // Step 3: prewarm on IO (VFS/filesystem operations should be off EDT)
  withContext(Dispatchers.IO) { prewarmSdkValidation(result.suitableJdk) }

  // Step 4: UI update - already on EDT (uiScope is EDT-bound)
  finishPreselectJdk(jdkComboBox, result)
}

General rule for future migrations

When migrating from runInBackground/Task.Backgroundable to coroutines, check what the legacy code actually calls internally:

IntelliJ API pattern Threading requirement
ProgressManager.runProcessWithProgressSynchronously(...) EDT (shows modal dialog)
ProgressManager.checkCanceled() Needs ProgressIndicator on thread - use coroutineToIndicator
Pure computation / data cloning Any thread (Dispatchers.IO or Default)
VFS / filesystem access Off EDT (Dispatchers.IO)
Swing component updates EDT (Dispatchers.EDT or withContext(Dispatchers.Main))

Don't assume a single method like runInBackground { ... } had uniform threading needs - the block may contain calls with conflicting requirements. When it "worked", some parts may have been silently failing.

Tsyklop added 3 commits April 20, 2026 16:02
migrate to courutine instead of background tasks.
migrate to courutine instead of background tasks.
migrate to courutine instead of background tasks.
@Tsyklop Tsyklop requested a review from meanmail April 20, 2026 14:04
Copy link
Copy Markdown
Contributor

@meanmail meanmail left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Threading split looks correct now — reset/sync/find on IO, addBundledJdkIfNeeded on EDT, prewarm on IO. Nice work.

One bug and one minor issue remaining, see inline comments.

Comment thread intellij-plugin/hs-jvm-core/src/org/hyperskill/academy/jvm/JdkLanguageSettings.kt Outdated
fix review
@Tsyklop Tsyklop requested a review from meanmail April 21, 2026 07:18
Copy link
Copy Markdown
Contributor

@meanmail meanmail left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both issues fixed. LGTM!

fix build
Copy link
Copy Markdown
Contributor

@meanmail meanmail left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving after new commit. The additional changes (EAP version bump, plugin version 2026.9→2026.10, OpenProjectTask constructor adaptation for 261 API change) look fine.

Minor nit: import java.nio.file.Path in OpenProjectTaskCompat.kt appears unused — all Path? params are passed as null. Likely needed for constructor overload resolution at compile time, but worth a quick check if the build passes without it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants