| @file:Suppress("UNUSED", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") |
| |
| package kotlinx.coroutines.debug |
| |
| import kotlinx.coroutines.* |
| import kotlinx.coroutines.debug.internal.* |
| import java.io.* |
| import java.lang.management.* |
| import kotlin.coroutines.* |
| |
| /** |
| * Kotlin debug probes support. |
| * |
| * Debug probes is a dynamic attach mechanism which installs multiple hooks into coroutines machinery. |
| * It slows down all coroutine-related code, but in return provides diagnostic information, including |
| * asynchronous stacktraces, coroutine dumps (similar to [ThreadMXBean.dumpAllThreads] and `jstack`) via [DebugProbes.dumpCoroutines], |
| * and programmatic introspection of all alive coroutines. |
| * All introspecting methods throw [IllegalStateException] if debug probes were not installed. |
| * |
| * ### Consistency guarantees |
| * |
| * All snapshotting operations (e.g. [dumpCoroutines]) are *weakly-consistent*, meaning that they happen |
| * concurrently with coroutines progressing their own state. These operations are guaranteed to observe |
| * each coroutine's state exactly once, but the state is not guaranteed to be the most recent before the operation. |
| * In practice, it means that for snapshotting operations in progress, for each concurrent coroutine either |
| * the state prior to the operation or the state that was reached during the current operation is observed. |
| * |
| * ### Overhead |
| * |
| * - Every created coroutine is stored in a concurrent hash map, and the hash map is looked up in and |
| * updated on each suspension and resumption. |
| * - If [DebugProbes.enableCreationStackTraces] is enabled, stack trace of the current thread is captured on |
| * each created coroutine that is a rough equivalent of throwing an exception per each created coroutine. |
| * |
| * ### Internal machinery and classloading. |
| * |
| * Under the hood, debug probes replace internal `kotlin.coroutines.jvm.internal.DebugProbesKt` class that has the following |
| * empty static methods: |
| * |
| * - `probeCoroutineResumed` that is invoked on every [Continuation.resume]. |
| * - `probeCoroutineSuspended` that is invoked on every continuation suspension. |
| * - `probeCoroutineCreated` that is invoked on every coroutine creation. |
| * |
| * with a `kotlinx-coroutines`-specific class to keep track of all the coroutines machinery. |
| * |
| * The new class is located in the `kotlinx-coroutines-core` module, meaning that all target application classes that use |
| * coroutines and `suspend` functions have to be loaded by the classloader in which `kotlinx-coroutines-core` classes are available. |
| */ |
| @ExperimentalCoroutinesApi |
| public object DebugProbes { |
| |
| /** |
| * Whether coroutine creation stack traces should be sanitized. |
| * Sanitization removes all frames from `kotlinx.coroutines` package except |
| * the first one and the last one to simplify diagnostic. |
| * |
| * `true` by default. |
| */ |
| public var sanitizeStackTraces: Boolean |
| get() = DebugProbesImpl.sanitizeStackTraces |
| @Suppress("INVISIBLE_SETTER") // do not remove the INVISIBLE_SETTER suppression: required in k2 |
| set(value) { |
| DebugProbesImpl.sanitizeStackTraces = value |
| } |
| |
| /** |
| * Whether coroutine creation stack traces should be captured. |
| * When enabled, for each created coroutine a stack trace of the current thread is captured and attached to the coroutine. |
| * This option can be useful during local debug sessions, but is recommended |
| * to be disabled in production environments to avoid performance overhead of capturing real stacktraces. |
| * |
| * `false` by default. |
| */ |
| public var enableCreationStackTraces: Boolean |
| get() = DebugProbesImpl.enableCreationStackTraces |
| @Suppress("INVISIBLE_SETTER") // do not remove the INVISIBLE_SETTER suppression: required in k2 |
| set(value) { |
| DebugProbesImpl.enableCreationStackTraces = value |
| } |
| |
| /** |
| * Whether to ignore coroutines whose context is [EmptyCoroutineContext]. |
| * |
| * Coroutines with empty context are considered to be irrelevant for the concurrent coroutines' observability: |
| * - They do not contribute to any concurrent executions |
| * - They do not contribute to the (concurrent) system's liveness and/or deadlocks, as no other coroutines might wait for them |
| * - The typical usage of such coroutines is a combinator/builder/lookahead parser that can be debugged using more convenient tools. |
| * |
| * `true` by default. |
| */ |
| public var ignoreCoroutinesWithEmptyContext: Boolean |
| get() = DebugProbesImpl.ignoreCoroutinesWithEmptyContext |
| @Suppress("INVISIBLE_SETTER") // do not remove the INVISIBLE_SETTER suppression: required in k2 |
| set(value) { |
| DebugProbesImpl.ignoreCoroutinesWithEmptyContext = value |
| } |
| |
| /** |
| * Determines whether debug probes were [installed][DebugProbes.install]. |
| */ |
| public val isInstalled: Boolean get() = DebugProbesImpl.isInstalled |
| |
| /** |
| * Installs a [DebugProbes] instead of no-op stdlib probes by redefining |
| * debug probes class using the same class loader as one loaded [DebugProbes] class. |
| */ |
| public fun install() { |
| DebugProbesImpl.install() |
| } |
| |
| /** |
| * Uninstall debug probes. |
| */ |
| public fun uninstall() { |
| DebugProbesImpl.uninstall() |
| } |
| |
| /** |
| * Invokes given block of code with installed debug probes and uninstall probes in the end. |
| */ |
| public inline fun withDebugProbes(block: () -> Unit) { |
| install() |
| try { |
| block() |
| } finally { |
| uninstall() |
| } |
| } |
| |
| /** |
| * Returns string representation of the coroutines [job] hierarchy with additional debug information. |
| * Hierarchy is printed from the [job] as a root transitively to all children. |
| */ |
| public fun jobToString(job: Job): String = DebugProbesImpl.hierarchyToString(job) |
| |
| /** |
| * Returns string representation of all coroutines launched within the given [scope]. |
| * Throws [IllegalStateException] if the scope has no a job in it. |
| */ |
| public fun scopeToString(scope: CoroutineScope): String = |
| jobToString(scope.coroutineContext[Job] ?: error("Job is not present in the scope")) |
| |
| /** |
| * Prints [job] hierarchy representation from [jobToString] to the given [out]. |
| */ |
| public fun printJob(job: Job, out: PrintStream = System.out): Unit = |
| out.println(DebugProbesImpl.hierarchyToString(job)) |
| |
| /** |
| * Prints all coroutines launched within the given [scope]. |
| * Throws [IllegalStateException] if the scope has no a job in it. |
| */ |
| public fun printScope(scope: CoroutineScope, out: PrintStream = System.out): Unit = |
| printJob(scope.coroutineContext[Job] ?: error("Job is not present in the scope"), out) |
| |
| /** |
| * Returns all existing coroutines' info. |
| * The resulting collection represents a consistent snapshot of all existing coroutines at the moment of invocation. |
| */ |
| public fun dumpCoroutinesInfo(): List<CoroutineInfo> = |
| DebugProbesImpl.dumpCoroutinesInfo().map { CoroutineInfo(it) } |
| |
| /** |
| * Dumps all active coroutines into the given output stream, providing a consistent snapshot of all existing coroutines at the moment of invocation. |
| * The output of this method is similar to `jstack` or a full thread dump. It can be used as the replacement to |
| * "Dump threads" action. |
| * |
| * Example of the output: |
| * ``` |
| * Coroutines dump 2018/11/12 19:45:14 |
| * |
| * Coroutine "coroutine#42":StandaloneCoroutine{Active}@58fdd99, state: SUSPENDED |
| * at MyClass$awaitData.invokeSuspend(MyClass.kt:37) |
| * at _COROUTINE._CREATION._(CoroutineDebugging.kt) |
| * at MyClass.createIoRequest(MyClass.kt:142) |
| * at MyClass.fetchData(MyClass.kt:154) |
| * at MyClass.showData(MyClass.kt:31) |
| * ... |
| * ``` |
| */ |
| public fun dumpCoroutines(out: PrintStream = System.out): Unit = DebugProbesImpl.dumpCoroutines(out) |
| } |