Skip to content

Commit 5f2f137

Browse files
committed
Support 'ExternalStaticDebugProbes' (in collaboration with the kotlin-stdlib)
1 parent 6655f85 commit 5f2f137

File tree

6 files changed

+121
-1
lines changed

6 files changed

+121
-1
lines changed

Diff for: integration-testing/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The tests are the following:
88
* `coreAgentTest` checks that `kotlinx-coroutines-core` can be run as a Java agent.
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.
11+
* `externalStaticDebugProbesTest` checks that a `ExternalStaticDebugProbes` is picked up by the kotlin stdlib
1112
* `smokeTest` builds the multiplatform test project that depends on coroutines.
1213

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

Diff for: integration-testing/build.gradle

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapperKt
2+
import org.jetbrains.kotlin.tooling.core.KotlinToolingVersionKt
3+
14
buildscript {
25

36
/*
@@ -117,6 +120,17 @@ sourceSets {
117120
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
118121
}
119122
}
123+
124+
externalStaticDebugProbesTest {
125+
kotlin
126+
compileClasspath += sourceSets.test.runtimeClasspath
127+
runtimeClasspath += sourceSets.test.runtimeClasspath
128+
129+
dependencies {
130+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
131+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
132+
}
133+
}
120134
}
121135

122136
compileDebugAgentTestKotlin {
@@ -162,12 +176,19 @@ task coreAgentTest(type: Test) {
162176
classpath = sourceSet.runtimeClasspath
163177
}
164178

179+
task externalStaticDebugProbesTest(type: Test) {
180+
enabled = KotlinPluginWrapperKt.getKotlinToolingVersion(project) >= KotlinToolingVersionKt.KotlinToolingVersion("2.1.255-SNAPSHOT")
181+
def sourceSet = sourceSets.externalStaticDebugProbesTest
182+
testClassesDirs = sourceSet.output.classesDirs
183+
classpath = sourceSet.runtimeClasspath
184+
}
185+
165186
compileTestKotlin {
166187
kotlinOptions.jvmTarget = "1.8"
167188
}
168189

169190
check {
170-
dependsOn([jvmCoreTest, debugDynamicAgentTest, mavenTest, debugAgentTest, coreAgentTest, 'smokeTest:build'])
191+
dependsOn([jvmCoreTest, debugDynamicAgentTest, mavenTest, debugAgentTest, coreAgentTest, externalStaticDebugProbesTest, 'smokeTest:build'])
171192
}
172193
compileKotlin {
173194
kotlinOptions {
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,21 @@
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+
println("Hello")
12+
runBlocking {
13+
val baos = ByteArrayOutputStream()
14+
ExternalStaticDebugProbes.dumpCoroutines(PrintStream(baos))
15+
// if the agent works, then dumps should contain something,
16+
// at least the fact that this test is running.
17+
val dump = baos.toString()
18+
Assert.assertTrue(dump, dump.contains("testDumpCoroutines"))
19+
}
20+
}
21+
}

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/DebugCoroutineInfo {
915923
public final fun getContext ()Lkotlin/coroutines/CoroutineContext;
916924
public final fun getCreationStackTrace ()Ljava/util/List;
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)