Skip to content

Commit 115a139

Browse files
authored
Debug improvements (#900)
Debug improvements * Rename printHierarchy to printJob and hierarchyToString to jobToString, introduce corresponding methods for scope to simplify scopes debugging * Remove CoroutineState.job in favor of CoroutineState.jobOrNull * Use runCatching in Continuation.toDebugString to avoid crashes on some Androids with R8 issue Addresses #858
1 parent 876e9ba commit 115a139

File tree

8 files changed

+49
-37
lines changed

8 files changed

+49
-37
lines changed

binary-compatibility-validator/reference-public-api/kotlinx-coroutines-debug.txt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ public final class kotlinx/coroutines/debug/CoroutineState {
55
public fun equals (Ljava/lang/Object;)Z
66
public final fun getContinuation ()Lkotlin/coroutines/Continuation;
77
public final fun getCreationStackTrace ()Ljava/util/List;
8-
public final fun getJob ()Lkotlinx/coroutines/Job;
98
public final fun getJobOrNull ()Lkotlinx/coroutines/Job;
109
public final fun getState ()Lkotlinx/coroutines/debug/State;
1110
public fun hashCode ()I
@@ -19,10 +18,13 @@ public final class kotlinx/coroutines/debug/DebugProbes {
1918
public static synthetic fun dumpCoroutines$default (Lkotlinx/coroutines/debug/DebugProbes;Ljava/io/PrintStream;ILjava/lang/Object;)V
2019
public final fun dumpCoroutinesState ()Ljava/util/List;
2120
public final fun getSanitizeStackTraces ()Z
22-
public final fun hierarchyToString (Lkotlinx/coroutines/Job;)Ljava/lang/String;
2321
public final fun install ()V
24-
public final fun printHierarchy (Lkotlinx/coroutines/Job;Ljava/io/PrintStream;)V
25-
public static synthetic fun printHierarchy$default (Lkotlinx/coroutines/debug/DebugProbes;Lkotlinx/coroutines/Job;Ljava/io/PrintStream;ILjava/lang/Object;)V
22+
public final fun jobToString (Lkotlinx/coroutines/Job;)Ljava/lang/String;
23+
public final fun printJob (Lkotlinx/coroutines/Job;Ljava/io/PrintStream;)V
24+
public static synthetic fun printJob$default (Lkotlinx/coroutines/debug/DebugProbes;Lkotlinx/coroutines/Job;Ljava/io/PrintStream;ILjava/lang/Object;)V
25+
public final fun printScope (Lkotlinx/coroutines/CoroutineScope;Ljava/io/PrintStream;)V
26+
public static synthetic fun printScope$default (Lkotlinx/coroutines/debug/DebugProbes;Lkotlinx/coroutines/CoroutineScope;Ljava/io/PrintStream;ILjava/lang/Object;)V
27+
public final fun scopeToString (Lkotlinx/coroutines/CoroutineScope;)Ljava/lang/String;
2628
public final fun setSanitizeStackTraces (Z)V
2729
public final fun uninstall ()V
2830
public final fun withDebugProbes (Lkotlin/jvm/functions/Function0;)V

core/kotlinx-coroutines-core/src/Debug.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ internal actual val Any.hexAddress: String
6161

6262
internal actual fun Continuation<*>.toDebugString(): String = when (this) {
6363
is DispatchedContinuation -> toString()
64-
else -> "$this@$hexAddress"
64+
// Workaround for #858
65+
else -> kotlin.runCatching { "$this@$hexAddress" }.getOrElse { "${this::class.java.name}@$hexAddress" }
6566
}
6667

6768
internal actual val Any.classSimpleName: String get() = this::class.java.simpleName

core/kotlinx-coroutines-core/test/CancellableContinuationJvmTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class CancellableContinuationJvmTest : TestBase() {
1616
private suspend fun checkToString() {
1717
suspendCancellableCoroutine<Unit> {
1818
it.resume(Unit)
19-
assertTrue(it.toString().contains("kotlinx/coroutines/CancellableContinuationJvmTest.checkToString(CancellableContinuationJvmTest.kt:16"))
19+
assertTrue(it.toString().contains("kotlinx/coroutines/CancellableContinuationJvmTest.checkToString(CancellableContinuationJvmTest.kt"))
2020
}
2121
}
2222
}

core/kotlinx-coroutines-debug/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Call to [DebugProbes.install] installs debug agent via ByteBuddy and starts to s
1111
After that, you can use [DebugProbes.dumpCoroutines] to print all active (suspended or running) coroutines, including their state, creation and
1212
suspension stacktraces.
1313
Additionally, it is possible to process the list of such coroutines via [DebugProbes.dumpCoroutinesState] or dump isolated parts
14-
of coroutines hierarchy referenced by a [Job] instance using [DebugProbes.printHierarchy].
14+
of coroutines hierarchy referenced by a [Job] instance using [DebugProbes.printScope] or [DebugProbes.printJob].
1515

1616
### Using as JVM agent
1717

@@ -59,7 +59,7 @@ fun main(args: Array<String>) = runBlocking {
5959
DebugProbes.dumpCoroutines()
6060

6161
println("\nDumping only deferred")
62-
DebugProbes.printHierarchy(deferred)
62+
DebugProbes.printJob(deferred)
6363
}
6464
```
6565

@@ -116,5 +116,6 @@ Do not use this module in production environment and do not rely on the format o
116116
[DebugProbes.install]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/install.html
117117
[DebugProbes.dumpCoroutines]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/dump-coroutines.html
118118
[DebugProbes.dumpCoroutinesState]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/dump-coroutines-state.html
119-
[DebugProbes.printHierarchy]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/print-hierarchy.html
119+
[DebugProbes.printScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/print-scope.html
120+
[DebugProbes.printJob]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/print-job.html
120121
<!--- END -->

core/kotlinx-coroutines-debug/src/CoroutineState.kt

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,14 @@ public data class CoroutineState internal constructor(
2121
@JvmField internal val sequenceNumber: Long
2222
) {
2323

24-
/**
25-
* [Job] associated with a current coroutine or [IllegalStateException] otherwise.
26-
* May be later used in [DebugProbes.printHierarchy]
27-
*/
28-
public val job: Job get() = continuation.context[Job] ?: error("Continuation $continuation does not have a job")
29-
3024
/**
3125
* [Job] associated with a current coroutine or null.
32-
* May be later used in [DebugProbes.printHierarchy]
26+
* May be later used in [DebugProbes.printJob].
3327
*/
3428
public val jobOrNull: Job? get() = continuation.context[Job]
3529

3630
/**
37-
* Creation stacktrace of coroutine
31+
* Creation stacktrace of the coroutine.
3832
*/
3933
public val creationStackTrace: List<StackTraceElement> get() = creationStackTrace()
4034

@@ -80,7 +74,7 @@ public data class CoroutineState internal constructor(
8074
/**
8175
* Last observed stacktrace of the coroutine captured on its suspension or resumption point.
8276
* It means that for [running][State.RUNNING] coroutines resulting stacktrace is inaccurate and
83-
* reflects stacktrace of the resumption point, not the actual current stacktrace
77+
* reflects stacktrace of the resumption point, not the actual current stacktrace.
8478
*/
8579
public fun lastObservedStackTrace(): List<StackTraceElement> {
8680
var frame: CoroutineStackFrame? = lastObservedFrame ?: return emptyList()
@@ -99,15 +93,15 @@ public data class CoroutineState internal constructor(
9993
*/
10094
public enum class State {
10195
/**
102-
* Created, but not yet started
96+
* Created, but not yet started.
10397
*/
10498
CREATED,
10599
/**
106-
* Started and running
100+
* Started and running.
107101
*/
108102
RUNNING,
109103
/**
110-
* Suspended
104+
* Suspended.
111105
*/
112106
SUSPENDED
113107
}

core/kotlinx-coroutines-debug/src/DebugProbes.kt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,28 @@ public object DebugProbes {
7272
* Returns string representation of the coroutines [job] hierarchy with additional debug information.
7373
* Hierarchy is printed from the [job] as a root transitively to all children.
7474
*/
75-
public fun hierarchyToString(job: Job): String = DebugProbesImpl.hierarchyToString(job)
75+
public fun jobToString(job: Job): String = DebugProbesImpl.hierarchyToString(job)
7676

7777
/**
78-
* Prints [job] hierarchy representation from [hierarchyToString] to the given [out].
78+
* Returns string representation of all coroutines launched within the given [scope].
79+
* Throws [IllegalStateException] if the scope has no a job in it.
7980
*/
80-
public fun printHierarchy(job: Job, out: PrintStream = System.out) =
81+
public fun scopeToString(scope: CoroutineScope): String =
82+
jobToString(scope.coroutineContext[Job] ?: error("Job is not present in the scope"))
83+
84+
/**
85+
* Prints [job] hierarchy representation from [jobToString] to the given [out].
86+
*/
87+
public fun printJob(job: Job, out: PrintStream = System.out): Unit =
8188
out.println(DebugProbesImpl.hierarchyToString(job))
8289

90+
/**
91+
* Prints all coroutines launched within the given [scope].
92+
* Throws [IllegalStateException] if the scope has no a job in it.
93+
*/
94+
public fun printScope(scope: CoroutineScope, out: PrintStream = System.out): Unit =
95+
printJob(scope.coroutineContext[Job] ?: error("Job is not present in the scope"), out)
96+
8397
/**
8498
* Returns all existing coroutine states.
8599
* The resulting collection represents a consistent snapshot of all existing coroutines at the moment of invocation.

core/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class CoroutinesDumpTest : TestBase() {
5050
"\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:99)\n")
5151

5252
val found = DebugProbes.dumpCoroutinesState().single { it.jobOrNull === deferred }
53-
assertSame(deferred, found.job)
53+
assertSame(deferred, found.jobOrNull)
5454
runBlocking { deferred.cancelAndJoin() }
5555
}
5656

core/kotlinx-coroutines-debug/test/HierarchyToStringTest.kt renamed to core/kotlinx-coroutines-debug/test/ToStringTest.kt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import org.junit.Test
1111
import kotlin.coroutines.*
1212
import kotlin.test.*
1313

14-
class HierarchyToStringTest : TestBase() {
14+
class ToStringTest : TestBase() {
1515

1616
@Before
1717
fun setUp() {
@@ -34,9 +34,9 @@ class HierarchyToStringTest : TestBase() {
3434
val tab = '\t'
3535
val expectedString = """
3636
"coroutine#2":StandaloneCoroutine{Completing}
37-
$tab"foo#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line HierarchyToStringTest${'$'}launchHierarchy${'$'}1${'$'}1.invokeSuspend(HierarchyToStringTest.kt:30)
38-
$tab"coroutine#4":ActorCoroutine{Active}, continuation is SUSPENDED at line HierarchyToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}1.invokeSuspend(HierarchyToStringTest.kt:40)
39-
$tab$tab"coroutine#5":StandaloneCoroutine{Active}, continuation is SUSPENDED at line HierarchyToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}job$1.invokeSuspend(HierarchyToStringTest.kt:37)
37+
$tab"foo#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}1.invokeSuspend(ToStringTest.kt:30)
38+
$tab"coroutine#4":ActorCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}1.invokeSuspend(ToStringTest.kt:40)
39+
$tab$tab"coroutine#5":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}job$1.invokeSuspend(ToStringTest.kt:37)
4040
""".trimIndent()
4141

4242
checkHierarchy(isCompleting = true, expectedString = expectedString)
@@ -46,22 +46,22 @@ class HierarchyToStringTest : TestBase() {
4646
fun testActiveHierarchy() = runBlocking {
4747
val tab = '\t'
4848
val expectedString = """
49-
"coroutine#2":StandaloneCoroutine{Active}, continuation is SUSPENDED at line HierarchyToStringTest${'$'}launchHierarchy${'$'}1.invokeSuspend(HierarchyToStringTest.kt:94)
50-
$tab"foo#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line HierarchyToStringTest${'$'}launchHierarchy${'$'}1${'$'}1.invokeSuspend(HierarchyToStringTest.kt:30)
51-
$tab"coroutine#4":ActorCoroutine{Active}, continuation is SUSPENDED at line HierarchyToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}1.invokeSuspend(HierarchyToStringTest.kt:40)
52-
$tab$tab"coroutine#5":StandaloneCoroutine{Active}, continuation is SUSPENDED at line HierarchyToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}job$1.invokeSuspend(HierarchyToStringTest.kt:37)
49+
"coroutine#2":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1.invokeSuspend(ToStringTest.kt:94)
50+
$tab"foo#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}1.invokeSuspend(ToStringTest.kt:30)
51+
$tab"coroutine#4":ActorCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}1.invokeSuspend(ToStringTest.kt:40)
52+
$tab$tab"coroutine#5":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchHierarchy${'$'}1${'$'}2${'$'}job$1.invokeSuspend(ToStringTest.kt:37)
5353
""".trimIndent()
5454
checkHierarchy(isCompleting = false, expectedString = expectedString)
5555
}
5656

5757
private suspend fun CoroutineScope.checkHierarchy(isCompleting: Boolean, expectedString: String) {
5858
val root = launchHierarchy(isCompleting)
5959
repeat(4) { yield() }
60+
val expected = expectedString.trimStackTrace().trimPackage()
6061
expect(6)
61-
assertEquals(
62-
expectedString.trimStackTrace().trimPackage(),
63-
DebugProbes.hierarchyToString(root).trimEnd().trimStackTrace().trimPackage()
64-
)
62+
assertEquals(expected, DebugProbes.jobToString(root).trimEnd().trimStackTrace().trimPackage())
63+
assertEquals(expected, DebugProbes.scopeToString(CoroutineScope(root)).trimEnd().trimStackTrace().trimPackage())
64+
6565
root.cancel()
6666
root.join()
6767
finish(7)

0 commit comments

Comments
 (0)