mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-384931 [debugger]: Extract job hierarchy for CoroutineInfoData relying on the uniqueId of Job object rather than its string representation
GitOrigin-RevId: 2f05173bc71bc2ac9ad41af35ab081bd7823352e
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1f79479346
commit
088fedd031
@@ -8,6 +8,7 @@ import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -276,6 +277,64 @@ public final class CoroutinesDebugHelper {
|
||||
return jobToCapturedParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Object array containing coroutine job hierarchy information, or {@code null} if something went wrong
|
||||
* or job corresponding to some coroutine does not exist.
|
||||
* <ol>
|
||||
* <li> {@code String[]} - string representations of the {@code kotlinx.coroutines.Job Job} extracted
|
||||
* from each coroutine's context, in the same order as the input array.</li>
|
||||
* <li> {@code Object[]} - the actual {@code kotlinx.coroutines.Job Job} objects corresponding to each coroutine.</li>
|
||||
* <li> {@code int[]} - for each job, the index of its nearest ancestor job that is also present
|
||||
* in this dump, or -1 if no such parent exists.</li>
|
||||
* </ol>
|
||||
*/
|
||||
public static Object[] getCoroutineJobHierarchyInfo(Object ... debugCoroutineInfos) throws ReflectiveOperationException {
|
||||
if (debugCoroutineInfos.length == 0) return new String[]{};
|
||||
ClassLoader loader = debugCoroutineInfos[0].getClass().getClassLoader();
|
||||
Class<?> debugCoroutineInfoClass = Class.forName(DEBUG_COROUTINE_INFO_FQN, false, loader);
|
||||
Class<?> coroutineContext = Class.forName(COROUTINE_CONTEXT_FQN, false, loader);
|
||||
Class<?> coroutineContextKey = Class.forName(COROUTINE_CONTEXT_KEY_FQN, false, loader);
|
||||
Class<?> coroutineJobClass = Class.forName(COROUTINE_JOB_FQN, false, loader);
|
||||
Object coroutineJobKey = coroutineJobClass.getField("Key").get(null); // Job.Key
|
||||
Method coroutineContextGet = coroutineContext.getMethod("get", coroutineContextKey);
|
||||
Method getParentJob = coroutineJobClass.getMethod("getParent");
|
||||
Method getContext = debugCoroutineInfoClass.getMethod("getContext");
|
||||
|
||||
String[] jobNames = new String[debugCoroutineInfos.length];
|
||||
Object[] jobObjects = new Object[debugCoroutineInfos.length];
|
||||
int[] parentIndexes = new int[debugCoroutineInfos.length];
|
||||
Arrays.fill(parentIndexes, -1);
|
||||
|
||||
for (int i = 0; i < debugCoroutineInfos.length; i++) {
|
||||
Object info = debugCoroutineInfos[i];
|
||||
Object context = invoke(info, getContext);
|
||||
Object job = invoke(context, coroutineContextGet, coroutineJobKey);
|
||||
// job corresponding to the coroutine should not be null, coroutines should be dumped again, fast return
|
||||
if (job == null) return null;
|
||||
jobNames[i] = job.toString();
|
||||
jobObjects[i] = job;
|
||||
}
|
||||
// we use this set of coroutine jobs to only save parent jobs which correspond to some coroutine in the dump
|
||||
Map<Object, Integer> jobToIndex = new HashMap<>();
|
||||
for (int i = 0; i < jobObjects.length; i++) {
|
||||
Object obj = jobObjects[i];
|
||||
jobToIndex.put(obj, i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < debugCoroutineInfos.length; i++) {
|
||||
Object job = jobObjects[i];
|
||||
Object parent = invoke(job, getParentJob);
|
||||
while (parent != null) {
|
||||
if (jobToIndex.containsKey(parent)) {
|
||||
parentIndexes[i] = jobToIndex.get(parent);
|
||||
break;
|
||||
}
|
||||
parent = invoke(parent, getParentJob);
|
||||
}
|
||||
}
|
||||
return new Object[]{jobNames, jobObjects, parentIndexes};
|
||||
}
|
||||
|
||||
private static Object getField(Object object, String fieldName) throws ReflectiveOperationException {
|
||||
Field field = object.getClass().getField(fieldName);
|
||||
field.setAccessible(true);
|
||||
|
||||
+5
@@ -38,6 +38,11 @@ open class CoroutineInfoData(
|
||||
|
||||
var job: String? = null
|
||||
|
||||
var jobId: Long? = null
|
||||
|
||||
var parentJobId: Long? = null
|
||||
|
||||
@Deprecated("Do not use parentJob String as an id to group coroutines, use parentJobId instead.")
|
||||
var parentJob: String? = null
|
||||
|
||||
// NOTE: dispatchers may have a custom String representation, see IDEA-371498
|
||||
|
||||
+33
@@ -6,6 +6,8 @@ import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerUtilsImpl.logError
|
||||
import com.intellij.rt.debugger.coroutines.CoroutinesDebugHelper
|
||||
import com.sun.jdi.ArrayReference
|
||||
import com.sun.jdi.IntegerValue
|
||||
import com.sun.jdi.ObjectReference
|
||||
import com.sun.jdi.StringReference
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.kotlin.idea.debugger.base.util.evaluate.DefaultExecutionContext
|
||||
@@ -39,6 +41,15 @@ class CoroutineDebugProbesProxy(val suspendContext: SuspendContextImpl) {
|
||||
return coroutineInfoCache
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun dumpCoroutinesWithHierarchy(): CoroutineInfoCache {
|
||||
val cache = dumpCoroutines()
|
||||
val infos = cache.cache
|
||||
if (cache.cache.isEmpty()) return cache
|
||||
fetchAndSetJobNamesAndJobUniqueIds(infos)
|
||||
return cache
|
||||
}
|
||||
|
||||
/**
|
||||
* This method aims to reduce the overhead of obtaining the whole parent hierarchy for every DebugCoroutineInfo.
|
||||
* It is invoked only when the coroutines hierarchy is requested in the Coroutines View.
|
||||
@@ -67,6 +78,28 @@ class CoroutineDebugProbesProxy(val suspendContext: SuspendContextImpl) {
|
||||
return true
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
fun fetchAndSetJobNamesAndJobUniqueIds(infos: List<CoroutineInfoData>): Boolean {
|
||||
val executionContext = suspendContext.executionContext() ?: return false
|
||||
val debugCoroutineInfos = infos.map { it.debugCoroutineInfoRef }
|
||||
val array = callMethodFromHelper(CoroutinesDebugHelper::class.java, executionContext, "getCoroutineJobHierarchyInfo", debugCoroutineInfos)
|
||||
if (array == null) return false
|
||||
val jobNames = ((array as ArrayReference).values[0] as ArrayReference).values.map { (it as StringReference).value() }
|
||||
val jobRefs = (array.values[1] as ArrayReference).values.map { (it as ObjectReference) }
|
||||
val parentIndexes = (array.values[2] as ArrayReference).values.map { (it as IntegerValue).value() }
|
||||
for ((i, info) in infos.withIndex()) {
|
||||
info.job = jobNames[i]
|
||||
info.jobId = jobRefs[i].uniqueID()
|
||||
val parentIndex = parentIndexes[i]
|
||||
if (parentIndex == -1) {
|
||||
info.parentJobId = null
|
||||
} else {
|
||||
info.parentJobId = jobRefs[parentIndex].uniqueID()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun fallBackToMirrorFetchJobsAndParentsForCoroutines(
|
||||
executionContext: DefaultExecutionContext,
|
||||
infos: List<CoroutineInfoData>
|
||||
|
||||
Reference in New Issue
Block a user