Open
Description
-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?
kotlinx-coroutines-debug
bundles repackagedbyte-buddy
(15,3 MB out of total 20,3 MB).- Considerable time is spent on loading the
byte-buddy
(anywhere from 300ms to 500ms in our runs). - It's used only to patch a single class
kotlin.coroutines.jvm.internal.DebugProbesKt
which is located inkotlin-stdlib
. - It does not work on winarm64 at the moment (minor).
What should be instead?
A way to statically load the proper binary without dealing with byte-buddy
.
Why?
- Reduce the size of production jars.
- Reduce the class loading time.
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.
ProbesBridge
should setAgentInstallationType
in its<clinit>
.ProbesBridge
should start the cleaner thread in its<clinit>
(questionable, but enables no-code setup by putting the debug jar into classpath).- (Optionally) Provide a separate minimal production-ready
kotlinx-coroutines-debug-core
JAR with probes.
kotlinx.coroutines.debug.internal.DebugProbesImpl
resides inkotlinx-coroutines-core-jvm
, I think that it should be a part of that JAR instead.
Other solutions
- Patch
kotlin-stdlib
jar statically, replacekotlin.coroutines.jvm.internal.DebugProbesKt
during build time, publish a separatekotlin-stdlib
artefact. - Patch
kotlin-stdlib
jar dynamically.
2.1. Put the jar beforekotlin-stdlib
in classpath, similar to Please provide artifact with DebugProbes for classpath patching instead of instrumentations. #3356.
2.2. Change IJ own class loader to loadDebugProbesKt.bin
or the proposed class whenkotlin.coroutines.jvm.internal.DebugProbesKt
is requested (somewhat equivalent to javaagent solution, also a hack). - DI probes into stdlib.
3.1. proposed solution, or
3.2. separate interface,ServiceLoader
similar toDispatchers.Main
, no op implementation by default.