From 26b320e5d11d24f24e44b57096a88f37e72caac6 Mon Sep 17 00:00:00 2001 From: Sergei Tachenov Date: Wed, 9 Jul 2025 15:17:56 +0300 Subject: [PATCH] IJPL-196231 Fix launch(Once)OnShow for unconfined coroutines Dispatchers.Unconfined doesn't immediately invoke the coroutine if already running an unconfined coroutine. If the current coroutine is blocked on dialog.show(), it means that no unconfined jobs will be executed while the dialog is showing. To work around it, we replace Dispatchers.Unconfined with the appropriate UI dispatcher that is guaranteed to not dispatch if used with any() modality. Implement the fix under a registry key, disabled for now, so we can safely pick it to the release branches to have this as a possible manual fallback for the users who experience this issue. Reviewed in IJ-CR-168711 for 252 and release. (cherry picked from commit 3cfd1a9e46d12d942c7ab6f30f158b71cd7e52d8) GitOrigin-RevId: c700503bf1f2c328d4be4eb088210a6deed536de --- .../src/com/intellij/util/ui/uiScope.kt | 35 ++++++++++++++----- .../util/resources/misc/registry.properties | 3 ++ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/platform/ide-core/src/com/intellij/util/ui/uiScope.kt b/platform/ide-core/src/com/intellij/util/ui/uiScope.kt index 778ba7472e3a..8b1c9185a21d 100644 --- a/platform/ide-core/src/com/intellij/util/ui/uiScope.kt +++ b/platform/ide-core/src/com/intellij/util/ui/uiScope.kt @@ -1,10 +1,8 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.util.ui -import com.intellij.openapi.application.AccessToken -import com.intellij.openapi.application.ModalityState -import com.intellij.openapi.application.UI -import com.intellij.openapi.application.asContextElement +import com.intellij.openapi.application.* +import com.intellij.openapi.util.registry.Registry import com.intellij.platform.kernel.withKernel import com.intellij.ui.ComponentUtil import com.intellij.util.BitUtil @@ -155,11 +153,30 @@ fun C.launchOnShow( * Launches the given task in the global scope without dispatching. */ private fun launchUnconfined(debugName: String, block: suspend CoroutineScope.() -> Unit): Job { - @OptIn(DelicateCoroutinesApi::class) - return GlobalScope.launch( - context = Dispatchers.Unconfined + CoroutineName(debugName), - block = block, - ) + if (Registry.`is`("ide.ui.coroutine.scopes.unconfined.fix", false)) { + // The whole point here is to launch the coroutine in-place without dispatching, + // and to make sure it doesn't dispatch later when called on the EDT from the hierarchy listener. + // This is why Dispatchers.ui(UiDispatcherKind.RELAX, immediate = true) is used with ModalityState.any(). + // The modality is obvious: using any() ensures that the current modality is ignored when processing hierarchy events. + // The task itself will then check the modality before executing, but events should be processed immediately. + // The dispatcher is a bit more tricky: using EDT will dispatch if the WIL is forbidden (when already running under UI), + // and using UI will dispatch if the WIL is currently locked. + // But RELAX doesn't care about the WIL and whether it's allowed, so it will not dispatch if immediate = true. + // Note that using Dispatchers.Unconfined here seems the obvious choice, + // but it doesn't work when already running an unconfined coroutine (IJPL-196231, CPP-45385). + @OptIn(DelicateCoroutinesApi::class) + return GlobalScope.launch( + context = Dispatchers.ui(UiDispatcherKind.RELAX, immediate = true) + ModalityState.any().asContextElement() + CoroutineName(debugName), + block = block, + ) + } + else { + @OptIn(DelicateCoroutinesApi::class) + return GlobalScope.launch( + context = Dispatchers.Unconfined + CoroutineName(debugName), + block = block, + ) + } } private suspend fun showingAsChannel(component: Component, block: suspend (ReceiveChannel) -> Unit) { diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 6adc12de2192..767b774d641c 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -2507,6 +2507,9 @@ coroutine.scope.model.description=Use the new experimental coroutine-based model ide.treat.project.modality.as.application=true ide.treat.project.modality.as.application.description=Treat DialogWrapper.IdeModalityType.PROJECT as Dialog.ModalityType.APPLICATION_MODAL +ide.ui.coroutine.scopes.unconfined.fix=false +ide.ui.coroutine.scopes.unconfined.fix.description=Enable the fix for launchOnShow/launchOnceOnShow issue that prevented these from working in modal dialogs sometimes. + editor.show.sticky.lines.debug=false editor.show.sticky.lines.debug.description=Show editor sticky lines in debug mode editor.show.sticky.lines.shadow=true