Skip to content

Commit 04b1e50

Browse files
committed
Reduce public API surface, introduce JDWP-specific API
1 parent d1a035a commit 04b1e50

File tree

9 files changed

+160
-111
lines changed

9 files changed

+160
-111
lines changed

kotlinx-coroutines-core/api/kotlinx-coroutines-core.api

-39
Original file line numberDiff line numberDiff line change
@@ -832,45 +832,6 @@ public final class kotlinx/coroutines/channels/ValueOrClosed {
832832
public final synthetic fun unbox-impl ()Ljava/lang/Object;
833833
}
834834

835-
public final class kotlinx/coroutines/debug/CoroutineInfo {
836-
public final fun copy ()Lkotlinx/coroutines/debug/CoroutineInfo;
837-
public final fun getContext ()Lkotlin/coroutines/CoroutineContext;
838-
public final fun getCreationStackTrace ()Ljava/util/List;
839-
public final fun getJob ()Lkotlinx/coroutines/Job;
840-
public final fun getState ()Lkotlinx/coroutines/debug/State;
841-
public final fun lastObservedStackTrace ()Ljava/util/List;
842-
public fun toString ()Ljava/lang/String;
843-
}
844-
845-
public final class kotlinx/coroutines/debug/DebugProbes {
846-
public static final field INSTANCE Lkotlinx/coroutines/debug/DebugProbes;
847-
public final fun dumpCoroutines (Ljava/io/PrintStream;)V
848-
public static synthetic fun dumpCoroutines$default (Lkotlinx/coroutines/debug/DebugProbes;Ljava/io/PrintStream;ILjava/lang/Object;)V
849-
public final fun dumpCoroutinesInfo ()Ljava/util/List;
850-
public final fun getEnableCreationStackTraces ()Z
851-
public final fun getSanitizeStackTraces ()Z
852-
public final fun install ()V
853-
public final fun isInstalled ()Z
854-
public final fun jobToString (Lkotlinx/coroutines/Job;)Ljava/lang/String;
855-
public final fun printJob (Lkotlinx/coroutines/Job;Ljava/io/PrintStream;)V
856-
public static synthetic fun printJob$default (Lkotlinx/coroutines/debug/DebugProbes;Lkotlinx/coroutines/Job;Ljava/io/PrintStream;ILjava/lang/Object;)V
857-
public final fun printScope (Lkotlinx/coroutines/CoroutineScope;Ljava/io/PrintStream;)V
858-
public static synthetic fun printScope$default (Lkotlinx/coroutines/debug/DebugProbes;Lkotlinx/coroutines/CoroutineScope;Ljava/io/PrintStream;ILjava/lang/Object;)V
859-
public final fun scopeToString (Lkotlinx/coroutines/CoroutineScope;)Ljava/lang/String;
860-
public final fun setEnableCreationStackTraces (Z)V
861-
public final fun setSanitizeStackTraces (Z)V
862-
public final fun uninstall ()V
863-
public final fun withDebugProbes (Lkotlin/jvm/functions/Function0;)V
864-
}
865-
866-
public final class kotlinx/coroutines/debug/State : java/lang/Enum {
867-
public static final field CREATED Lkotlinx/coroutines/debug/State;
868-
public static final field RUNNING Lkotlinx/coroutines/debug/State;
869-
public static final field SUSPENDED Lkotlinx/coroutines/debug/State;
870-
public static fun valueOf (Ljava/lang/String;)Lkotlinx/coroutines/debug/State;
871-
public static fun values ()[Lkotlinx/coroutines/debug/State;
872-
}
873-
874835
public synthetic class kotlinx/coroutines/debug/internal/DebugProbesImplSequenceNumberRefVolatile {
875836
public fun <init> (J)V
876837
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines.debug.internal
6+
7+
import kotlin.coroutines.*
8+
import kotlin.coroutines.jvm.internal.*
9+
10+
internal const val CREATED = "CREATED"
11+
internal const val RUNNING = "RUNNING"
12+
internal const val SUSPENDED = "SUSPENDED"
13+
14+
internal class DebugCoroutineInfo(
15+
public val context: CoroutineContext,
16+
private val creationStackBottom: CoroutineStackFrame?,
17+
@JvmField internal val sequenceNumber: Long
18+
) {
19+
20+
public val creationStackTrace: List<StackTraceElement> get() = creationStackTrace()
21+
22+
/**
23+
* Last observed state of the coroutine.
24+
* Can be CREATED, RUNNING, SUSPENDED.
25+
*/
26+
public val state: String get() = _state
27+
private var _state: String = CREATED
28+
29+
@JvmField
30+
internal var lastObservedThread: Thread? = null
31+
@JvmField
32+
internal var lastObservedFrame: CoroutineStackFrame? = null
33+
34+
public fun copy(): DebugCoroutineInfo = DebugCoroutineInfo(
35+
context,
36+
creationStackBottom,
37+
sequenceNumber
38+
).also {
39+
it._state = _state
40+
it.lastObservedFrame = lastObservedFrame
41+
it.lastObservedThread = lastObservedThread
42+
}
43+
44+
/**
45+
* Last observed stacktrace of the coroutine captured on its suspension or resumption point.
46+
* It means that for [running][State.RUNNING] coroutines resulting stacktrace is inaccurate and
47+
* reflects stacktrace of the resumption point, not the actual current stacktrace.
48+
*/
49+
public fun lastObservedStackTrace(): List<StackTraceElement> {
50+
var frame: CoroutineStackFrame? = lastObservedFrame ?: return emptyList()
51+
val result = ArrayList<StackTraceElement>()
52+
while (frame != null) {
53+
frame.getStackTraceElement()?.let { result.add(it) }
54+
frame = frame.callerFrame
55+
}
56+
return result
57+
}
58+
59+
private fun creationStackTrace(): List<StackTraceElement> {
60+
val bottom = creationStackBottom ?: return emptyList()
61+
// Skip "Coroutine creation stacktrace" frame
62+
return sequence<StackTraceElement> { yieldFrames(bottom.callerFrame) }.toList()
63+
}
64+
65+
private tailrec suspend fun SequenceScope<StackTraceElement>.yieldFrames(frame: CoroutineStackFrame?) {
66+
if (frame == null) return
67+
frame.getStackTraceElement()?.let { yield(it) }
68+
val caller = frame.callerFrame
69+
if (caller != null) {
70+
yieldFrames(caller)
71+
}
72+
}
73+
74+
internal fun updateState(state: String, frame: Continuation<*>) {
75+
// Propagate only duplicating transitions to running for KT-29997
76+
if (_state == state && state == SUSPENDED && lastObservedFrame != null) return
77+
_state = state
78+
lastObservedFrame = frame as? CoroutineStackFrame
79+
lastObservedThread = if (state == RUNNING) {
80+
Thread.currentThread()
81+
} else {
82+
null
83+
}
84+
}
85+
86+
override fun toString(): String = "DebugCoroutineInfo(state=$state,context=$context)"
87+
}

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

+19-23
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package kotlinx.coroutines.debug.internal
66

77
import kotlinx.atomicfu.*
88
import kotlinx.coroutines.*
9-
import kotlinx.coroutines.debug.*
109
import java.io.*
1110
import java.text.*
1211
import java.util.*
@@ -18,11 +17,6 @@ import kotlin.coroutines.*
1817
import kotlin.coroutines.jvm.internal.*
1918
import kotlinx.coroutines.internal.artificialFrame as createArtificialFrame // IDEA bug workaround
2019

21-
/**
22-
* Mirror of [DebugProbes] with actual implementation.
23-
* [DebugProbes] are implemented with pimpl to simplify user-facing class and make it look simple and
24-
* documented.
25-
*/
2620
internal object DebugProbesImpl {
2721
private const val ARTIFICIAL_FRAME_MESSAGE = "Coroutine creation stacktrace"
2822
private val dateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
@@ -52,7 +46,7 @@ internal object DebugProbesImpl {
5246
* Then at least three RUNNING -> RUNNING transitions will occur consecutively and complexity of each is O(depth).
5347
* To avoid that quadratic complexity, we are caching lookup result for such chains in this map and update it incrementally.
5448
*/
55-
private val callerInfoCache = ConcurrentHashMap<CoroutineStackFrame, CoroutineInfo>()
49+
private val callerInfoCache = ConcurrentHashMap<CoroutineStackFrame, DebugCoroutineInfo>()
5650

5751
public fun install(): Unit = coroutineStateLock.write {
5852
if (++installations > 1) return
@@ -77,7 +71,7 @@ internal object DebugProbesImpl {
7771
}
7872
}
7973

80-
private fun Job.build(map: Map<Job, CoroutineInfo>, builder: StringBuilder, indent: String) {
74+
private fun Job.build(map: Map<Job, DebugCoroutineInfo>, builder: StringBuilder, indent: String) {
8175
val info = map[this]
8276
val newIndent: String
8377
if (info == null) { // Append coroutine without stacktrace
@@ -105,14 +99,16 @@ internal object DebugProbesImpl {
10599
@Suppress("DEPRECATION_ERROR") // JobSupport
106100
private val Job.debugString: String get() = if (this is JobSupport) toDebugString() else toString()
107101

108-
public fun dumpCoroutinesInfo(): List<CoroutineInfo> = coroutineStateLock.write {
102+
public fun dumpCoroutinesInfo(): List<DebugCoroutineInfo> = coroutineStateLock.write {
109103
check(isInstalled) { "Debug probes are not installed" }
110104
return capturedCoroutines.asSequence()
111105
.map { it.info.copy() } // Copy as CoroutineInfo can be mutated concurrently by DebugProbes
112106
.sortedBy { it.sequenceNumber }
113107
.toList()
114108
}
115109

110+
public fun dumpDebuggerInfo() = dumpCoroutinesInfo().map { DebuggerInfo(it) }
111+
116112
public fun dumpCoroutines(out: PrintStream): Unit = synchronized(out) {
117113
/*
118114
* This method synchronizes both on `out` and `this` for a reason:
@@ -134,7 +130,7 @@ internal object DebugProbesImpl {
134130
val info = owner.info
135131
val observedStackTrace = info.lastObservedStackTrace()
136132
val enhancedStackTrace = enhanceStackTraceWithThreadDump(info, observedStackTrace)
137-
val state = if (info.state == State.RUNNING && enhancedStackTrace === observedStackTrace)
133+
val state = if (info.state == RUNNING && enhancedStackTrace === observedStackTrace)
138134
"${info.state} (Last suspension stacktrace, not an actual stacktrace)"
139135
else
140136
info.state.toString()
@@ -156,17 +152,17 @@ internal object DebugProbesImpl {
156152
}
157153

158154
/**
159-
* Tries to enhance [coroutineTrace] (obtained by call to [CoroutineInfo.lastObservedStackTrace]) with
160-
* thread dump of [CoroutineInfo.lastObservedThread].
155+
* Tries to enhance [coroutineTrace] (obtained by call to [DebugCoroutineInfo.lastObservedStackTrace]) with
156+
* thread dump of [DebugCoroutineInfo.lastObservedThread].
161157
*
162158
* Returns [coroutineTrace] if enhancement was unsuccessful or the enhancement result.
163159
*/
164160
private fun enhanceStackTraceWithThreadDump(
165-
info: CoroutineInfo,
161+
info: DebugCoroutineInfo,
166162
coroutineTrace: List<StackTraceElement>
167163
): List<StackTraceElement> {
168164
val thread = info.lastObservedThread
169-
if (info.state != State.RUNNING || thread == null) return coroutineTrace
165+
if (info.state != RUNNING || thread == null) return coroutineTrace
170166
// Avoid security manager issues
171167
val actualTrace = runCatching { thread.stackTrace }.getOrNull()
172168
?: return coroutineTrace
@@ -250,13 +246,13 @@ internal object DebugProbesImpl {
250246
}
251247
}
252248

253-
internal fun probeCoroutineResumed(frame: Continuation<*>) = updateState(frame, State.RUNNING)
249+
internal fun probeCoroutineResumed(frame: Continuation<*>) = updateState(frame, RUNNING)
254250

255-
internal fun probeCoroutineSuspended(frame: Continuation<*>) = updateState(frame, State.SUSPENDED)
251+
internal fun probeCoroutineSuspended(frame: Continuation<*>) = updateState(frame, SUSPENDED)
256252

257-
private fun updateState(frame: Continuation<*>, state: State) {
253+
private fun updateState(frame: Continuation<*>, state: String) {
258254
// KT-29997 is here only since 1.3.30
259-
if (state == State.RUNNING && KotlinVersion.CURRENT.isAtLeast(1, 3, 30)) {
255+
if (state == RUNNING && KotlinVersion.CURRENT.isAtLeast(1, 3, 30)) {
260256
val stackFrame = frame as? CoroutineStackFrame ?: return
261257
updateRunningState(stackFrame, state)
262258
return
@@ -268,10 +264,10 @@ internal object DebugProbesImpl {
268264
}
269265

270266
// See comment to callerInfoCache
271-
private fun updateRunningState(frame: CoroutineStackFrame, state: State): Unit = coroutineStateLock.read {
267+
private fun updateRunningState(frame: CoroutineStackFrame, state: String): Unit = coroutineStateLock.read {
272268
if (!isInstalled) return
273269
// Lookup coroutine info in cache or by traversing stack frame
274-
val info: CoroutineInfo
270+
val info: DebugCoroutineInfo
275271
val cached = callerInfoCache.remove(frame)
276272
if (cached != null) {
277273
info = cached
@@ -293,7 +289,7 @@ internal object DebugProbesImpl {
293289
return if (caller.getStackTraceElement() != null) caller else caller.realCaller()
294290
}
295291

296-
private fun updateState(owner: CoroutineOwner<*>, frame: Continuation<*>, state: State) = coroutineStateLock.read {
292+
private fun updateState(owner: CoroutineOwner<*>, frame: Continuation<*>, state: String) = coroutineStateLock.read {
297293
if (!isInstalled) return
298294
owner.info.updateState(state, frame)
299295
}
@@ -336,7 +332,7 @@ internal object DebugProbesImpl {
336332

337333
private fun <T> createOwner(completion: Continuation<T>, frame: CoroutineStackFrame?): Continuation<T> {
338334
if (!isInstalled) return completion
339-
val info = CoroutineInfo(completion.context, frame, sequenceNumber.incrementAndGet())
335+
val info = DebugCoroutineInfo(completion.context, frame, sequenceNumber.incrementAndGet())
340336
val owner = CoroutineOwner(completion, info, frame)
341337
capturedCoroutines += owner
342338
if (!isInstalled) capturedCoroutines.clear()
@@ -360,7 +356,7 @@ internal object DebugProbesImpl {
360356
*/
361357
private class CoroutineOwner<T>(
362358
@JvmField val delegate: Continuation<T>,
363-
@JvmField val info: CoroutineInfo,
359+
@JvmField val info: DebugCoroutineInfo,
364360
private val frame: CoroutineStackFrame?
365361
) : Continuation<T> by delegate, CoroutineStackFrame {
366362

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
@file:Suppress("PropertyName", "NO_EXPLICIT_VISIBILITY_IN_API_MODE")
6+
7+
package kotlinx.coroutines.debug.internal
8+
9+
import java.io.Serializable
10+
import kotlin.coroutines.*
11+
import kotlin.coroutines.jvm.internal.*
12+
13+
internal class DebuggerInfo(source: DebugCoroutineInfo) : Serializable {
14+
public val name: String? = source.context[kotlinx.coroutines.CoroutineName]?.name
15+
public val state: String = source.state
16+
public val lastObservedThreadState = source.lastObservedThread?.state
17+
public val lastObservedThreadName = source.lastObservedThread?.name
18+
public val lastObservedStackTrace = source.lastObservedStackTrace()
19+
public val sequenceNumber = source.sequenceNumber
20+
}

kotlinx-coroutines-core/jvm/src/debug/internal/DynamicAttach.kt

+1
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ private fun getFunction(clzName: String): Function0<Unit>? = runCatching {
1616
val ctor = clz.constructors[0]
1717
ctor.newInstance() as Function0<Unit>
1818
}.getOrNull()
19+

kotlinx-coroutines-debug/api/kotlinx-coroutines-debug.api

+17
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
public final class kotlinx/coroutines/debug/CoroutineInfo {
2+
public final fun getContext ()Lkotlin/coroutines/CoroutineContext;
3+
public final fun getCreationStackTrace ()Ljava/util/List;
4+
public final fun getJob ()Lkotlinx/coroutines/Job;
5+
public final fun getState ()Lkotlinx/coroutines/debug/State;
6+
public final fun lastObservedStackTrace ()Ljava/util/List;
7+
public fun toString ()Ljava/lang/String;
8+
}
9+
110
public final class kotlinx/coroutines/debug/CoroutinesBlockHoundIntegration : reactor/blockhound/integration/BlockHoundIntegration {
211
public fun <init> ()V
312
public fun applyTo (Lreactor/blockhound/BlockHound$Builder;)V
@@ -24,6 +33,14 @@ public final class kotlinx/coroutines/debug/DebugProbes {
2433
public final fun withDebugProbes (Lkotlin/jvm/functions/Function0;)V
2534
}
2635

36+
public final class kotlinx/coroutines/debug/State : java/lang/Enum {
37+
public static final field CREATED Lkotlinx/coroutines/debug/State;
38+
public static final field RUNNING Lkotlinx/coroutines/debug/State;
39+
public static final field SUSPENDED Lkotlinx/coroutines/debug/State;
40+
public static fun valueOf (Ljava/lang/String;)Lkotlinx/coroutines/debug/State;
41+
public static fun values ()[Lkotlinx/coroutines/debug/State;
42+
}
43+
2744
public final class kotlinx/coroutines/debug/junit4/CoroutinesTimeout : org/junit/rules/TestRule {
2845
public static final field Companion Lkotlinx/coroutines/debug/junit4/CoroutinesTimeout$Companion;
2946
public fun <init> (JZ)V

0 commit comments

Comments
 (0)