1 /* 2 * Copyright 2016-2020 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 * Installed hooks: 24 * 25 * * `probeCoroutineResumed` is invoked on every [Continuation.resume]. 26 * * `probeCoroutineSuspended` is invoked on every continuation suspension. 27 * * `probeCoroutineCreated` is invoked on every coroutine creation using stdlib intrinsics. 28 * 29 * Overhead: 30 * * Every created coroutine is stored in a concurrent hash map and hash map is looked up 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 @ExperimentalCoroutinesApi 36 public object DebugProbes { 37 38 /** 39 * Whether coroutine creation stack traces should be sanitized. 40 * Sanitization removes all frames from `kotlinx.coroutines` package except 41 * the first one and the last one to simplify diagnostic. 42 */ 43 public var sanitizeStackTraces: Boolean 44 get() = DebugProbesImpl.sanitizeStackTraces 45 set(value) { 46 DebugProbesImpl.sanitizeStackTraces = value 47 } 48 49 /** 50 * Whether coroutine creation stack traces should be captured. 51 * When enabled, for each created coroutine a stack trace of the current 52 * thread is captured and attached to the coroutine. 53 * This option can be useful during local debug sessions, but is recommended 54 * to be disabled in production environments to avoid stack trace dumping overhead. 55 */ 56 public var enableCreationStackTraces: Boolean 57 get() = DebugProbesImpl.enableCreationStackTraces 58 set(value) { 59 DebugProbesImpl.enableCreationStackTraces = value 60 } 61 62 /** 63 * Determines whether debug probes were [installed][DebugProbes.install]. 64 */ 65 public val isInstalled: Boolean get() = DebugProbesImpl.isInstalled 66 67 /** 68 * Installs a [DebugProbes] instead of no-op stdlib probes by redefining 69 * debug probes class using the same class loader as one loaded [DebugProbes] class. 70 */ installnull71 public fun install() { 72 DebugProbesImpl.install() 73 } 74 75 /** 76 * Uninstall debug probes. 77 */ uninstallnull78 public fun uninstall() { 79 DebugProbesImpl.uninstall() 80 } 81 82 /** 83 * Invokes given block of code with installed debug probes and uninstall probes in the end. 84 */ withDebugProbesnull85 public inline fun withDebugProbes(block: () -> Unit) { 86 install() 87 try { 88 block() 89 } finally { 90 uninstall() 91 } 92 } 93 94 /** 95 * Returns string representation of the coroutines [job] hierarchy with additional debug information. 96 * Hierarchy is printed from the [job] as a root transitively to all children. 97 */ jobToStringnull98 public fun jobToString(job: Job): String = DebugProbesImpl.hierarchyToString(job) 99 100 /** 101 * Returns string representation of all coroutines launched within the given [scope]. 102 * Throws [IllegalStateException] if the scope has no a job in it. 103 */ 104 public fun scopeToString(scope: CoroutineScope): String = 105 jobToString(scope.coroutineContext[Job] ?: error("Job is not present in the scope")) 106 107 /** 108 * Prints [job] hierarchy representation from [jobToString] to the given [out]. 109 */ 110 public fun printJob(job: Job, out: PrintStream = System.out): Unit = 111 out.println(DebugProbesImpl.hierarchyToString(job)) 112 113 /** 114 * Prints all coroutines launched within the given [scope]. 115 * Throws [IllegalStateException] if the scope has no a job in it. 116 */ 117 public fun printScope(scope: CoroutineScope, out: PrintStream = System.out): Unit = 118 printJob(scope.coroutineContext[Job] ?: error("Job is not present in the scope"), out) 119 120 /** 121 * Returns all existing coroutines info. 122 * The resulting collection represents a consistent snapshot of all existing coroutines at the moment of invocation. 123 */ 124 public fun dumpCoroutinesInfo(): List<CoroutineInfo> = DebugProbesImpl.dumpCoroutinesInfo().map { CoroutineInfo(it) } 125 126 /** 127 * Dumps all active coroutines into the given output stream, providing a consistent snapshot of all existing coroutines at the moment of invocation. 128 * The output of this method is similar to `jstack` or a full thread dump. It can be used as the replacement to 129 * "Dump threads" action. 130 * 131 * Example of the output: 132 * ``` 133 * Coroutines dump 2018/11/12 19:45:14 134 * 135 * Coroutine "coroutine#42":StandaloneCoroutine{Active}@58fdd99, state: SUSPENDED 136 * at MyClass$awaitData.invokeSuspend(MyClass.kt:37) 137 * (Coroutine creation stacktrace) 138 * at MyClass.createIoRequest(MyClass.kt:142) 139 * at MyClass.fetchData(MyClass.kt:154) 140 * at MyClass.showData(MyClass.kt:31) 141 * ... 142 * ``` 143 */ dumpCoroutinesnull144 public fun dumpCoroutines(out: PrintStream = System.out): Unit = DebugProbesImpl.dumpCoroutines(out) 145 } 146 147 // Stubs which are injected as coroutine probes. Require direct match of signatures 148 internal fun probeCoroutineResumed(frame: Continuation<*>) = DebugProbesImpl.probeCoroutineResumed(frame) 149 150 internal fun probeCoroutineSuspended(frame: Continuation<*>) = DebugProbesImpl.probeCoroutineSuspended(frame) 151 internal fun <T> probeCoroutineCreated(completion: Continuation<T>): Continuation<T> = 152 DebugProbesImpl.probeCoroutineCreated(completion) 153