kotlinx.coroutines
kotlinx.coroutines copied to clipboard
A way to statically load DebugProbesKt in production
-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-debugbundles 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.DebugProbesKtwhich 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.
ProbesBridgeshould setAgentInstallationTypein its<clinit>.ProbesBridgeshould 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-coreJAR with probes.kotlinx.coroutines.debug.internal.DebugProbesImplresides inkotlinx-coroutines-core-jvm, I think that it should be a part of that JAR instead.
Other solutions
- Patch
kotlin-stdlibjar statically, replacekotlin.coroutines.jvm.internal.DebugProbesKtduring build time, publish a separatekotlin-stdlibartefact. - Patch
kotlin-stdlibjar dynamically. 2.1. Put the jar beforekotlin-stdlibin classpath, similar to #3356. 2.2. Change IJ own class loader to loadDebugProbesKt.binor the proposed class whenkotlin.coroutines.jvm.internal.DebugProbesKtis requested (somewhat equivalent to javaagent solution, also a hack). - DI probes into stdlib.
3.1. proposed solution, or
3.2. separate interface,
ServiceLoadersimilar toDispatchers.Main, no op implementation by default.
-javaagent is not a production-level solution for IJ
From our internal notes:
- Passing
-javaagenteverywhere in each launch script is quite a lot of technical burden - It potentially (?) affects classloading times as it processes all classes in IJ
https://youtrack.jetbrains.com/issue/KT-62096/Consider-replacing-JVM-agent-specific-kotlin.coroutines.jvm.internal.DebugProbesKt-with-SPI-based-mechanism