mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 02:59:33 +07:00
IJPL-156281 make coroutine dumper resilient to circular job dependencies
GitOrigin-RevId: 95d64a8fca56c22a2fd9c3d0a7efd29c39ff8126
This commit is contained in:
committed by
intellij-monorepo-bot
parent
58e9edebab
commit
fe1a45223e
@@ -159,22 +159,29 @@ private fun jobTrees(scope: CoroutineScope? = null): Sequence<JobTree> {
|
|||||||
|
|
||||||
return sequence {
|
return sequence {
|
||||||
for (job in rootJobs) {
|
for (job in rootJobs) {
|
||||||
yieldAll(buildJobTrees(job, jobToStack))
|
yieldAll(buildJobTrees(job, jobToStack, hashSetOf()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildJobTrees(
|
private fun buildJobTrees(
|
||||||
job: Job,
|
job: Job,
|
||||||
jobToStack: Map<Job, DebugCoroutineInfo>
|
jobToStack: Map<Job, DebugCoroutineInfo>,
|
||||||
|
visited: MutableSet<Job>
|
||||||
): List<JobTree> {
|
): List<JobTree> {
|
||||||
|
return visited.withElement(job) { notSeenThisJob ->
|
||||||
|
if (notSeenThisJob) {
|
||||||
val info = jobToStack[job]
|
val info = jobToStack[job]
|
||||||
if (info === null && job is ScopeCoroutine<*>) {
|
if (info === null && job is ScopeCoroutine<*>) {
|
||||||
// don't yield ScopeCoroutine without info, such as `coroutineScope` or `withContext`
|
// don't yield ScopeCoroutine without info, such as `coroutineScope` or `withContext`
|
||||||
return job.children.flatMap { buildJobTrees(it, jobToStack) }.toList()
|
job.children.flatMap { buildJobTrees(it, jobToStack, visited) }.toList()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return listOf(JobTree(job, info, job.children.flatMap { buildJobTrees(it, jobToStack) }.toList()))
|
listOf(JobTree(job, info, job.children.flatMap { buildJobTrees(it, jobToStack, visited) }.toList()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
listOf(JobTree(RecursiveJob(job), null, emptyList()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,3 +316,19 @@ private fun traceToDump(info: DebugCoroutineInfo, stripTrace: Boolean): List<Sta
|
|||||||
}
|
}
|
||||||
return DebugProbesImpl.enhanceStackTraceWithThreadDump(info, trace)
|
return DebugProbesImpl.enhanceStackTraceWithThreadDump(info, trace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun <T, R> MutableSet<T>.withElement(elem: T, body: (added: Boolean) -> R): R {
|
||||||
|
val added = add(elem)
|
||||||
|
try {
|
||||||
|
return body(added)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (added) remove(elem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RecursiveJob(private val originalJob: Job) : Job by originalJob {
|
||||||
|
override fun toString(): String {
|
||||||
|
return "CIRCULAR REFERENCE: $originalJob"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// 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.diagnostic
|
||||||
|
|
||||||
|
import com.intellij.platform.util.coroutines.childScope
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.BeforeAll
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.Timeout
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class CoroutineDumpTest {
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@BeforeAll
|
||||||
|
fun enableDumps() {
|
||||||
|
enableCoroutineDump()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(InternalCoroutinesApi::class)
|
||||||
|
@Suppress("SSBasedInspection", "DEPRECATION_ERROR")
|
||||||
|
@Test
|
||||||
|
@Timeout(value = 5, unit = TimeUnit.SECONDS)
|
||||||
|
fun testRecursiveJobsDump() {
|
||||||
|
val projectScope = CoroutineScope(CoroutineName("Project"))
|
||||||
|
val pluginScope = CoroutineScope(CoroutineName("Plugin"))
|
||||||
|
val activity = projectScope.childScope("Project Activity")
|
||||||
|
pluginScope.coroutineContext[Job]!!.attachChild(activity.coroutineContext[Job]!! as ChildJob)
|
||||||
|
// e.g. bug here
|
||||||
|
activity.coroutineContext[Job]!!.attachChild(pluginScope.coroutineContext[Job]!! as ChildJob)
|
||||||
|
assertEquals("""
|
||||||
|
- JobImpl{Active}
|
||||||
|
- "Project Activity":supervisor:ChildScope{Active}
|
||||||
|
- JobImpl{Active}
|
||||||
|
- CIRCULAR REFERENCE: "Project Activity":supervisor:ChildScope{Active}
|
||||||
|
""", dumpCoroutines(projectScope, true, true))
|
||||||
|
assertEquals("""
|
||||||
|
- JobImpl{Active}
|
||||||
|
- "Project Activity":supervisor:ChildScope{Active}
|
||||||
|
- CIRCULAR REFERENCE: JobImpl{Active}
|
||||||
|
""", dumpCoroutines(pluginScope, true, true))
|
||||||
|
projectScope.cancel()
|
||||||
|
pluginScope.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user