diff --git a/platform/util/base/src/com/intellij/diagnostic/coroutineDumper.kt b/platform/util/base/src/com/intellij/diagnostic/coroutineDumper.kt index 9e6a906011a8..bded3f807a71 100644 --- a/platform/util/base/src/com/intellij/diagnostic/coroutineDumper.kt +++ b/platform/util/base/src/com/intellij/diagnostic/coroutineDumper.kt @@ -333,9 +333,24 @@ private fun CoroutineContext.joinElementsToString(): String = if (acc.isNotEmpty()) { acc.append(", ") } - acc.append(element.toString()) + acc.append(element.toStringSmart()) }.toString() +/** + * Default implementation of [Object.toString] includes hex representation of the object's hash code + * This prevents our logic for deduplication, resulting in huge coroutine dumps + */ +private fun Any.toStringSmart(): String { + val baseToString = toString() + val hashCodeSuffix = "@" + Integer.toHexString(this.hashCode()) + return if (baseToString.endsWith(hashCodeSuffix)) { + baseToString.substring(0, baseToString.length - hashCodeSuffix.length) + } + else { + return baseToString + } +} + private fun MutableSet.withElement(elem: T, body: (added: Boolean) -> R): R { val added = add(elem) try { diff --git a/platform/util/testSrc/com/intellij/diagnostic/CoroutineDumpTest.kt b/platform/util/testSrc/com/intellij/diagnostic/CoroutineDumpTest.kt index 213af2633d63..dd36b471a112 100644 --- a/platform/util/testSrc/com/intellij/diagnostic/CoroutineDumpTest.kt +++ b/platform/util/testSrc/com/intellij/diagnostic/CoroutineDumpTest.kt @@ -2,6 +2,8 @@ package com.intellij.diagnostic import com.intellij.platform.util.coroutines.childScope +import com.intellij.testFramework.common.timeoutRunBlocking +import com.intellij.testFramework.junit5.SystemProperty import kotlinx.coroutines.* import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeAll @@ -11,6 +13,8 @@ import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.locks.LockSupport import kotlin.concurrent.thread +import kotlin.coroutines.AbstractCoroutineContextElement +import kotlin.coroutines.CoroutineContext class CoroutineDumpTest { companion object { @@ -97,4 +101,30 @@ class CoroutineDumpTest { t.join() } } + + private class AnElement : AbstractCoroutineContextElement(Key) { + companion object Key : CoroutineContext.Key + } + + @Test + fun `different instances of the same class with base toString are deduplicated`() = timeoutRunBlocking { + val job = Job() + repeat(10) { + launch(AnElement()) { + job.join() + } + } + try { + val dumpWithDeduplication = dumpCoroutines(this, stripDump = false, deduplicateTrees = true)!! + // because in debug mode, there is CoroutineId which appears in the context and in coroutine names + val dumpWithReplacedNumbers = dumpWithDeduplication.replace(Regex("[0-9]+"), "").lines() + val targetLine = dumpWithReplacedNumbers.find { it.contains("AnElement") }!! + // if it was not for CoroutineId, this dump would be deduplicated + // here we check that apart from these numbers, nothing blocks the deduplication + assert(dumpWithReplacedNumbers.count { it == targetLine } == 10) + } + finally { + job.complete() + } + } } \ No newline at end of file