Introduce coroutine dump format for better visualisation of structured concurrency #3587
Open
Description
Introduction
Currently kotlinx-coroutines-debug
provides the following API for dumping coroutines:
DebugProbes.dumpCoroutines
prints all active coroutines, including their state, creation and suspension stacktraces. Though the output of this method is similar tojstack
, it only presents a flat list of coroutines, that is rather difficult to analyse in case of a large number of coroutines.DebugProbes.dumpCoroutinesInfo
provides a list of objects with information about active coroutines.DebugProbes.printJob
/DebugProbes.printScope
prints isolated parts of coroutines hierarchy referenced by aJob
orCoroutineScope
instances. Though these methods can not provide suspension stacktrace.
For now, there is no coroutine dump that would group coroutines in a meaningful way visualising relationships of structured concurrency and providing a stack trace. This proposal aims to come up with a more suitable dump format.
Proposed solution:
DebugProbes.dumpCoroutines
can provide coroutine dump in JSON format in addition to plain text:
DebugProbes.dumpCoroutines(System.out, CoroutineDumpFormat.JSON)
Pros:
- This format is already provided by HotSpotDiagnosticMXBean.dumpThreads for virtual threads in Java Loom project (see JEP 425#Observing-virtual-threads), and IDE facilities to visualise this format in a suitable way will be provided at some point.
Cons:
- Currently there are no tools in IDE to visualise this dump format and it is unreadable when it’s just printed to the terminal. Though you can paste it into the scratch file in IDE and collapse/expand objects corresponding to coroutines.
Possible alternatives:
Plain text + indents
We can still print coroutine dump as a plain text, but coroutines may be sorted by their parent and indents will show their relationships.
Example output:
- BlockingCoroutine{Completing}@2a5c959b [BlockingEventLoop@1d80b9da]
- StandaloneCoroutine{Active}@154d1536, state: SUSPENDED, name: 'root rb 0' [BlockingEventLoop@1d80b9da]
at kotlinx.coroutines.DelayKt.awaitCancellation(Delay.kt:148)
at com.intellij.internal.TestCoroutineProgressAction$cancellableBGProgress$1$1$2$2.invokeSuspend(TestCoroutineProgressAction.kt:91)
- StandaloneCoroutine{Completing}@1b7e4b1f, name: 'root rb 1' [BlockingEventLoop@1d80b9da]
- StandaloneCoroutine{Active}@56b4c42, state: SUSPENDED, name: 'root rb 1:0' [BlockingEventLoop@1d80b9da]
at kotlinx.coroutines.DelayKt.awaitCancellation(Delay.kt:148)
at com.intellij.internal.TestCoroutineProgressAction$cancellableBGProgress$1$1$2$3$1.invokeSuspend(TestCoroutineProgressAction.kt:95)
- StandaloneCoroutine{Active}@491608fb, state: SUSPENDED, name: 'root rb 2' [BlockingEventLoop@1d80b9da]
at kotlinx.coroutines.DelayKt.awaitCancellation(Delay.kt:148)
at com.intellij.internal.TestCoroutineProgressAction$cancellableBGProgress$1$1$2$4.invokeSuspend(TestCoroutineProgressAction.kt:102)
- StandaloneCoroutine{Active}@2d37afba, state: SUSPENDED, name: 'root rb 2:0' [BlockingEventLoop@1d80b9da]
at kotlinx.coroutines.DelayKt.awaitCancellation(Delay.kt:148)
at com.intellij.internal.TestCoroutineProgressAction$cancellableBGProgress$1$1$2$4$1.invokeSuspend(TestCoroutineProgressAction.kt:100)
Pros:
- A little bit better than just a plain thread dump, though still unreadable in case of a large number of coroutines.