diff --git a/platform/platform-impl/src/com/intellij/ide/startup/impl/StartupManagerImpl.kt b/platform/platform-impl/src/com/intellij/ide/startup/impl/StartupManagerImpl.kt index 5bbfdd265191..7179ab6bfa1e 100644 --- a/platform/platform-impl/src/com/intellij/ide/startup/impl/StartupManagerImpl.kt +++ b/platform/platform-impl/src/com/intellij/ide/startup/impl/StartupManagerImpl.kt @@ -36,6 +36,7 @@ import com.intellij.platform.diagnostic.telemetry.Scope import com.intellij.platform.diagnostic.telemetry.TelemetryManager import com.intellij.serviceContainer.ComponentManagerImpl import com.intellij.util.ModalityUiUtil +import com.intellij.util.application import com.intellij.util.concurrency.ThreadingAssertions import io.opentelemetry.api.common.AttributeKey import io.opentelemetry.api.common.Attributes @@ -48,6 +49,7 @@ import org.jetbrains.annotations.VisibleForTesting import java.awt.event.InvocationEvent import java.util.* import java.util.concurrent.atomic.AtomicInteger +import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.coroutineContext import kotlin.time.Duration.Companion.minutes @@ -175,14 +177,10 @@ open class StartupManagerImpl(private val project: Project, private val coroutin } else { val activities = runPostStartupActivities(async = true) - if (coroutineContext.contextModality() == null || coroutineContext.contextModality() == ModalityState.nonModal()) { - withTimeout(2.minutes) { - activities.joinAll() - } + withTimeout(2.minutes) { + activities.joinAll() } - // don't wait, because waiting under modal progress may result in a deadlock - // (for example, if activities are waiting for smart mode which will only start in non-modal context) CompletableDeferred(activities) } } @@ -258,7 +256,9 @@ open class StartupManagerImpl(private val project: Project, private val coroutin if (activity is ProjectActivity) { if (async) { - val job = launchActivity(activity = activity, project = project, pluginId = pluginDescriptor.pluginId) + val job = blockingContext { + launchActivity(activity = activity, project = project, pluginId = pluginDescriptor.pluginId) + } launchedActivities.add(job) } else { @@ -466,8 +466,13 @@ private fun launchBackgroundPostStartupActivity(activity: Any, pluginId: PluginI } private fun launchActivity(activity: ProjectActivity, project: Project, pluginId: PluginId): Job { + // we propagate modality only in unit tests, because in unit tests activities are often started in modal context (see TestProjectManager), + // so any "invokeAndWait" will hang without modality propagation. In the future we want to avoid .joinAll in runPostStartupActivities at all. + val launchTaskModality = if (application.isUnitTestMode) currentThreadContextModality() else null + val launchTaskModalityContext = launchTaskModality?.asContextElement() ?: EmptyCoroutineContext + return (project as ComponentManagerImpl).pluginCoroutineScope(activity.javaClass.classLoader).launch( - tracer.rootSpan(name = "run activity", arrayOf("class", activity.javaClass.name, "plugin", pluginId.idString)) + launchTaskModalityContext + tracer.rootSpan(name = "run activity", arrayOf("class", activity.javaClass.name, "plugin", pluginId.idString)), ) { activity.execute(project) } diff --git a/platform/testFramework/src/com/intellij/project/TestProjectManager.kt b/platform/testFramework/src/com/intellij/project/TestProjectManager.kt index 7c21bab3e695..6f0672dedcb0 100644 --- a/platform/testFramework/src/com/intellij/project/TestProjectManager.kt +++ b/platform/testFramework/src/com/intellij/project/TestProjectManager.kt @@ -27,16 +27,12 @@ import com.intellij.openapi.project.impl.runInitProjectActivities import com.intellij.openapi.startup.StartupManager import com.intellij.openapi.util.Disposer import com.intellij.testFramework.LeakHunter -import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.TestApplicationManager.Companion.publishHeapDump import com.intellij.testFramework.common.LEAKED_PROJECTS import com.intellij.util.ModalityUiUtil -import com.intellij.util.application import com.intellij.util.containers.UnsafeWeakList import com.intellij.util.ref.GCUtil -import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.runBlocking import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.TestOnly @@ -118,7 +114,7 @@ open class TestProjectManager : ProjectManagerImpl() { val app = ApplicationManager.getApplication() try { - val launchedActivities: List = runUnderModalProgressIfIsEdt { + runUnderModalProgressIfIsEdt { coroutineScope { runInitProjectActivities(project = project) } @@ -129,17 +125,6 @@ open class TestProjectManager : ProjectManagerImpl() { emptyList() } } - - // wait outside the modal progress, because some activities will be waiting for smart mode - // which on start will only start in non-modal context - launchedActivities.forEach { - if (application.isDispatchThread) { - PlatformTestUtil.waitForFuture(it.asCompletableFuture()) - } - else { - it.asCompletableFuture().get(2, TimeUnit.MINUTES) - } - } } catch (e: ProcessCanceledException) { app.invokeAndWait { closeProject(project, saveProject = false, checkCanClose = false) }