• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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