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.8.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.8.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 not to enable [DebugProbes.enableCreationStackTraces], as enabling it makes 74the performance overhead of the debug probes non-negligible. 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 127... More coroutines here ... 128 129Dumping only deferred 130"coroutine#2":DeferredCoroutine{Active}, continuation is SUSPENDED at line kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:99) 131 "coroutine#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ExampleKt.computeOne(Example.kt:14) 132 "coroutine#4":DeferredCoroutine{Active}, continuation is SUSPENDED at line ExampleKt.computeTwo(Example.kt:19) 133``` 134 135### Status of the API 136 137API is experimental, and it is not guaranteed it won't be changed (while it is marked as `@ExperimentalCoroutinesApi`). 138Like the rest of experimental API, `DebugProbes` is carefully designed, tested and ready to use in both test and production 139environments. It is marked as experimental to leave us the room to enrich the output data in a potentially backwards incompatible manner 140to further improve diagnostics and debugging experience. 141 142The output format of [DebugProbes] can be changed in the future and it is not recommended to rely on the string representation 143of the dump programmatically. 144 145### Debug agent and Android 146 147Android runtime does not support Instrument API necessary for `kotlinx-coroutines-debug` to function, triggering `java.lang.NoClassDefFoundError: Failed resolution of: Ljava/lang/management/ManagementFactory;`, 148and it is not possible to use coroutine debugger along with Android emulator. 149 150<!--- 151Make an exception googlable 152java.lang.NoClassDefFoundError: Failed resolution of: Ljava/lang/management/ManagementFactory; 153 at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider$ForCurrentVm$ForLegacyVm.resolve(ByteBuddyAgent.java:1055) 154 at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider$ForCurrentVm.resolve(ByteBuddyAgent.java:1038) 155 at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:374) 156 at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:342) 157 at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:328) 158 at kotlinx.coroutines.debug.internal.DebugProbesImpl.install(DebugProbesImpl.kt:39) 159 at kotlinx.coroutines.debug.DebugProbes.install(DebugProbes.kt:49) 160--> 161 162#### Build failures due to duplicate resource files 163 164Building an Android project that depends on `kotlinx-coroutines-debug` (usually introduced by being a transitive 165dependency of `kotlinx-coroutines-test`) may fail with `DuplicateRelativeFileException` for `META-INF/AL2.0`, 166`META-INF/LGPL2.1`, or `win32-x86/attach_hotspot_windows.dll` when trying to merge the Android resource. 167 168The problem is that Android merges the resources of all its dependencies into a single directory and complains about 169conflicts, but: 170* `kotlinx-coroutines-debug` transitively depends on JNA and JNA-platform, both of which include license files in their 171 META-INF directories. Trying to merge these files leads to conflicts, which means that any Android project that 172 depends on JNA and JNA-platform will experience build failures. 173* Additionally, `kotlinx-coroutines-debug` embeds `byte-buddy-agent` and `byte-buddy`, along with their resource files. 174 Then, if the project separately depends on `byte-buddy`, merging the resources of `kotlinx-coroutines-debug` with ones 175 from `byte-buddy` and `byte-buddy-agent` will lead to conflicts as the resource files are duplicated. 176 177One possible workaround for these issues is to add the following to the `android` block in your gradle file for the 178application subproject: 179```groovy 180 packagingOptions { 181 // for JNA and JNA-platform 182 exclude "META-INF/AL2.0" 183 exclude "META-INF/LGPL2.1" 184 // for byte-buddy 185 exclude "META-INF/licenses/ASM" 186 pickFirst "win32-x86-64/attach_hotspot_windows.dll" 187 pickFirst "win32-x86/attach_hotspot_windows.dll" 188 } 189``` 190This will cause the resource merge algorithm to exclude the problematic license files altogether and only leave a single 191copy of the files needed for `byte-buddy-agent` to work. 192 193Alternatively, avoid depending on `kotlinx-coroutines-debug`. In particular, if the only reason why this library a 194dependency of your project is that `kotlinx-coroutines-test` in turn depends on it, you may change your dependency on 195`kotlinx.coroutines.test` to exclude `kotlinx-coroutines-debug`. For example, you could replace 196```kotlin 197androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version") 198``` 199with 200```groovy 201androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version") { 202 exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug" 203} 204``` 205<!--- 206Snippets of stacktraces for googling: 207 208org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:mergeDebugAndroidTestJavaResource'. 209 ... 210Caused by: org.gradle.workers.intelrnal.DefaultWorkerExecutor$WorkExecutionException: A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade 211 ... 212Caused by: com.android.builder.merge.DuplicateRelativeFileException: More than one file was found with OS independent path 'META-INF/AL2.0'. 213 at com.android.builder.merge.StreamMergeAlgorithms.lambda$acceptOnlyOne$2(StreamMergeAlgorithms.java:85) 214 at com.android.builder.merge.StreamMergeAlgorithms.lambda$select$3(StreamMergeAlgorithms.java:106) 215 at com.android.builder.merge.IncrementalFileMergerOutputs$1.create(IncrementalFileMergerOutputs.java:88) 216 at com.android.builder.merge.DelegateIncrementalFileMergerOutput.create(DelegateIncrementalFileMergerOutput.java:64) 217 at com.android.build.gradle.internal.tasks.MergeJavaResourcesDelegate$run$output$1.create(MergeJavaResourcesDelegate.kt:230) 218 at com.android.builder.merge.IncrementalFileMerger.updateChangedFile(IncrementalFileMerger.java:242) 219 at com.android.builder.merge.IncrementalFileMerger.mergeChangedInputs(IncrementalFileMerger.java:203) 220 at com.android.builder.merge.IncrementalFileMerger.merge(IncrementalFileMerger.java:80) 221 at com.android.build.gradle.internal.tasks.MergeJavaResourcesDelegate.run(MergeJavaResourcesDelegate.kt:276) 222 at com.android.build.gradle.internal.tasks.MergeJavaResRunnable.run(MergeJavaResRunnable.kt:81) 223 at com.android.build.gradle.internal.tasks.Workers$ActionFacade.run(Workers.kt:242) 224 at org.gradle.workers.internal.AdapterWorkAction.execute(AdapterWorkAction.java:50) 225 at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:50) 226 at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:63) 227 at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:59) 228 at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:98) 229 at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:59) 230 at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) 231 at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) 232 at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:416) 233 at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:406) 234 at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165) 235 at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250) 236 at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158) 237 at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:102) 238 at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36) 239 at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) 240 at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:53) 241 at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$2(DefaultWorkerExecutor.java:200) 242 at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:215) 243 at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164) 244 at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:131) 245 246Execution failed for task ':app:mergeStagingDebugAndroidTestJavaResource'. 247Execution failed for task ':app:mergeDebugAndroidTestJavaResource'. 248Execution failed for task ':app:mergeDebugTestJavaResource' 249 250More than one file was found with OS independent path 'META-INF/LGPL2.1' 251More than one file was found with OS independent path 'win32-x86/attach_hotspot_windows.dll' 252More than one file was found with OS independent path 'win32-x86-64/attach_hotspot_windows.dll' 253--> 254<!--- MODULE kotlinx-coroutines-core --> 255<!--- INDEX kotlinx.coroutines --> 256 257[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html 258[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html 259 260<!--- MODULE kotlinx-coroutines-debug --> 261<!--- INDEX kotlinx.coroutines.debug --> 262 263[DebugProbes]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/index.html 264[DebugProbes.install]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/install.html 265[DebugProbes.dumpCoroutines]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/dump-coroutines.html 266[DebugProbes.dumpCoroutinesInfo]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/dump-coroutines-info.html 267[DebugProbes.printJob]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/print-job.html 268[DebugProbes.printScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/print-scope.html 269[DebugProbes.enableCreationStackTraces]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/enable-creation-stack-traces.html 270 271<!--- INDEX kotlinx.coroutines.debug.junit4 --> 272 273[CoroutinesTimeout]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug.junit4/-coroutines-timeout/index.html 274 275<!--- END --> 276