Skip to content

Commit c61d914

Browse files
committed
Support 'ExternalStaticDebugProbes' (in collaboration with the kotlin-stdlib)
1 parent d30af7c commit c61d914

File tree

6 files changed

+115
-0
lines changed

6 files changed

+115
-0
lines changed

Diff for: integration-testing/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The tests are the following:
99
* `debugAgentTest` checks that the coroutine debugger can be run as a Java agent.
1010
* `debugDynamicAgentTest` checks that `kotlinx-coroutines-debug` agent can self-attach dynamically to JVM as a standalone dependency.
1111
* `debugDynamicAgentJpmsTest` checks that `kotlinx-coroutines-debug` agent can self-attach dynamically to JVM as a standalone dependency (with JPMS)
12+
* `externalStaticDebugProbesTest` checks that a `ExternalStaticDebugProbes` is picked up by the kotlin stdlib
1213
* `smokeTest` builds the multiplatform test project that depends on coroutines.
1314

1415
The `integration-testing` project is expected to be in a subdirectory of the main `kotlinx.coroutines` project.

Diff for: integration-testing/jpmsTest/build.gradle.kts

+17
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ kotlin {
2121
jvmToolchain(17)
2222

2323
val test = target.compilations.getByName("test")
24+
2425
target.compilations.create("debugDynamicAgentJpmsTest") {
2526
associateWith(test)
2627

@@ -35,6 +36,22 @@ kotlin {
3536
classpath = javaSourceSet.runtimeClasspath
3637
}
3738
}
39+
40+
41+
target.compilations.create("externalStaticDebugProbesTest") {
42+
associateWith(test)
43+
44+
45+
defaultSourceSet.dependencies {
46+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version")
47+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-debug:$coroutines_version")
48+
}
49+
50+
tasks.register<Test>("externalStaticDebugProbesTest") {
51+
testClassesDirs = output.classesDirs
52+
classpath = javaSourceSet.runtimeClasspath
53+
}
54+
}
3855
}
3956

4057
tasks.named("check") {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package kotlinx.coroutines.external
2+
3+
import kotlinx.coroutines.debug.internal.AbstractStaticDebugProbes
4+
import kotlin.coroutines.*
5+
6+
object ExternalStaticDebugProbes: AbstractStaticDebugProbes() {
7+
override fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> {
8+
return super.probeCoroutineCreated(completion)
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import org.junit.*
2+
import kotlinx.coroutines.*
3+
import kotlinx.coroutines.external.ExternalStaticDebugProbes
4+
import org.junit.Test
5+
import java.io.*
6+
7+
class ExternalStaticDebugProbesTest {
8+
9+
@Test
10+
fun testDumpCoroutines() {
11+
runBlocking {
12+
val baos = ByteArrayOutputStream()
13+
ExternalStaticDebugProbes.dumpCoroutines(PrintStream(baos))
14+
// if the agent works, then dumps should contain something,
15+
// at least the fact that this test is running.
16+
val dump = baos.toString()
17+
Assert.assertTrue(dump, dump.contains("testDumpCoroutines"))
18+
}
19+
}
20+
}

Diff for: kotlinx-coroutines-core/api/kotlinx-coroutines-core.api

+8
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,14 @@ public final class kotlinx/coroutines/channels/TickerMode : java/lang/Enum {
911911
public static fun values ()[Lkotlinx/coroutines/channels/TickerMode;
912912
}
913913

914+
public abstract class kotlinx/coroutines/debug/internal/AbstractStaticDebugProbes {
915+
public fun <init> ()V
916+
public final fun dumpCoroutines (Ljava/io/PrintStream;)V
917+
public fun probeCoroutineCreated (Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
918+
public fun probeCoroutineResumed (Lkotlin/coroutines/Continuation;)V
919+
public fun probeCoroutineSuspended (Lkotlin/coroutines/Continuation;)V
920+
}
921+
914922
public final class kotlinx/coroutines/debug/internal/AgentInstallationType {
915923
public static final field INSTANCE Lkotlinx/coroutines/debug/internal/AgentInstallationType;
916924
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package kotlinx.coroutines.debug.internal
2+
3+
import kotlinx.coroutines.*
4+
import java.io.*
5+
import kotlin.coroutines.*
6+
7+
/**
8+
* Allows to statically install 'Debug Probes' at the known location
9+
* (kotlinx.coroutines.external.ExternalStaticDebugProbes).
10+
*
11+
* **Discussion**
12+
*
13+
* There are three methods of installing/engaging coroutines 'Debug Probes'
14+
*
15+
* 1) Dynamic Attach (using the 'kotlinx-coroutines-debug' module)
16+
* This uses runtime byte-code alteration to replace the 'Debug Probes' straight from the kotlin-stdlib
17+
*
18+
* 2) Static Attach using an Agent
19+
* This uses a java agent to replace the 'Debug Probes' from the kotlin-stdlib statically
20+
*
21+
* 3) ExternalStaticDebugProbes
22+
* The kotlin-stdlib compiled against a class at
23+
* `kotlinx.coroutines.external.ExternalStaticDebugProbes` which is not available at runtime, by default.
24+
* If a class at this location is present, then the kotlin-stdlib will call into it.
25+
*
26+
* ```kotlin
27+
* package kotlinx.coroutines.external
28+
* object ExternalStaticDebugProbes: AbstractStaticDebugProbes() {
29+
* override fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> {
30+
* // intercept
31+
* // ...
32+
*
33+
* // forward to debugger machinery
34+
* return super.probeCoroutineCreated(completion)
35+
* }
36+
* }
37+
* ```
38+
*/
39+
@Suppress("unused")
40+
@DelicateCoroutinesApi
41+
@ExperimentalCoroutinesApi
42+
abstract class AbstractStaticDebugProbes {
43+
init {
44+
require(javaClass.name == "kotlinx.coroutines.external.ExternalStaticDebugProbes")
45+
AgentInstallationType.isInstalledStatically = true
46+
DebugProbesImpl.install()
47+
}
48+
49+
open fun probeCoroutineResumed(frame: Continuation<*>) = DebugProbesImpl.probeCoroutineResumed(frame)
50+
51+
open fun probeCoroutineSuspended(frame: Continuation<*>) = DebugProbesImpl.probeCoroutineSuspended(frame)
52+
53+
open fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> =
54+
DebugProbesImpl.probeCoroutineCreated(completion)
55+
56+
fun dumpCoroutines(out: PrintStream) {
57+
DebugProbesImpl.dumpCoroutines(out)
58+
}
59+
}

0 commit comments

Comments
 (0)