mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-14 18:05:27 +07:00
IJPL-475 ability to skip waiting for completion of project coroutines
GitOrigin-RevId: 8f976fe99b2e8b1d333889ba92a5517da7dfd1e2
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a71c43c894
commit
28c8c92318
@@ -1,17 +1,17 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.openapi.application.impl
|
||||
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
|
||||
@Internal
|
||||
fun <T> inModalContext(modalJob: JobProvider, action: (ModalityState) -> T): T {
|
||||
val newModalityState = LaterInvocator.getCurrentModalityState().appendEntity(modalJob)
|
||||
LaterInvocator.enterModal(modalJob, newModalityState)
|
||||
fun <T> inModalContext(modalEntity: Any, action: (ModalityState) -> T): T {
|
||||
val newModalityState = LaterInvocator.getCurrentModalityState().appendEntity(modalEntity)
|
||||
LaterInvocator.enterModal(modalEntity, newModalityState)
|
||||
try {
|
||||
return action(newModalityState)
|
||||
}
|
||||
finally {
|
||||
LaterInvocator.leaveModal(modalJob)
|
||||
LaterInvocator.leaveModal(modalEntity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
package com.intellij.ide
|
||||
|
||||
import com.intellij.diagnostic.dumpCoroutines
|
||||
import com.intellij.diagnostic.isCoroutineDumpEnabled
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.impl.ApplicationImpl
|
||||
import com.intellij.openapi.application.impl.inModalContext
|
||||
import com.intellij.openapi.diagnostic.Attachment
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.diagnostic.trace
|
||||
import com.intellij.openapi.progress.impl.pumpEventsForHierarchy
|
||||
import com.intellij.openapi.project.impl.ProjectImpl
|
||||
import com.intellij.openapi.util.EmptyRunnable
|
||||
@@ -12,11 +16,14 @@ import com.intellij.openapi.util.IntellijInternalApi
|
||||
import com.intellij.platform.ide.progress.ModalTaskOwner
|
||||
import com.intellij.platform.ide.progress.TaskCancellation
|
||||
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
|
||||
import com.intellij.serviceContainer.ComponentManagerImpl
|
||||
import com.intellij.util.ObjectUtils
|
||||
import com.intellij.util.io.blockingDispatcher
|
||||
import com.intellij.util.ui.EDT
|
||||
import kotlinx.coroutines.*
|
||||
import javax.swing.SwingUtilities
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.nanoseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
private val LOG = Logger.getInstance("#com.intellij.ide.shutdown")
|
||||
@@ -83,3 +90,53 @@ internal fun cancelAndJoinBlocking(
|
||||
}
|
||||
|
||||
private val delayUntilCoroutineDump: Duration = 10.seconds
|
||||
|
||||
internal fun cancelAndTryJoin(project: ProjectImpl) {
|
||||
val containerScope = project.coroutineScope
|
||||
val debugString = "Project $project"
|
||||
LOG.trace { "$debugString: trying to join scope" }
|
||||
val containerJob = containerScope.coroutineContext.job
|
||||
val start = System.nanoTime()
|
||||
|
||||
containerJob.cancel()
|
||||
if (containerJob.isCompleted) {
|
||||
LOG.trace { "$debugString: already completed" }
|
||||
return
|
||||
}
|
||||
|
||||
inModalContext(ObjectUtils.sentinel("$debugString shutdown")) { // enter modality to avoid running arbitrary write actions which
|
||||
LOG.trace { "$debugString: flushing EDT queue" }
|
||||
IdeEventQueue.getInstance().flushQueue() // flush once to give EDT coroutines a chance to complete
|
||||
}
|
||||
if (containerJob.isCompleted) {
|
||||
val elapsed = System.nanoTime() - start
|
||||
// this might mean that the flush helped coroutines to complete OR completion happened on BG during the flush
|
||||
LOG.trace { "$debugString: completed after flush in ${elapsed.nanoseconds}" }
|
||||
return
|
||||
}
|
||||
|
||||
if (!isCoroutineDumpEnabled()) {
|
||||
return
|
||||
}
|
||||
// TODO install and use currentThreadCoroutineScope instead OR make this function suspending
|
||||
val applicationScope = (ApplicationManager.getApplication() as ComponentManagerImpl).getCoroutineScope()
|
||||
applicationScope.launch(@OptIn(IntellijInternalApi::class, DelicateCoroutinesApi::class) blockingDispatcher) {
|
||||
val dumpJob = launch {
|
||||
delay(delayUntilCoroutineDump)
|
||||
LOG.error(
|
||||
"$debugString: scope was not completed in $delayUntilCoroutineDump",
|
||||
Attachment("coroutineDump.txt", dumpCoroutines(scope = containerScope)!!),
|
||||
)
|
||||
}
|
||||
try {
|
||||
containerJob.join()
|
||||
val elapsed = System.nanoTime() - start
|
||||
LOG.trace { "$debugString: completed in ${elapsed.nanoseconds}" }
|
||||
dumpJob.cancel()
|
||||
}
|
||||
catch (ce: CancellationException) {
|
||||
LOG.trace { "$debugString: coroutine dump was cancelled" }
|
||||
throw ce
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ import com.intellij.openapi.ui.MessageDialogBuilder
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.util.*
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.impl.ZipHandler
|
||||
import com.intellij.openapi.wm.IdeFocusManager
|
||||
@@ -382,7 +383,12 @@ open class ProjectManagerImpl : ProjectManagerEx(), Disposable {
|
||||
// somebody can start progress here, do not wrap in write action
|
||||
fireProjectClosing(project)
|
||||
if (project is ProjectImpl) {
|
||||
cancelAndJoinBlocking(project)
|
||||
if (Registry.`is`("ide.await.project.scope.completion")) {
|
||||
cancelAndJoinBlocking(project)
|
||||
}
|
||||
else {
|
||||
cancelAndTryJoin(project)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,10 @@ fun isCoroutineDumpHeader(line: String): Boolean {
|
||||
return line == COROUTINE_DUMP_HEADER || line == COROUTINE_DUMP_HEADER_STRIPPED
|
||||
}
|
||||
|
||||
fun isCoroutineDumpEnabled(): Boolean {
|
||||
return DebugProbes.isInstalled
|
||||
}
|
||||
|
||||
fun enableCoroutineDump(): Result<Unit> {
|
||||
return runCatching {
|
||||
DebugProbes.enableCreationStackTraces = false
|
||||
@@ -43,7 +47,7 @@ fun enableCoroutineDump(): Result<Unit> {
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun dumpCoroutines(scope: CoroutineScope? = null, stripDump: Boolean = true, deduplicateTrees: Boolean = true): String? {
|
||||
if (!DebugProbes.isInstalled) {
|
||||
if (!isCoroutineDumpEnabled()) {
|
||||
return null
|
||||
}
|
||||
val charset = StandardCharsets.UTF_8.name()
|
||||
|
||||
@@ -2424,5 +2424,9 @@ editor.show.sticky.lines.debug.description=Show editor sticky lines in debug mod
|
||||
ide.background.save.settings=false
|
||||
ide.background.save.settings.description=Save project settings on a background thread (experimental)
|
||||
|
||||
ide.await.project.scope.completion=true
|
||||
ide.await.project.scope.completion.description=Whether closing project should wait for all coroutines to be completed before disposing the project. \
|
||||
When `false`, the project scope is cancelled without waiting. This may result in exceptions and project leaks.
|
||||
|
||||
# please leave this note as last line
|
||||
# TODO please use EP com.intellij.registryKey for plugin/product specific keys
|
||||
|
||||
Reference in New Issue
Block a user