Skip to content

Commit 8d506b3

Browse files
authored
Make SupervisorCoroutine implement ScopeCoroutine
* Implement ScopeCoroutine in SupervisorCoroutine * Do not indent scoped coroutines in debug strings Fixes #915 Fixes #916
1 parent 2bdd460 commit 8d506b3

File tree

5 files changed

+72
-26
lines changed

5 files changed

+72
-26
lines changed

kotlinx-coroutines-core/common/src/AbstractCoroutine.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ public abstract class AbstractCoroutine<in T>(
3737
/**
3838
* Context of the parent coroutine.
3939
*/
40-
4140
@JvmField
4241
protected val parentContext: CoroutineContext,
4342
active: Boolean = true
@@ -53,7 +52,7 @@ public abstract class AbstractCoroutine<in T>(
5352
*/
5453
public override val coroutineContext: CoroutineContext get() = context
5554

56-
override val isActive: Boolean get() = super<JobSupport>.isActive
55+
override val isActive: Boolean get() = super.isActive
5756

5857
/**
5958
* Initializes parent job from the `parentContext` of this coroutine that was passed to it during construction.

kotlinx-coroutines-core/common/src/Supervisor.kt

+5-13
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
package kotlinx.coroutines
88

9+
import kotlinx.coroutines.internal.*
910
import kotlinx.coroutines.intrinsics.*
1011
import kotlin.coroutines.*
1112
import kotlin.coroutines.intrinsics.*
@@ -56,18 +57,9 @@ private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
5657
override fun childCancelled(cause: Throwable): Boolean = false
5758
}
5859

59-
private class SupervisorCoroutine<R>(
60-
parentContext: CoroutineContext,
61-
@JvmField val uCont: Continuation<R>
62-
) : AbstractCoroutine<R>(parentContext, true) {
63-
override val defaultResumeMode: Int get() = MODE_DIRECT
60+
private class SupervisorCoroutine<in T>(
61+
context: CoroutineContext,
62+
uCont: Continuation<T>
63+
) : ScopeCoroutine<T>(context, uCont) {
6464
override fun childCancelled(cause: Throwable): Boolean = false
65-
66-
@Suppress("UNCHECKED_CAST")
67-
internal override fun onCompletionInternal(state: Any?, mode: Int, suppressed: Boolean) {
68-
if (state is CompletedExceptionally)
69-
uCont.resumeUninterceptedWithExceptionMode(state.cause, mode)
70-
else
71-
uCont.resumeUninterceptedMode(state as R, mode)
72-
}
7365
}

kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ class StackTraceRecoveryTest : TestBase() {
180180
"\t(Coroutine boundary)\n" +
181181
"\tat kotlinx.coroutines.DeferredCoroutine.await\$suspendImpl(Builders.common.kt:99)\n" +
182182
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.innerMethod(StackTraceRecoveryTest.kt:158)\n" +
183+
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$outerScopedMethod\$2\$1.invokeSuspend(StackTraceRecoveryTest.kt:193)\n" +
183184
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$outerScopedMethod\$2.invokeSuspend(StackTraceRecoveryTest.kt:151)\n" +
184185
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCoroutineScope\$1.invokeSuspend(StackTraceRecoveryTest.kt:141)\n",
185186
"Caused by: kotlinx.coroutines.RecoverableTestException\n" +
@@ -217,7 +218,10 @@ class StackTraceRecoveryTest : TestBase() {
217218
}
218219

219220
private suspend fun outerScopedMethod(deferred: Deferred<Nothing>, vararg traces: String) = coroutineScope {
220-
innerMethod(deferred, *traces)
221+
supervisorScope {
222+
innerMethod(deferred, *traces)
223+
assertTrue(true)
224+
}
221225
assertTrue(true)
222226
}
223227

kotlinx-coroutines-debug/src/internal/DebugProbesImpl.kt

+16-8
Original file line numberDiff line numberDiff line change
@@ -76,24 +76,32 @@ internal object DebugProbesImpl {
7676

7777
private fun Job.build(map: Map<Job, CoroutineState>, builder: StringBuilder, indent: String) {
7878
val state = map[this]
79-
builder.append(indent)
80-
@Suppress("DEPRECATION_ERROR")
81-
val str = if (this !is JobSupport) toString() else toDebugString()
82-
if (state == null) {
79+
val newIndent: String
80+
if (state == null) { // Append coroutine without stacktrace
81+
// Do not print scoped coroutines and do not increase indentation level
8382
@Suppress("INVISIBLE_REFERENCE")
84-
if (this !is kotlinx.coroutines.internal.ScopeCoroutine<*>) { // Do not print scoped coroutines
85-
builder.append("$str\n")
83+
if (this !is kotlinx.coroutines.internal.ScopeCoroutine<*>) {
84+
builder.append("$indent$debugString\n")
85+
newIndent = indent + "\t"
86+
} else {
87+
newIndent = indent
8688
}
8789
} else {
90+
// Append coroutine with its last stacktrace element
8891
val element = state.lastObservedStackTrace().firstOrNull()
8992
val contState = state.state
90-
builder.append("$str, continuation is $contState at line $element\n")
93+
builder.append("$indent$debugString, continuation is $contState at line $element\n")
94+
newIndent = indent + "\t"
9195
}
96+
// Append children with new indent
9297
for (child in children) {
93-
child.build(map, builder, indent + "\t")
98+
child.build(map, builder, newIndent)
9499
}
95100
}
96101

102+
@Suppress("DEPRECATION_ERROR") // JobSupport
103+
private val Job.debugString: String get() = if (this is JobSupport) toDebugString() else toString()
104+
97105
@Synchronized
98106
public fun dumpCoroutinesState(): List<CoroutineState> {
99107
check(isInstalled) { "Debug probes are not installed" }

kotlinx-coroutines-debug/test/ToStringTest.kt

+45-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,50 @@ class ToStringTest : TestBase() {
2929
}
3030
}
3131

32+
33+
private suspend fun CoroutineScope.launchNestedScopes(): Job {
34+
return launch {
35+
expect(1)
36+
coroutineScope {
37+
expect(2)
38+
launchDelayed()
39+
40+
supervisorScope {
41+
expect(3)
42+
launchDelayed()
43+
}
44+
}
45+
}
46+
}
47+
48+
private fun CoroutineScope.launchDelayed(): Job {
49+
return launch {
50+
delay(Long.MAX_VALUE)
51+
}
52+
}
53+
54+
@Test
55+
fun testPrintHierarchyWithScopes() = runBlocking {
56+
val tab = '\t'
57+
val expectedString = """
58+
"coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchNestedScopes$2$1.invokeSuspend(ToStringTest.kt)
59+
$tab"coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchDelayed$1.invokeSuspend(ToStringTest.kt)
60+
$tab"coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchDelayed$1.invokeSuspend(ToStringTest.kt)
61+
""".trimIndent()
62+
63+
val job = launchNestedScopes()
64+
try {
65+
repeat(5) { yield() }
66+
val expected = expectedString.trimStackTrace().trimPackage()
67+
expect(4)
68+
assertEquals(expected, DebugProbes.jobToString(job).trimEnd().trimStackTrace().trimPackage())
69+
assertEquals(expected, DebugProbes.scopeToString(CoroutineScope(job)).trimEnd().trimStackTrace().trimPackage())
70+
} finally {
71+
finish(5)
72+
job.cancelAndJoin()
73+
}
74+
}
75+
3276
@Test
3377
fun testCompletingHierarchy() = runBlocking {
3478
val tab = '\t'
@@ -62,8 +106,7 @@ class ToStringTest : TestBase() {
62106
assertEquals(expected, DebugProbes.jobToString(root).trimEnd().trimStackTrace().trimPackage())
63107
assertEquals(expected, DebugProbes.scopeToString(CoroutineScope(root)).trimEnd().trimStackTrace().trimPackage())
64108

65-
root.cancel()
66-
root.join()
109+
root.cancelAndJoin()
67110
finish(7)
68111
}
69112

0 commit comments

Comments
 (0)