@@ -32,6 +32,16 @@ internal object DebugProbesImpl {
32
32
// To sort coroutines by creation order, used as unique id
33
33
private var sequenceNumber: Long = 0
34
34
35
+ /*
36
+ * This is an optimization in the face of KT-29997:
37
+ * Consider suspending call stack a()->b()->c() and c() completes its execution and every call is
38
+ * "almost" in tail position.
39
+ *
40
+ * Then at least three RUNNING -> RUNNING transitions will occur consecutively and complexity of each is O(depth).
41
+ * To avoid that quadratic complexity, we are caching lookup result for such chains in this map and update it incrementally.
42
+ */
43
+ private val stateCache = WeakHashMap <CoroutineStackFrame , CoroutineState >()
44
+
35
45
@Synchronized
36
46
public fun install () {
37
47
if (++ installations > 1 ) return
@@ -172,7 +182,7 @@ internal object DebugProbesImpl {
172
182
* 4) Prepend dumped stacktrace (trimmed by target frame) to continuation stacktrace
173
183
*
174
184
* Heuristic may fail on recursion and overloads, but it will be automatically improved
175
- * with KT-29997
185
+ * with KT-29997.
176
186
*/
177
187
val indexOfResumeWith = actualTrace.indexOfFirst {
178
188
it.className == " kotlin.coroutines.jvm.internal.BaseContinuationImpl" &&
@@ -248,11 +258,36 @@ internal object DebugProbesImpl {
248
258
249
259
private fun updateState (frame : Continuation <* >, state : State ) {
250
260
if (! isInstalled) return
261
+ // KT-29997 is here only since 1.3.30
262
+ if (state == State .RUNNING && KotlinVersion .CURRENT .isAtLeast(1 , 3 , 30 )) {
263
+ updateRunningState(frame, state)
264
+ return
265
+ }
266
+
251
267
// Find ArtificialStackFrame of the coroutine
252
268
val owner = frame.owner()
253
269
updateState(owner, frame, state)
254
270
}
255
271
272
+ @Synchronized // See comment to stateCache
273
+ private fun updateRunningState (continuation : Continuation <* >, state : State ) {
274
+ val frame = continuation as ? CoroutineStackFrame ? : return
275
+ val coroutineState = stateCache.remove(frame) ? : capturedCoroutines[frame.owner()] ? : return
276
+ // Do not cache states for proxy-classes such as ScopeCoroutines
277
+ val caller = frame.realCaller()
278
+ if (caller != null ) {
279
+ stateCache[caller] = coroutineState
280
+ }
281
+
282
+ coroutineState.updateState(state, continuation)
283
+ }
284
+
285
+ private tailrec fun CoroutineStackFrame.realCaller (): CoroutineStackFrame ? {
286
+ val caller = callerFrame ? : return null
287
+ if (caller.getStackTraceElement() != null ) return caller
288
+ else return caller.realCaller()
289
+ }
290
+
256
291
@Synchronized
257
292
private fun updateState (owner : ArtificialStackFrame <* >? , frame : Continuation <* >, state : State ) {
258
293
val coroutineState = capturedCoroutines[owner] ? : return
0 commit comments