Kotlin / kotlinx.coroutines

Library support for Kotlin coroutines
Apache License 2.0
12.99k stars 1.85k forks source link

A way to statically load DebugProbesKt in production #3633

Open dovchinnikov opened 1 year ago

dovchinnikov commented 1 year ago

-javaagent is not a production-level solution for IJ, so IJ enables coroutine debugger dynamically at very early stage of application start up:

DebugProbes.enableCreationStackTraces = false
DebugProbes.install()

What do we have now?

What should be instead?

A way to statically load the proper binary without dealing with byte-buddy.

Why?

Possible solution

Replace stdlib's kotlin.coroutines.jvm.internal.DebugProbesKt with the following:

private val probesBridge get() = Class.forName("ProbesBridge Name TBD")
private val probeCoroutineCreatedMethod: Method? = runCatching {
  probesBridge.getDeclaredMethod("probeCoroutineCreated", Continuation::class.java)
}.getOrNull()
private val probeCoroutineResumedMethod: Method? = runCatching {
  probesBridge.getDeclaredMethod("probeCoroutineResumed", Continuation::class.java)
}.getOrNull()
private val probeCoroutineSuspended: Method? = runCatching {
  probesBridge.getDeclaredMethod("probeCoroutineSuspended", Continuation::class.java)
}.getOrNull()

internal fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> {
  if (probeCoroutineCreatedMethod != null) {
    return probeCoroutineCreatedMethod.invoke(null, completion) as Continuation<T>
  }
  return completion
}

internal fun probeCoroutineResumed(frame: Continuation<*>) {
  probeCoroutineResumedMethod?.invoke(null, frame)
}

internal fun probeCoroutineSuspended(frame: Continuation<*>) {
  probeCoroutineSuspended?.invoke(null, frame)
}

This approach is already used here. JIT should not have any trouble inlining one of == null branches when ProbesBridge is present or absent.

  1. ProbesBridge should set AgentInstallationType in its <clinit>.
  2. ProbesBridge should start the cleaner thread in its <clinit> (questionable, but enables no-code setup by putting the debug jar into classpath).
  3. (Optionally) Provide a separate minimal production-ready kotlinx-coroutines-debug-core JAR with probes. kotlinx.coroutines.debug.internal.DebugProbesImpl resides in kotlinx-coroutines-core-jvm, I think that it should be a part of that JAR instead.

Other solutions

  1. Patch kotlin-stdlib jar statically, replace kotlin.coroutines.jvm.internal.DebugProbesKt during build time, publish a separate kotlin-stdlib artefact.
  2. Patch kotlin-stdlib jar dynamically. 2.1. Put the jar before kotlin-stdlib in classpath, similar to #3356. 2.2. Change IJ own class loader to load DebugProbesKt.bin or the proposed class when kotlin.coroutines.jvm.internal.DebugProbesKt is requested (somewhat equivalent to javaagent solution, also a hack).
  3. DI probes into stdlib. 3.1. proposed solution, or 3.2. separate interface,ServiceLoader similar to Dispatchers.Main, no op implementation by default.
qwwdfsad commented 1 year ago

-javaagent is not a production-level solution for IJ

From our internal notes:

qwwdfsad commented 1 year ago

https://youtrack.jetbrains.com/issue/KT-62096/Consider-replacing-JVM-agent-specific-kotlin.coroutines.jvm.internal.DebugProbesKt-with-SPI-based-mechanism

sellmair commented 1 week ago

Benchmarks and viability tested and logged here: https://youtrack.jetbrains.com/issue/IDEA-359879/coroutines-debugging-Investigate-split-package-problem