[threading] IJPL-179848: Treat context elements with different hashcodes in their default string representation as equal

GitOrigin-RevId: 2b9b0a3494d3e5777f45f9dabdd4d2231f56e63a
This commit is contained in:
Konstantin Nisht
2025-03-13 11:36:09 +00:00
committed by intellij-monorepo-bot
parent 5fd6813a79
commit 7f548f80e7
2 changed files with 46 additions and 1 deletions
@@ -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 <T, R> MutableSet<T>.withElement(elem: T, body: (added: Boolean) -> R): R {
val added = add(elem)
try {
@@ -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<AnElement>
}
@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]+"), "<NUM>").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()
}
}
}