IDEA-384931 [debugger]: Collect the hierarchy of thread containers for virtual threads

GitOrigin-RevId: ea0c34a1f0f0718e0dc534ab738adbbdb308cb17
This commit is contained in:
Maria Sokolova
2026-02-16 23:07:47 +00:00
committed by intellij-monorepo-bot
parent 1ae9e16a76
commit 0cc3449b3a
9 changed files with 287 additions and 286 deletions
@@ -217,7 +217,11 @@ private fun dumpItemDtos(allDumpItems: List<DumpItem>, maxItems: Int): ThreadDum
isDeadLocked = it.isDeadLocked,
stackTraceIndex = stackTraceIndex,
iconToolTipIndex = iconToolTipToIndex[it.iconToolTip]!!.toByte(),
firstLine = firstLine)
firstLine = firstLine,
isContainer = it.isContainer,
id = it.id,
parentId = it.parentId,
)
}
return ThreadDumpWithAwaitingDependencies(items = items,
@@ -73,7 +73,7 @@ class ThreadDumpAction {
@JvmStatic
fun buildThreadStates(vmProxy: VirtualMachineProxyImpl): List<ThreadState> {
val platformThreads = vmProxy.virtualMachine.allThreads()
return buildThreadStates(vmProxy, platformThreads, virtualThreads = emptyList())
return buildThreadStates(vmProxy, platformThreads, virtualThreads = emptyList(), emptyList())
}
@JvmStatic
@@ -85,7 +85,7 @@ class ThreadDumpAction {
@ApiStatus.Internal
suspend fun buildThreadDump(context: DebuggerContextImpl, onlyPlatformThreads: Boolean, dumpItemsChannel: SendChannel<List<MergeableDumpItem>>) {
suspend fun sendJavaPlatformThreads() {
val platformThreads = buildJavaPlatformThreadDump(context).toDumpItems()
val platformThreads = toDumpItems(buildJavaPlatformThreadDump())
dumpItemsChannel.send(platformThreads)
}
@@ -185,7 +185,7 @@ class ThreadDumpAction {
}
}
fun buildJavaPlatformThreadDump(context: DebuggerContextImpl): List<ThreadState> {
fun buildJavaPlatformThreadDump(): List<ThreadState> {
val vm = VirtualMachineProxyImpl.getCurrent()
vm.suspend()
try {
@@ -315,6 +315,7 @@ private fun buildThreadStates(
vmProxy: VirtualMachineProxyImpl,
platformThreads: List<ThreadReference>,
virtualThreads: List<Triple<ThreadReference, String, Long>>,
threadContainerRefs: List<ObjectReference>
): List<ThreadState> {
val result = mutableListOf<ThreadState>()
@@ -355,21 +356,23 @@ private fun buildThreadStates(
val threadName: String
val stateString: String
val javaThreadStateString: String
val threadContainerId: Long?
val isVirtual: Boolean
val isDaemon: Boolean
val tid: Long?
val prio: Int?
val rawStackTrace: String
if (virtualThreadInfo != null) {
val nameStateAndStackTrace = splitFirstTwoAndRemainingLines(virtualThreadInfo.first)
val nameRaw = nameStateAndStackTrace.first
javaThreadStateString = nameStateAndStackTrace.second
rawStackTrace = nameStateAndStackTrace.third
val lines = virtualThreadInfo.first.lineSequence()
val (nameRaw, javaThreadState, threadContainerIdx) = lines.take(3).toList()
rawStackTrace = lines.drop(3).joinToString("\n")
if (javaThreadStateString == Thread.State.TERMINATED.name) return
if (javaThreadState == Thread.State.TERMINATED.name) return
threadName = threadName(nameRaw, threadReference)
stateString = javaThreadStateToState(javaThreadStateString)
stateString = javaThreadStateToState(javaThreadState)
javaThreadStateString = javaThreadState
threadContainerId = threadContainerRefs[threadContainerIdx.toInt()].uniqueID()
tid = virtualThreadInfo.second
@@ -384,6 +387,7 @@ private fun buildThreadStates(
threadName = threadName(threadReference)
stateString = threadStatusToState(threadStatus)
javaThreadStateString = threadStatusToJavaThreadState(threadStatus)
threadContainerId = null // thread container is not provided for platform threads for now
isVirtual = threadReference is ThreadReferenceImpl && threadReference.isVirtual
@@ -394,9 +398,11 @@ private fun buildThreadStates(
prio = getFieldValue(priorityField, threadReference, holderObj)?.let { (it as IntegerValue).intValue() }
tid = getFieldValue(tidField, threadReference, holderObj)?.let { (it as LongValue).longValue() }
}
val threadState = ThreadState(threadName, stateString)
threadState.javaThreadState = javaThreadStateString
threadState.uniqueId = threadReference.uniqueID()
threadState.threadContainerUniqueId = threadContainerId
nameToThreadMap[threadName] = threadState
result += threadState
@@ -503,6 +509,9 @@ private fun buildThreadStates(
processOne(pthread, virtualThreadInfo = null)
}
if (virtualThreads.isNotEmpty()) {
require(threadContainerRefs.isNotEmpty()) { "The list of thread container references was not provided for virtual threads." }
}
virtualThreads.forEach { (vthread, stackTrace, tid) ->
processOne(vthread, stackTrace to tid)
}
@@ -554,13 +563,6 @@ private fun getStackTrace(threadReference: ThreadReference): String {
}
}
private fun splitFirstTwoAndRemainingLines(text: String): Triple<String, String, String> {
val first = text.lineSequence().first()
val second = text.lineSequence().drop(1).first()
val remaining = text.lineSequence().drop(2).joinToString("\n")
return Triple(first, second, remaining)
}
internal class JavaVirtualThreadsProvider : ThreadDumpItemsProviderFactory() {
override fun getProvider(context: DebuggerContextImpl) = object : ThreadDumpItemsProvider {
val vm = VirtualMachineProxyImpl.getCurrent()
@@ -581,13 +583,12 @@ internal class JavaVirtualThreadsProvider : ThreadDumpItemsProviderFactory() {
return (
if (!enabled) emptyList()
else {
val virtualThreads = evaluateAndGetAllVirtualThreads(suspendContext!!)
buildThreadStates(vm, platformThreads = emptyList(), virtualThreads).toDumpItems()
evaluateAndGetAllVirtualThreadsDumpItems(suspendContext!!)
})
.also { DebuggerStatistics.logVirtualThreadsDump(context.project, it.size) }
}
private fun evaluateAndGetAllVirtualThreads(suspendContext: SuspendContextImpl): List<Triple<ThreadReference, String, Long>> {
private fun evaluateAndGetAllVirtualThreadsDumpItems(suspendContext: SuspendContextImpl): List<MergeableDumpItem> {
val evaluationContext = EvaluationContextImpl(suspendContext, suspendContext.frameProxy)
val lookupImpl = getMethodHandlesImplLookup(evaluationContext)
@@ -599,7 +600,7 @@ internal class JavaVirtualThreadsProvider : ThreadDumpItemsProviderFactory() {
val evaluated = try {
DebuggerUtilsImpl.invokeHelperMethod(
evaluationContext,
VirtualThreadDumper::class.java, "getAllVirtualThreadsWithStackTraces",
VirtualThreadDumper::class.java, "getAllVirtualThreadsWithStackTracesAndContainers",
listOf(lookupImpl)
)
}
@@ -609,10 +610,22 @@ internal class JavaVirtualThreadsProvider : ThreadDumpItemsProviderFactory() {
}
if (evaluated == null) return emptyList()
val (packedThreadsAndStackTraces, threadIds) = (evaluated as ArrayReference).values.map { (it as ArrayReference).values }
val packedThreadsAndStackTraces = ((evaluated as ArrayReference).values[0] as ArrayReference).values
val threadIds = (evaluated.values[1] as ArrayReference).values
val threadContainerNames = (evaluated.values[2] as ArrayReference).values.map { (it as StringReference).value() }
val threadContainerRefs = (evaluated.values[3] as ArrayReference).values.map { it as ObjectReference }
val parentContainerOrdinals = (evaluated.values[4] as ArrayReference).values.map { (it as IntegerValue).intValue() }
require(threadContainerNames.size == threadContainerRefs.size) { "The number of thread container names should be equal the number of thread container references." }
require(threadContainerNames.size == parentContainerOrdinals.size) { "The number of thread container names should be equal the number of corresponding parent container ordinals." }
val threadStates = buildVirtualThreadStates(packedThreadsAndStackTraces, threadIds, threadContainerRefs)
return toDumpItems(threadStates, threadContainerNames, threadContainerRefs, parentContainerOrdinals)
}
private fun buildVirtualThreadStates(packedThreadsAndStackTraces: List<Value?>, threadIds: List<Value?>, threadContainerRefs: List<ObjectReference>): List<ThreadState> {
ProgressManager.checkCanceled()
return buildList {
val virtualThreads = buildList {
var tidIdx = 0
var stIdx = 0
while (stIdx < packedThreadsAndStackTraces.size) {
@@ -627,6 +640,7 @@ internal class JavaVirtualThreadsProvider : ThreadDumpItemsProviderFactory() {
}
}
}
return buildThreadStates(vm, platformThreads = emptyList(), virtualThreads, threadContainerRefs)
}
}
}
@@ -131,8 +131,14 @@ private class FrontendDumpItem(
override val attributes: SimpleTextAttributes get() = attributesCache[itemDto.attributesIndex.toInt().toUInt().toInt()]
override val isDeadLocked: Boolean get() = itemDto.isDeadLocked
override val awaitingDumpItems: Set<DumpItem> get() = internalAwaitingItems
override val isContainer: Boolean get() = itemDto.isContainer
override val id: Long get() = itemDto.id
override val parentId: Long? get() = itemDto.parentId
fun setAwaitingItems(items: Set<DumpItem>) {
internalAwaitingItems = items
}
override fun toString(): String =
"FrontendDumpItem(name=$name)"
}
@@ -87,4 +87,7 @@ data class JavaThreadDumpItemDto(
val iconIndex: Byte,
val attributesIndex: Byte,
val isDeadLocked: Boolean,
val isContainer: Boolean,
val id: Long,
val parentId: Long?,
)