mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 22:51:17 +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
|
package com.intellij.openapi.application.impl
|
||||||
|
|
||||||
import com.intellij.openapi.application.ModalityState
|
import com.intellij.openapi.application.ModalityState
|
||||||
import org.jetbrains.annotations.ApiStatus.Internal
|
import org.jetbrains.annotations.ApiStatus.Internal
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
fun <T> inModalContext(modalJob: JobProvider, action: (ModalityState) -> T): T {
|
fun <T> inModalContext(modalEntity: Any, action: (ModalityState) -> T): T {
|
||||||
val newModalityState = LaterInvocator.getCurrentModalityState().appendEntity(modalJob)
|
val newModalityState = LaterInvocator.getCurrentModalityState().appendEntity(modalEntity)
|
||||||
LaterInvocator.enterModal(modalJob, newModalityState)
|
LaterInvocator.enterModal(modalEntity, newModalityState)
|
||||||
try {
|
try {
|
||||||
return action(newModalityState)
|
return action(newModalityState)
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
LaterInvocator.leaveModal(modalJob)
|
LaterInvocator.leaveModal(modalEntity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,13 @@
|
|||||||
package com.intellij.ide
|
package com.intellij.ide
|
||||||
|
|
||||||
import com.intellij.diagnostic.dumpCoroutines
|
import com.intellij.diagnostic.dumpCoroutines
|
||||||
|
import com.intellij.diagnostic.isCoroutineDumpEnabled
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.application.impl.ApplicationImpl
|
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.Logger
|
||||||
|
import com.intellij.openapi.diagnostic.trace
|
||||||
import com.intellij.openapi.progress.impl.pumpEventsForHierarchy
|
import com.intellij.openapi.progress.impl.pumpEventsForHierarchy
|
||||||
import com.intellij.openapi.project.impl.ProjectImpl
|
import com.intellij.openapi.project.impl.ProjectImpl
|
||||||
import com.intellij.openapi.util.EmptyRunnable
|
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.ModalTaskOwner
|
||||||
import com.intellij.platform.ide.progress.TaskCancellation
|
import com.intellij.platform.ide.progress.TaskCancellation
|
||||||
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
|
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.io.blockingDispatcher
|
||||||
import com.intellij.util.ui.EDT
|
import com.intellij.util.ui.EDT
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import javax.swing.SwingUtilities
|
import javax.swing.SwingUtilities
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.nanoseconds
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
private val LOG = Logger.getInstance("#com.intellij.ide.shutdown")
|
private val LOG = Logger.getInstance("#com.intellij.ide.shutdown")
|
||||||
@@ -83,3 +90,53 @@ internal fun cancelAndJoinBlocking(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val delayUntilCoroutineDump: Duration = 10.seconds
|
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.ui.Messages
|
||||||
import com.intellij.openapi.util.*
|
import com.intellij.openapi.util.*
|
||||||
import com.intellij.openapi.util.io.FileUtil
|
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.VirtualFile
|
||||||
import com.intellij.openapi.vfs.impl.ZipHandler
|
import com.intellij.openapi.vfs.impl.ZipHandler
|
||||||
import com.intellij.openapi.wm.IdeFocusManager
|
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
|
// somebody can start progress here, do not wrap in write action
|
||||||
fireProjectClosing(project)
|
fireProjectClosing(project)
|
||||||
if (project is ProjectImpl) {
|
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
|
return line == COROUTINE_DUMP_HEADER || line == COROUTINE_DUMP_HEADER_STRIPPED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isCoroutineDumpEnabled(): Boolean {
|
||||||
|
return DebugProbes.isInstalled
|
||||||
|
}
|
||||||
|
|
||||||
fun enableCoroutineDump(): Result<Unit> {
|
fun enableCoroutineDump(): Result<Unit> {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
DebugProbes.enableCreationStackTraces = false
|
DebugProbes.enableCreationStackTraces = false
|
||||||
@@ -43,7 +47,7 @@ fun enableCoroutineDump(): Result<Unit> {
|
|||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun dumpCoroutines(scope: CoroutineScope? = null, stripDump: Boolean = true, deduplicateTrees: Boolean = true): String? {
|
fun dumpCoroutines(scope: CoroutineScope? = null, stripDump: Boolean = true, deduplicateTrees: Boolean = true): String? {
|
||||||
if (!DebugProbes.isInstalled) {
|
if (!isCoroutineDumpEnabled()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val charset = StandardCharsets.UTF_8.name()
|
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=false
|
||||||
ide.background.save.settings.description=Save project settings on a background thread (experimental)
|
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
|
# please leave this note as last line
|
||||||
# TODO please use EP com.intellij.registryKey for plugin/product specific keys
|
# TODO please use EP com.intellij.registryKey for plugin/product specific keys
|
||||||
|
|||||||
Reference in New Issue
Block a user