[coroutines-debugger] Fix the slow Coroutine View: obtain jobs and parents of coroutines with a Helper method.

Build the coroutine hierarchy in CoroutineView, from a job and the first parent of each coroutine.

IDEA-335303

GitOrigin-RevId: 7fe634a291364438229cfae7680894fba25b07c6
This commit is contained in:
Maria Sokolova
2025-01-16 11:51:05 +00:00
committed by intellij-monorepo-bot
parent cc2d37d9cc
commit 10472a2795
11 changed files with 346 additions and 208 deletions
@@ -11,6 +11,10 @@ public final class CoroutinesDebugHelper {
private static final String COROUTINE_OWNER_CLASS = "CoroutineOwner";
private static final String DEBUG_METADATA_FQN = "kotlin.coroutines.jvm.internal.DebugMetadataKt";
private static final String BASE_CONTINUATION_FQN = "kotlin.coroutines.jvm.internal.BaseContinuationImpl";
private static final String DEBUG_COROUTINE_INFO_FQN = "kotlinx.coroutines.debug.internal.DebugCoroutineInfo";
private static final String COROUTINE_CONTEXT_FQN = "kotlin.coroutines.CoroutineContext";
private static final String COROUTINE_JOB_FQN = "kotlinx.coroutines.Job";
private static final String COROUTINE_CONTEXT_KEY_FQN = "kotlin.coroutines.CoroutineContext$Key";
public static long[] getCoroutinesRunningOnCurrentThread(Object debugProbes, Thread currentThread) throws ReflectiveOperationException {
List<Long> coroutinesIds = new ArrayList<>();
@@ -140,12 +144,62 @@ public final class CoroutinesDebugHelper {
return current.getClass().getSimpleName().contains(COROUTINE_OWNER_CLASS);
}
public static Object[] dumpCoroutinesInfoAsJsonAndReferences() throws ReflectiveOperationException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> debugProbesImplClass = classLoader.loadClass("kotlinx.coroutines.debug.internal.DebugProbesImpl");
Object debugProbesImplInstance = debugProbesImplClass.getField("INSTANCE").get(null);
Object[] infos = (Object[])invoke(debugProbesImplInstance, "dumpCoroutinesInfoAsJsonAndReferences");
return infos;
}
/**
* This method takes the array of {@link kotlinx.coroutines.debug.internal.DebugCoroutineInfo} instances
* and for each coroutine requests it's job, and it's first parent.
*
* @return an array of Strings of size (debugCoroutineInfos.size * 2), where
* (2 * i)-th element is a String representation of the job and
* (2 * i + 1)-th element is a String representation of the parent of the i-th coroutine from debugCoroutineInfos array.
*/
public static String[] getJobsAndParentsForCoroutines(Object ... debugCoroutineInfos) throws ReflectiveOperationException {
if (debugCoroutineInfos.length == 0) return new String[]{};
String[] jobsWithParents = new String[debugCoroutineInfos.length * 2];
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");
for (int i = 0; i < debugCoroutineInfos.length * 2; i += 2) {
Object info = debugCoroutineInfos[i / 2];
if (info == null) {
jobsWithParents[i] = null;
jobsWithParents[i + 1] = null;
continue;
}
Object context = invoke(info, getContext);
Object job = invoke(context, coroutineContextGet, coroutineJobKey);
Object parent = invoke(job, getParentJob);
jobsWithParents[i] = (job == null) ? null : job.toString();
jobsWithParents[i + 1] = (parent == null) ? null : parent.toString();
}
return jobsWithParents;
}
private static Object getField(Object object, String fieldName) throws ReflectiveOperationException {
Field field = object.getClass().getField(fieldName);
field.setAccessible(true);
return field.get(object);
}
private static Object invoke(Object object, Method method, Object... args) throws ReflectiveOperationException {
method.setAccessible(true);
return method.invoke(object, args);
}
private static Object invoke(Object object, String methodName) throws ReflectiveOperationException {
Method method = object.getClass().getMethod(methodName);
method.setAccessible(true);