README.md
1# Module kotlinx-coroutines-debug
2
3Debugging facilities for `kotlinx.coroutines` on JVM.
4
5### Overview
6
7This module provides a debug JVM agent that allows to track and trace existing coroutines.
8The main entry point to debug facilities is [DebugProbes] API.
9Call to [DebugProbes.install] installs debug agent via ByteBuddy and starts spying on coroutines when they are created, suspended and resumed.
10
11After that, you can use [DebugProbes.dumpCoroutines] to print all active (suspended or running) coroutines, including their state, creation and
12suspension stacktraces.
13Additionally, it is possible to process the list of such coroutines via [DebugProbes.dumpCoroutinesInfo] or dump isolated parts
14of coroutines hierarchy referenced by a [Job] or [CoroutineScope] instances using [DebugProbes.printJob] and [DebugProbes.printScope] respectively.
15
16This module also provides an automatic [BlockHound](https://github.com/reactor/BlockHound) integration
17that detects when a blocking operation was called in a coroutine context that prohibits it. In order to use it,
18please follow the BlockHound [quick start guide](
19https://github.com/reactor/BlockHound/blob/1.0.2.RELEASE/docs/quick_start.md).
20
21### Using in your project
22
23Add `kotlinx-coroutines-debug` to your project test dependencies:
24```
25dependencies {
26 testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.4.0'
27}
28```
29
30### Using in unit tests
31
32For JUnit4 debug module provides special test rule, [CoroutinesTimeout], for installing debug probes
33and to dump coroutines on timeout to simplify tests debugging.
34
35Its usage is better demonstrated by the example (runnable code is [here](test/TestRuleExample.kt)):
36
37```kotlin
38class TestRuleExample {
39 @get:Rule
40 public val timeout = CoroutinesTimeout.seconds(1)
41
42 private suspend fun someFunctionDeepInTheStack() {
43 withContext(Dispatchers.IO) {
44 delay(Long.MAX_VALUE) // Hang method
45 }
46 }
47
48 @Test
49 fun hangingTest() = runBlocking {
50 val job = launch {
51 someFunctionDeepInTheStack()
52 }
53 job.join() // Join will hang
54 }
55}
56```
57
58After 1 second, test will fail with `TestTimeoutException` and all coroutines (`runBlocking` and `launch`) and their
59stacktraces will be dumped to the console.
60
61### Using as JVM agent
62
63Debug module can also be used as a standalone JVM agent to enable debug probes on the application startup.
64You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.4.1.jar`.
65Additionally, on Linux and Mac OS X you can use `kill -5 $pid` command in order to force your application to print all alive coroutines.
66When used as Java agent, `"kotlinx.coroutines.debug.enable.creation.stack.trace"` system property can be used to control
67[DebugProbes.enableCreationStackTraces] along with agent startup.
68
69### Using in production environment
70
71It is possible to run an application in production environments with debug probes in order to monitor its
72state and improve its observability.
73For that, it is strongly recommended to switch off [DebugProbes.enableCreationStackTraces] property to significantly
74reduce the overhead of debug probes and make it insignificant.
75With creation stack-traces disabled, the typical overhead of enabled debug probes is a single-digit percentage of the total
76application throughput.
77
78
79### Example of usage
80
81Capabilities of this module can be demonstrated by the following example
82(runnable code is [here](test/Example.kt)):
83
84```kotlin
85suspend fun computeValue(): String = coroutineScope {
86 val one = async { computeOne() }
87 val two = async { computeTwo() }
88 combineResults(one, two)
89}
90
91suspend fun combineResults(one: Deferred<String>, two: Deferred<String>): String =
92 one.await() + two.await()
93
94suspend fun computeOne(): String {
95 delay(5000)
96 return "4"
97}
98
99suspend fun computeTwo(): String {
100 delay(5000)
101 return "2"
102}
103
104fun main() = runBlocking {
105 DebugProbes.install()
106 val deferred = async { computeValue() }
107 // Delay for some time
108 delay(1000)
109 // Dump running coroutines
110 DebugProbes.dumpCoroutines()
111 println("\nDumping only deferred")
112 DebugProbes.printJob(deferred)
113}
114```
115
116Printed result will be:
117
118```
119Coroutines dump 2018/11/12 21:44:02
120
121Coroutine "coroutine#2":DeferredCoroutine{Active}@289d1c02, state: SUSPENDED
122 at kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:99)
123 at ExampleKt.combineResults(Example.kt:11)
124 at ExampleKt$computeValue$2.invokeSuspend(Example.kt:7)
125 at ExampleKt$main$1$deferred$1.invokeSuspend(Example.kt:25)
126 (Coroutine creation stacktrace)
127 at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)
128 at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:25)
129 at kotlinx.coroutines.BuildersKt.async$default(Unknown Source)
130 at ExampleKt$main$1.invokeSuspend(Example.kt:25)
131 at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
132 at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:233)
133 at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
134 at ExampleKt.main(Example.kt:23)
135 at ExampleKt.main(Example.kt)
136
137... More coroutines here ...
138
139Dumping only deferred
140"coroutine#2":DeferredCoroutine{Active}, continuation is SUSPENDED at line kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:99)
141 "coroutine#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ExampleKt.computeOne(Example.kt:14)
142 "coroutine#4":DeferredCoroutine{Active}, continuation is SUSPENDED at line ExampleKt.computeTwo(Example.kt:19)
143```
144
145### Status of the API
146
147API is experimental, and it is not guaranteed it won't be changed (while it is marked as `@ExperimentalCoroutinesApi`).
148Like the rest of experimental API, `DebugProbes` is carefully designed, tested and ready to use in both test and production
149environments. It is marked as experimental to leave us the room to enrich the output data in a potentially backwards incompatible manner
150to further improve diagnostics and debugging experience.
151
152The output format of [DebugProbes] can be changed in the future and it is not recommended to rely on the string representation
153of the dump programmatically.
154
155### Debug agent and Android
156
157Unfortunately, Android runtime does not support Instrument API necessary for `kotlinx-coroutines-debug` to function, triggering `java.lang.NoClassDefFoundError: Failed resolution of: Ljava/lang/management/ManagementFactory;`.
158
159Nevertheless, it will be possible to support debug agent on Android as soon as [GradleAspectJ-Android](https://github.com/Archinamon/android-gradle-aspectj) will support android-gradle 3.3
160
161<!---
162Make an exception googlable
163java.lang.NoClassDefFoundError: Failed resolution of: Ljava/lang/management/ManagementFactory;
164 at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider$ForCurrentVm$ForLegacyVm.resolve(ByteBuddyAgent.java:1055)
165 at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider$ForCurrentVm.resolve(ByteBuddyAgent.java:1038)
166 at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:374)
167 at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:342)
168 at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:328)
169 at kotlinx.coroutines.debug.internal.DebugProbesImpl.install(DebugProbesImpl.kt:39)
170 at kotlinx.coroutines.debug.DebugProbes.install(DebugProbes.kt:49)
171-->
172
173#### Build failures due to duplicate resource files
174
175Building an Android project that depends on `kotlinx-coroutines-debug` (usually introduced by being a transitive
176dependency of `kotlinx-coroutines-test`) may fail with `DuplicateRelativeFileException` for `META-INF/AL2.0`,
177`META-INF/LGPL2.1`, or `win32-x86/attach_hotspot_windows.dll` when trying to merge the Android resource.
178
179The problem is that Android merges the resources of all its dependencies into a single directory and complains about
180conflicts, but:
181* `kotlinx-coroutines-debug` transitively depends on JNA and JNA-platform, both of which include license files in their
182 META-INF directories. Trying to merge these files leads to conflicts, which means that any Android project that
183 depends on JNA and JNA-platform will experience build failures.
184* Additionally, `kotlinx-coroutines-debug` embeds `byte-buddy-agent` and `byte-buddy`, along with their resource files.
185 Then, if the project separately depends on `byte-buddy`, merging the resources of `kotlinx-coroutines-debug` with ones
186 from `byte-buddy` and `byte-buddy-agent` will lead to conflicts as the resource files are duplicated.
187
188One possible workaround for these issues is to add the following to the `android` block in your gradle file for the
189application subproject:
190```groovy
191 packagingOptions {
192 // for JNA and JNA-platform
193 exclude "META-INF/AL2.0"
194 exclude "META-INF/LGPL2.1"
195 // for byte-buddy
196 exclude "META-INF/licenses/ASM"
197 pickFirst "win32-x86-64/attach_hotspot_windows.dll"
198 pickFirst "win32-x86/attach_hotspot_windows.dll"
199 }
200```
201This will cause the resource merge algorithm to exclude the problematic license files altogether and only leave a single
202copy of the files needed for `byte-buddy-agent` to work.
203
204Alternatively, avoid depending on `kotlinx-coroutines-debug`. In particular, if the only reason why this library a
205dependency of your project is that `kotlinx-coroutines-test` in turn depends on it, you may change your dependency on
206`kotlinx.coroutines.test` to exclude `kotlinx-coroutines-debug`. For example, you could replace
207```kotlin
208androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version")
209```
210with
211```groovy
212androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version") {
213 exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
214}
215```
216<!---
217Snippets of stacktraces for googling:
218
219org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:mergeDebugAndroidTestJavaResource'.
220 ...
221Caused by: org.gradle.workers.intelrnal.DefaultWorkerExecutor$WorkExecutionException: A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
222 ...
223Caused by: com.android.builder.merge.DuplicateRelativeFileException: More than one file was found with OS independent path 'META-INF/AL2.0'.
224 at com.android.builder.merge.StreamMergeAlgorithms.lambda$acceptOnlyOne$2(StreamMergeAlgorithms.java:85)
225 at com.android.builder.merge.StreamMergeAlgorithms.lambda$select$3(StreamMergeAlgorithms.java:106)
226 at com.android.builder.merge.IncrementalFileMergerOutputs$1.create(IncrementalFileMergerOutputs.java:88)
227 at com.android.builder.merge.DelegateIncrementalFileMergerOutput.create(DelegateIncrementalFileMergerOutput.java:64)
228 at com.android.build.gradle.internal.tasks.MergeJavaResourcesDelegate$run$output$1.create(MergeJavaResourcesDelegate.kt:230)
229 at com.android.builder.merge.IncrementalFileMerger.updateChangedFile(IncrementalFileMerger.java:242)
230 at com.android.builder.merge.IncrementalFileMerger.mergeChangedInputs(IncrementalFileMerger.java:203)
231 at com.android.builder.merge.IncrementalFileMerger.merge(IncrementalFileMerger.java:80)
232 at com.android.build.gradle.internal.tasks.MergeJavaResourcesDelegate.run(MergeJavaResourcesDelegate.kt:276)
233 at com.android.build.gradle.internal.tasks.MergeJavaResRunnable.run(MergeJavaResRunnable.kt:81)
234 at com.android.build.gradle.internal.tasks.Workers$ActionFacade.run(Workers.kt:242)
235 at org.gradle.workers.internal.AdapterWorkAction.execute(AdapterWorkAction.java:50)
236 at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:50)
237 at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:63)
238 at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:59)
239 at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:98)
240 at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:59)
241 at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
242 at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
243 at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:416)
244 at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:406)
245 at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
246 at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
247 at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
248 at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:102)
249 at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36)
250 at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
251 at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:53)
252 at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$2(DefaultWorkerExecutor.java:200)
253 at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:215)
254 at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
255 at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:131)
256
257Execution failed for task ':app:mergeStagingDebugAndroidTestJavaResource'.
258Execution failed for task ':app:mergeDebugAndroidTestJavaResource'.
259Execution failed for task ':app:mergeDebugTestJavaResource'
260
261More than one file was found with OS independent path 'META-INF/LGPL2.1'
262More than one file was found with OS independent path 'win32-x86/attach_hotspot_windows.dll'
263More than one file was found with OS independent path 'win32-x86-64/attach_hotspot_windows.dll'
264-->
265<!--- MODULE kotlinx-coroutines-core -->
266<!--- INDEX kotlinx.coroutines -->
267[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
268[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
269<!--- MODULE kotlinx-coroutines-debug -->
270<!--- INDEX kotlinx.coroutines.debug -->
271[DebugProbes]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/index.html
272[DebugProbes.install]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/install.html
273[DebugProbes.dumpCoroutines]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/dump-coroutines.html
274[DebugProbes.dumpCoroutinesInfo]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/dump-coroutines-info.html
275[DebugProbes.printJob]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/print-job.html
276[DebugProbes.printScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/print-scope.html
277[DebugProbes.enableCreationStackTraces]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/enable-creation-stack-traces.html
278<!--- INDEX kotlinx.coroutines.debug.junit4 -->
279[CoroutinesTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug.junit4/-coroutines-timeout/index.html
280<!--- END -->
281