aboutsummaryrefslogtreecommitdiffstats
path: root/kotlinx-coroutines-debug/src/CoroutineInfo.kt
blob: 84cd9f370ecce00ecdd17d776b9bcde8cf804719 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/*
 * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

@file:Suppress("PropertyName")

package kotlinx.coroutines.debug

import kotlinx.coroutines.*
import kotlin.coroutines.*
import kotlin.coroutines.jvm.internal.*

/**
 * Class describing coroutine info such as its context, state and stacktrace.
 */
@ExperimentalCoroutinesApi
public class CoroutineInfo internal constructor(
    val context: CoroutineContext,
    private val creationStackBottom: CoroutineStackFrame,
    @JvmField internal val sequenceNumber: Long
) {

    /**
     * [Job] associated with a current coroutine or null.
     * May be later used in [DebugProbes.printJob].
     */
    public val job: Job? get() = context[Job]

    /**
     * Creation stacktrace of the coroutine.
     */
    public val creationStackTrace: List<StackTraceElement> get() = creationStackTrace()

    /**
     * Last observed [state][State] of the coroutine.
     */
    public val state: State get() = _state

    private var _state: State = State.CREATED

    @JvmField
    internal var lastObservedThread: Thread? = null

    @JvmField
    internal var lastObservedFrame: CoroutineStackFrame? = null

    public fun copy(): CoroutineInfo = CoroutineInfo(context, creationStackBottom, sequenceNumber).also {
        it._state = _state
        it.lastObservedFrame = lastObservedFrame
        it.lastObservedThread = lastObservedThread
    }

    /**
     * Last observed stacktrace of the coroutine captured on its suspension or resumption point.
     * It means that for [running][State.RUNNING] coroutines resulting stacktrace is inaccurate and
     * reflects stacktrace of the resumption point, not the actual current stacktrace.
     */
    public fun lastObservedStackTrace(): List<StackTraceElement> {
        var frame: CoroutineStackFrame? = lastObservedFrame ?: return emptyList()
        val result = ArrayList<StackTraceElement>()
        while (frame != null) {
            frame.getStackTraceElement()?.let { result.add(it) }
            frame = frame.callerFrame
        }
        return result
    }

    private fun creationStackTrace(): List<StackTraceElement> {
        // Skip "Coroutine creation stacktrace" frame
        return sequence<StackTraceElement> { yieldFrames(creationStackBottom.callerFrame) }.toList()
    }

    private tailrec suspend fun SequenceScope<StackTraceElement>.yieldFrames(frame: CoroutineStackFrame?) {
        if (frame == null) return
        frame.getStackTraceElement()?.let { yield(it) }
        val caller = frame.callerFrame
        if (caller != null) {
            yieldFrames(caller)
        }
    }

    internal fun updateState(state: State, frame: Continuation<*>) {
        // Propagate only duplicating transitions to running for KT-29997
        if (_state == state && state == State.SUSPENDED && lastObservedFrame != null) return
        _state = state
        lastObservedFrame = frame as? CoroutineStackFrame
        if (state == State.RUNNING) {
            lastObservedThread = Thread.currentThread()
        } else {
            lastObservedThread = null
        }
    }

    override fun toString(): String = "CoroutineInfo(state=$state,context=$context)"
}

/**
 * Current state of the coroutine.
 */
public enum class State {
    /**
     * Created, but not yet started.
     */
    CREATED,
    /**
     * Started and running.
     */
    RUNNING,
    /**
     * Suspended.
     */
    SUSPENDED
}