@@ -32,6 +32,16 @@ internal object DebugProbesImpl {
3232 // To sort coroutines by creation order, used as unique id
3333 private var sequenceNumber: Long = 0
3434
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+
3545 @Synchronized
3646 public fun install () {
3747 if (++ installations > 1 ) return
@@ -172,7 +182,7 @@ internal object DebugProbesImpl {
172182 * 4) Prepend dumped stacktrace (trimmed by target frame) to continuation stacktrace
173183 *
174184 * Heuristic may fail on recursion and overloads, but it will be automatically improved
175- * with KT-29997
185+ * with KT-29997.
176186 */
177187 val indexOfResumeWith = actualTrace.indexOfFirst {
178188 it.className == " kotlin.coroutines.jvm.internal.BaseContinuationImpl" &&
@@ -248,11 +258,36 @@ internal object DebugProbesImpl {
248258
249259 private fun updateState (frame : Continuation <* >, state : State ) {
250260 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+
251267 // Find ArtificialStackFrame of the coroutine
252268 val owner = frame.owner()
253269 updateState(owner, frame, state)
254270 }
255271
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+
256291 @Synchronized
257292 private fun updateState (owner : ArtificialStackFrame <* >? , frame : Continuation <* >, state : State ) {
258293 val coroutineState = capturedCoroutines[owner] ? : return
0 commit comments