@@ -13,6 +13,7 @@ import java.io.*
13
13
import java.text.*
14
14
import java.util.*
15
15
import kotlin.collections.ArrayList
16
+ import kotlin.collections.HashMap
16
17
import kotlin.coroutines.*
17
18
import kotlin.coroutines.jvm.internal.*
18
19
import kotlinx.coroutines.internal.artificialFrame as createArtificialFrame // IDEA bug workaround
@@ -25,7 +26,7 @@ import kotlinx.coroutines.internal.artificialFrame as createArtificialFrame // I
25
26
internal object DebugProbesImpl {
26
27
private const val ARTIFICIAL_FRAME_MESSAGE = " Coroutine creation stacktrace"
27
28
private val dateFormat = SimpleDateFormat (" yyyy/MM/dd HH:mm:ss" )
28
- private val capturedCoroutines = WeakHashMap < ArtificialStackFrame <* >, CoroutineState > ( )
29
+ private val capturedCoroutines: MutableSet < CoroutineOwner <* >> = Collections .newSetFromMap( HashMap () )
29
30
@Volatile
30
31
private var installations = 0
31
32
private val isInstalled: Boolean get() = installations > 0
@@ -77,8 +78,8 @@ internal object DebugProbesImpl {
77
78
public fun hierarchyToString (job : Job ): String {
78
79
check(isInstalled) { " Debug probes are not installed" }
79
80
val jobToStack = capturedCoroutines
80
- .filterKeys { it.delegate.context[Job ] != null }
81
- .mapKeys { it.key. delegate.context[Job ]!! }
81
+ .filter { it.delegate.context[Job ] != null }
82
+ .associateBy( { it.delegate.context[Job ]!! }, {it.state})
82
83
return buildString {
83
84
job.build(jobToStack, this , " " )
84
85
}
@@ -115,8 +116,8 @@ internal object DebugProbesImpl {
115
116
@Synchronized
116
117
public fun dumpCoroutinesState (): List <CoroutineState > {
117
118
check(isInstalled) { " Debug probes are not installed" }
118
- return capturedCoroutines.entries. asSequence()
119
- .map { CoroutineState (it.key. delegate, it.value ) }
119
+ return capturedCoroutines.asSequence()
120
+ .map { CoroutineState (it.delegate, it.state ) }
120
121
.sortedBy { it.sequenceNumber }
121
122
.toList()
122
123
}
@@ -133,19 +134,20 @@ internal object DebugProbesImpl {
133
134
append(" Coroutines dump ${dateFormat.format(System .currentTimeMillis())} " )
134
135
capturedCoroutines
135
136
.asSequence()
136
- .sortedBy { it.value.sequenceNumber }
137
- .forEach { (key, value) ->
138
- val observedStackTrace = value.lastObservedStackTrace()
139
- val enhancedStackTrace = enhanceStackTraceWithThreadDump(value, observedStackTrace)
140
- val state = if (value.state == State .RUNNING && enhancedStackTrace == = observedStackTrace)
141
- " ${value.state} (Last suspension stacktrace, not an actual stacktrace)"
137
+ .sortedBy { it.state.sequenceNumber }
138
+ .forEach { owner ->
139
+ val coroutineState = owner.state
140
+ val observedStackTrace = coroutineState.lastObservedStackTrace()
141
+ val enhancedStackTrace = enhanceStackTraceWithThreadDump(coroutineState, observedStackTrace)
142
+ val state = if (coroutineState.state == State .RUNNING && enhancedStackTrace == = observedStackTrace)
143
+ " ${coroutineState.state} (Last suspension stacktrace, not an actual stacktrace)"
142
144
else
143
- value .state.toString()
145
+ coroutineState .state.toString()
144
146
145
- append(" \n\n Coroutine $key , state: $state " )
147
+ append(" \n\n Coroutine ${owner.delegate} , state: $state " )
146
148
if (observedStackTrace.isEmpty()) {
147
149
append(" \n\t at ${createArtificialFrame(ARTIFICIAL_FRAME_MESSAGE )} " )
148
- printStackTrace(value .creationStackTrace)
150
+ printStackTrace(coroutineState .creationStackTrace)
149
151
} else {
150
152
printStackTrace(enhancedStackTrace)
151
153
}
@@ -265,14 +267,14 @@ internal object DebugProbesImpl {
265
267
}
266
268
267
269
// Find ArtificialStackFrame of the coroutine
268
- val owner = frame.owner()
270
+ val owner = frame.owner() ? : return
269
271
updateState(owner, frame, state)
270
272
}
271
273
272
274
@Synchronized // See comment to stateCache
273
275
private fun updateRunningState (continuation : Continuation <* >, state : State ) {
274
276
val frame = continuation as ? CoroutineStackFrame ? : return
275
- val coroutineState = stateCache.remove(frame) ? : capturedCoroutines[ frame.owner()] ? : return
277
+ val coroutineState = stateCache.remove(frame) ? : frame.owner()?.state ? : return
276
278
// Do not cache states for proxy-classes such as ScopeCoroutines
277
279
val caller = frame.realCaller()
278
280
if (caller != null ) {
@@ -289,16 +291,16 @@ internal object DebugProbesImpl {
289
291
}
290
292
291
293
@Synchronized
292
- private fun updateState (owner : ArtificialStackFrame <* >? , frame : Continuation <* >, state : State ) {
293
- val coroutineState = capturedCoroutines[ owner] ? : return
294
+ private fun updateState (owner : CoroutineOwner <* >, frame : Continuation <* >, state : State ) {
295
+ val coroutineState = owner.state
294
296
coroutineState.updateState(state, frame)
295
297
}
296
298
297
- private fun Continuation <* >.owner (): ArtificialStackFrame <* >? =
299
+ private fun Continuation <* >.owner (): CoroutineOwner <* >? =
298
300
(this as ? CoroutineStackFrame )?.owner()
299
301
300
- private tailrec fun CoroutineStackFrame.owner (): ArtificialStackFrame <* >? =
301
- if (this is ArtificialStackFrame <* >) this else callerFrame?.owner()
302
+ private tailrec fun CoroutineStackFrame.owner (): CoroutineOwner <* >? =
303
+ if (this is CoroutineOwner <* >) this else callerFrame?.owner()
302
304
303
305
internal fun <T > probeCoroutineCreated (completion : Continuation <T >): Continuation <T > {
304
306
if (! isInstalled) return completion
@@ -312,32 +314,39 @@ internal object DebugProbesImpl {
312
314
* Here we replace completion with a sequence of CoroutineStackFrame objects
313
315
* which represents creation stacktrace, thus making stacktrace recovery mechanism
314
316
* even more verbose (it will attach coroutine creation stacktrace to all exceptions),
315
- * and then using this artificial frame as an identifier of coroutineSuspended/resumed calls.
317
+ * and then using CoroutineOwner completion as unique identifier of coroutineSuspended/resumed calls.
316
318
*/
317
319
val stacktrace = sanitizeStackTrace(Exception ())
318
320
val frame = stacktrace.foldRight<StackTraceElement , CoroutineStackFrame ?>(null ) { frame, acc ->
319
321
object : CoroutineStackFrame {
320
322
override val callerFrame: CoroutineStackFrame ? = acc
321
323
override fun getStackTraceElement (): StackTraceElement = frame
322
324
}
323
- }
324
- return ArtificialStackFrame (completion, frame!! ).also {
325
- storeFrame(it, completion)
326
- }
325
+ }!!
326
+
327
+ return createOwner(completion, frame)
327
328
}
328
329
329
330
@Synchronized
330
- private fun <T > storeFrame (frame : ArtificialStackFrame <T >, completion : Continuation <T >) {
331
- capturedCoroutines[frame] = CoroutineState (completion, frame, ++ sequenceNumber)
331
+ private fun <T > createOwner (completion : Continuation <T >, frame : CoroutineStackFrame ): CoroutineOwner <T > {
332
+ val state = CoroutineState (completion, frame, ++ sequenceNumber)
333
+ val owner = CoroutineOwner (completion, state, frame)
334
+ capturedCoroutines + = owner
335
+ return owner
332
336
}
333
337
334
338
@Synchronized
335
- private fun probeCoroutineCompleted (coroutine : ArtificialStackFrame <* >) {
339
+ private fun probeCoroutineCompleted (coroutine : CoroutineOwner <* >) {
336
340
capturedCoroutines.remove(coroutine)
337
341
}
338
342
339
- private class ArtificialStackFrame <T >(
343
+ /* *
344
+ * This class is injected as completion of all continuations in [probeCoroutineCompleted].
345
+ * It is owning the coroutine state and responsible for managing all its external state related to debug agent.
346
+ */
347
+ private class CoroutineOwner <T >(
340
348
@JvmField val delegate : Continuation <T >,
349
+ @JvmField val state : CoroutineState ,
341
350
frame : CoroutineStackFrame
342
351
) : Continuation<T> by delegate, CoroutineStackFrame by frame {
343
352
override fun resumeWith (result : Result <T >) {
0 commit comments