Skip to content

Commit 0bf85e6

Browse files
committed
Replace WeakHashMap with Set to simplify debug probes
1 parent e35baa4 commit 0bf85e6

File tree

1 file changed

+39
-30
lines changed

1 file changed

+39
-30
lines changed

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

+39-30
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import java.io.*
1313
import java.text.*
1414
import java.util.*
1515
import kotlin.collections.ArrayList
16+
import kotlin.collections.HashMap
1617
import kotlin.coroutines.*
1718
import kotlin.coroutines.jvm.internal.*
1819
import kotlinx.coroutines.internal.artificialFrame as createArtificialFrame // IDEA bug workaround
@@ -25,7 +26,7 @@ import kotlinx.coroutines.internal.artificialFrame as createArtificialFrame // I
2526
internal object DebugProbesImpl {
2627
private const val ARTIFICIAL_FRAME_MESSAGE = "Coroutine creation stacktrace"
2728
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())
2930
@Volatile
3031
private var installations = 0
3132
private val isInstalled: Boolean get() = installations > 0
@@ -77,8 +78,8 @@ internal object DebugProbesImpl {
7778
public fun hierarchyToString(job: Job): String {
7879
check(isInstalled) { "Debug probes are not installed" }
7980
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})
8283
return buildString {
8384
job.build(jobToStack, this, "")
8485
}
@@ -115,8 +116,8 @@ internal object DebugProbesImpl {
115116
@Synchronized
116117
public fun dumpCoroutinesState(): List<CoroutineState> {
117118
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) }
120121
.sortedBy { it.sequenceNumber }
121122
.toList()
122123
}
@@ -133,19 +134,20 @@ internal object DebugProbesImpl {
133134
append("Coroutines dump ${dateFormat.format(System.currentTimeMillis())}")
134135
capturedCoroutines
135136
.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)"
142144
else
143-
value.state.toString()
145+
coroutineState.state.toString()
144146

145-
append("\n\nCoroutine $key, state: $state")
147+
append("\n\nCoroutine ${owner.delegate}, state: $state")
146148
if (observedStackTrace.isEmpty()) {
147149
append("\n\tat ${createArtificialFrame(ARTIFICIAL_FRAME_MESSAGE)}")
148-
printStackTrace(value.creationStackTrace)
150+
printStackTrace(coroutineState.creationStackTrace)
149151
} else {
150152
printStackTrace(enhancedStackTrace)
151153
}
@@ -265,14 +267,14 @@ internal object DebugProbesImpl {
265267
}
266268

267269
// Find ArtificialStackFrame of the coroutine
268-
val owner = frame.owner()
270+
val owner = frame.owner() ?: return
269271
updateState(owner, frame, state)
270272
}
271273

272274
@Synchronized // See comment to stateCache
273275
private fun updateRunningState(continuation: Continuation<*>, state: State) {
274276
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
276278
// Do not cache states for proxy-classes such as ScopeCoroutines
277279
val caller = frame.realCaller()
278280
if (caller != null) {
@@ -289,16 +291,16 @@ internal object DebugProbesImpl {
289291
}
290292

291293
@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
294296
coroutineState.updateState(state, frame)
295297
}
296298

297-
private fun Continuation<*>.owner(): ArtificialStackFrame<*>? =
299+
private fun Continuation<*>.owner(): CoroutineOwner<*>? =
298300
(this as? CoroutineStackFrame)?.owner()
299301

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()
302304

303305
internal fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> {
304306
if (!isInstalled) return completion
@@ -312,32 +314,39 @@ internal object DebugProbesImpl {
312314
* Here we replace completion with a sequence of CoroutineStackFrame objects
313315
* which represents creation stacktrace, thus making stacktrace recovery mechanism
314316
* 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.
316318
*/
317319
val stacktrace = sanitizeStackTrace(Exception())
318320
val frame = stacktrace.foldRight<StackTraceElement, CoroutineStackFrame?>(null) { frame, acc ->
319321
object : CoroutineStackFrame {
320322
override val callerFrame: CoroutineStackFrame? = acc
321323
override fun getStackTraceElement(): StackTraceElement = frame
322324
}
323-
}
324-
return ArtificialStackFrame(completion, frame!!).also {
325-
storeFrame(it, completion)
326-
}
325+
}!!
326+
327+
return createOwner(completion, frame)
327328
}
328329

329330
@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
332336
}
333337

334338
@Synchronized
335-
private fun probeCoroutineCompleted(coroutine: ArtificialStackFrame<*>) {
339+
private fun probeCoroutineCompleted(coroutine: CoroutineOwner<*>) {
336340
capturedCoroutines.remove(coroutine)
337341
}
338342

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>(
340348
@JvmField val delegate: Continuation<T>,
349+
@JvmField val state: CoroutineState,
341350
frame: CoroutineStackFrame
342351
) : Continuation<T> by delegate, CoroutineStackFrame by frame {
343352
override fun resumeWith(result: Result<T>) {

0 commit comments

Comments
 (0)