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