1 @file:Suppress("UNUSED", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") 2 3 package kotlinx.coroutines.debug 4 5 import kotlinx.coroutines.* 6 import kotlinx.coroutines.debug.internal.* 7 import java.io.* 8 import java.lang.management.* 9 import kotlin.coroutines.* 10 11 /** 12 * Kotlin debug probes support. 13 * 14 * Debug probes is a dynamic attach mechanism which installs multiple hooks into coroutines machinery. 15 * It slows down all coroutine-related code, but in return provides diagnostic information, including 16 * asynchronous stacktraces, coroutine dumps (similar to [ThreadMXBean.dumpAllThreads] and `jstack`) via [DebugProbes.dumpCoroutines], 17 * and programmatic introspection of all alive coroutines. 18 * All introspecting methods throw [IllegalStateException] if debug probes were not installed. 19 * 20 * ### Consistency guarantees 21 * 22 * All snapshotting operations (e.g. [dumpCoroutines]) are *weakly-consistent*, meaning that they happen 23 * concurrently with coroutines progressing their own state. These operations are guaranteed to observe 24 * each coroutine's state exactly once, but the state is not guaranteed to be the most recent before the operation. 25 * In practice, it means that for snapshotting operations in progress, for each concurrent coroutine either 26 * the state prior to the operation or the state that was reached during the current operation is observed. 27 * 28 * ### Overhead 29 * 30 * - Every created coroutine is stored in a concurrent hash map, and the hash map is looked up in and 31 * updated on each suspension and resumption. 32 * - If [DebugProbes.enableCreationStackTraces] is enabled, stack trace of the current thread is captured on 33 * each created coroutine that is a rough equivalent of throwing an exception per each created coroutine. 34 * 35 * ### Internal machinery and classloading. 36 * 37 * Under the hood, debug probes replace internal `kotlin.coroutines.jvm.internal.DebugProbesKt` class that has the following 38 * empty static methods: 39 * 40 * - `probeCoroutineResumed` that is invoked on every [Continuation.resume]. 41 * - `probeCoroutineSuspended` that is invoked on every continuation suspension. 42 * - `probeCoroutineCreated` that is invoked on every coroutine creation. 43 * 44 * with a `kotlinx-coroutines`-specific class to keep track of all the coroutines machinery. 45 * 46 * The new class is located in the `kotlinx-coroutines-core` module, meaning that all target application classes that use 47 * coroutines and `suspend` functions have to be loaded by the classloader in which `kotlinx-coroutines-core` classes are available. 48 */ 49 @ExperimentalCoroutinesApi 50 public object DebugProbes { 51 52 /** 53 * Whether coroutine creation stack traces should be sanitized. 54 * Sanitization removes all frames from `kotlinx.coroutines` package except 55 * the first one and the last one to simplify diagnostic. 56 * 57 * `true` by default. 58 */ 59 public var sanitizeStackTraces: Boolean 60 get() = DebugProbesImpl.sanitizeStackTraces 61 @Suppress("INVISIBLE_SETTER") // do not remove the INVISIBLE_SETTER suppression: required in k2 62 set(value) { 63 DebugProbesImpl.sanitizeStackTraces = value 64 } 65 66 /** 67 * Whether coroutine creation stack traces should be captured. 68 * When enabled, for each created coroutine a stack trace of the current thread is captured and attached to the coroutine. 69 * This option can be useful during local debug sessions, but is recommended 70 * to be disabled in production environments to avoid performance overhead of capturing real stacktraces. 71 * 72 * `false` by default. 73 */ 74 public var enableCreationStackTraces: Boolean 75 get() = DebugProbesImpl.enableCreationStackTraces 76 @Suppress("INVISIBLE_SETTER") // do not remove the INVISIBLE_SETTER suppression: required in k2 77 set(value) { 78 DebugProbesImpl.enableCreationStackTraces = value 79 } 80 81 /** 82 * Whether to ignore coroutines whose context is [EmptyCoroutineContext]. 83 * 84 * Coroutines with empty context are considered to be irrelevant for the concurrent coroutines' observability: 85 * - They do not contribute to any concurrent executions 86 * - They do not contribute to the (concurrent) system's liveness and/or deadlocks, as no other coroutines might wait for them 87 * - The typical usage of such coroutines is a combinator/builder/lookahead parser that can be debugged using more convenient tools. 88 * 89 * `true` by default. 90 */ 91 public var ignoreCoroutinesWithEmptyContext: Boolean 92 get() = DebugProbesImpl.ignoreCoroutinesWithEmptyContext 93 @Suppress("INVISIBLE_SETTER") // do not remove the INVISIBLE_SETTER suppression: required in k2 94 set(value) { 95 DebugProbesImpl.ignoreCoroutinesWithEmptyContext = value 96 } 97 98 /** 99 * Determines whether debug probes were [installed][DebugProbes.install]. 100 */ 101 public val isInstalled: Boolean get() = DebugProbesImpl.isInstalled 102 103 /** 104 * Installs a [DebugProbes] instead of no-op stdlib probes by redefining 105 * debug probes class using the same class loader as one loaded [DebugProbes] class. 106 */ installnull107 public fun install() { 108 DebugProbesImpl.install() 109 } 110 111 /** 112 * Uninstall debug probes. 113 */ uninstallnull114 public fun uninstall() { 115 DebugProbesImpl.uninstall() 116 } 117 118 /** 119 * Invokes given block of code with installed debug probes and uninstall probes in the end. 120 */ withDebugProbesnull121 public inline fun withDebugProbes(block: () -> Unit) { 122 install() 123 try { 124 block() 125 } finally { 126 uninstall() 127 } 128 } 129 130 /** 131 * Returns string representation of the coroutines [job] hierarchy with additional debug information. 132 * Hierarchy is printed from the [job] as a root transitively to all children. 133 */ jobToStringnull134 public fun jobToString(job: Job): String = DebugProbesImpl.hierarchyToString(job) 135 136 /** 137 * Returns string representation of all coroutines launched within the given [scope]. 138 * Throws [IllegalStateException] if the scope has no a job in it. 139 */ 140 public fun scopeToString(scope: CoroutineScope): String = 141 jobToString(scope.coroutineContext[Job] ?: error("Job is not present in the scope")) 142 143 /** 144 * Prints [job] hierarchy representation from [jobToString] to the given [out]. 145 */ 146 public fun printJob(job: Job, out: PrintStream = System.out): Unit = 147 out.println(DebugProbesImpl.hierarchyToString(job)) 148 149 /** 150 * Prints all coroutines launched within the given [scope]. 151 * Throws [IllegalStateException] if the scope has no a job in it. 152 */ 153 public fun printScope(scope: CoroutineScope, out: PrintStream = System.out): Unit = 154 printJob(scope.coroutineContext[Job] ?: error("Job is not present in the scope"), out) 155 156 /** 157 * Returns all existing coroutines' info. 158 * The resulting collection represents a consistent snapshot of all existing coroutines at the moment of invocation. 159 */ 160 public fun dumpCoroutinesInfo(): List<CoroutineInfo> = 161 DebugProbesImpl.dumpCoroutinesInfo().map { CoroutineInfo(it) } 162 163 /** 164 * Dumps all active coroutines into the given output stream, providing a consistent snapshot of all existing coroutines at the moment of invocation. 165 * The output of this method is similar to `jstack` or a full thread dump. It can be used as the replacement to 166 * "Dump threads" action. 167 * 168 * Example of the output: 169 * ``` 170 * Coroutines dump 2018/11/12 19:45:14 171 * 172 * Coroutine "coroutine#42":StandaloneCoroutine{Active}@58fdd99, state: SUSPENDED 173 * at MyClass$awaitData.invokeSuspend(MyClass.kt:37) 174 * at _COROUTINE._CREATION._(CoroutineDebugging.kt) 175 * at MyClass.createIoRequest(MyClass.kt:142) 176 * at MyClass.fetchData(MyClass.kt:154) 177 * at MyClass.showData(MyClass.kt:31) 178 * ... 179 * ``` 180 */ dumpCoroutinesnull181 public fun dumpCoroutines(out: PrintStream = System.out): Unit = DebugProbesImpl.dumpCoroutines(out) 182 } 183