Kotlin 1.4.0-RC: Debugging coroutines
We continue to highlight the upcoming changes in 1.4 release. In this blogpost, we want to describe a couple of important features related to coroutines:
- New functionality to conveniently debug coroutines
- The ability to define deep recursive functions
These changes are already available for you to try in the 1.4.0-RC release!
Let’s dive into details.
Coroutines are great for asynchronous programming (but not only for that), and many people already use them or are starting to use them. When you write code with coroutines, however, trying to debug them can be a real pain. Coroutines jump between threads. It can be difficult to understand what a specific coroutine is doing or to check its context. And in some cases, tracking steps over breakpoints simply doesn’t work. As a result, you have to rely on logging or mental effort to debug the code with coroutines. To address this issue, we’re introducing new functionality in the Kotlin plugin that aims to make debugging coroutines much more convenient.
The Debug Tool Window now contains a new Coroutines tab. It is visible by default, and you can switch it on and off:
In this tab, you can find information about both currently running and suspended coroutines. The coroutines are grouped by the dispatcher they are running on. If you started a coroutine with a custom name, you can find it by this name in the Tool Window. In the following example, you can see that the main coroutine is running (we’ve stopped on a breakpoint inside it), and the other four coroutines are suspended:
With the new functionality, you can check the state of each coroutine and see the values of local and captured variables. This also works for suspended coroutines!
In this example, we check the values of the local variables of suspended coroutines:
Choose a suspended coroutine (click
invokeSuspend to see its state on that point) and the
Variables tab will show you the state of the local variables:
You can now see a full coroutine creation stack, as well as a call stack inside the coroutine:
Use the ‘Get Coroutines Dump’ option to get a full report containing the state of each coroutine and its stack:
At the moment, the coroutines dump is still rather simple, but we’re going to make it more readable and helpful in future versions.
Note that to make the debugger stop at a given breakpoint inside a coroutine, this breakpoint should have the “Suspend: All” option chosen for it:
To try this new functionality for debugging coroutines, you need to use the latest version of kotlinx.coroutines, 1.3.8-1.4.0-rc, and the latest version of the Kotlin plugin (e.g. 1.4.0-rc-release-IJ2020.1-2).
The functionality is available only for Kotlin/JVM. If you encounter any problems (please don’t forget to share the details with us!), you can switch it off by opening Build, Execution, Deployment | Debugger | Data Views | Kotlin in Preferences and choosing Disable coroutines agent. For now, we’re releasing this functionality for debugging coroutines in the experimental state, and we’re looking forward to your feedback!
Defining deep recursive functions using coroutines
In Kotlin 1.4, you can define recursive functions and invoke them even when the call depth is greater than 100,000, using the standard library support based on coroutines!
Let’s first look at an ordinary recursive function, whose usage results in a
StackOverflowError when the recursion depth gets too high. After that, we’ll discuss how you can fix the problem and rewrite the function using the Kotlin standard library.
We’ll use a simple binary tree, where each
Tree node has a reference to its
The depth of the tree is the length of the longest path from its root to its child nodes. It can be computed using the following recursive function:
The tree depth is the maximum of the depths of the left and right children increased by one. When the tree is empty it’s zero.
This function works fine when the recursion depth is small:
However, if you create a tree with a depth greater than 100,000, which in practice is not so uncommon, you’ll get
StackOverflowError as a result:
The problem is that the call stack gets too large. To solve this issue, you can use a VM option to increase the maximum stack size. However, while this might work for specific use cases, it’s not a practical solution for the general case.
Alternatively, you can rewrite the code and store results for intermediate calls by hand in the heap rather than on the stack. This solution works in most cases and is common in other languages. However, the resulting code becomes non-trivial and complicated, and the beauty and simplicity of the initial function are lost. You can find an example here.
Kotlin now provides a clean way to solve this problem based on the coroutines machinery.
The Kotlin library now includes the definition
DeepRecursiveFunction, which models recursive calls using the suspension mechanism:
You can compare the two versions, the initial one and the one using
DeepRecursiveFunction, to make sure that the logic remains the same. Your new function now becomes a variable of type
DeepRecursiveFunction, which you can call using the ‘invoke’ convention as
depthFunction(t). The function body now becomes the body of the lambda argument of
DeepRecursiveFunction, and the recursive call is replaced with
callRecursive. These changes are straightforward and easy to make. Note that while the new
depth function uses coroutines under the hood, it is not itself a
DeepRecursiveFunction is implemented is interesting, but it is not necessary in order for you to use it and benefit from it. You can find the implementation details described in this blog post.
DeepRecursiveFunction is a part of the Kotlin standard library, not part of the
kotlinx.coroutines library, since it’s not about asynchronous programming. At the moment this API is still experimental, so we’re looking forward to your feedback!
How to try it
As always, you can try Kotlin online at play.kotl.in.
In IntelliJ IDEA, you can update the Kotlin Plugin to version 1.4.0-RC. See how to do this.
If you want to work on existing projects that were created before installing the preview version, you need to configure your build for the preview version in Gradle or Maven. Note that unlike the previous preview versions, Kotlin 1.4.0-RC is also available directly from Maven Central. This means you won’t have to manually add the
kotlin-eap repository to your build files.
You can download the command-line compiler from the Github release page.
Share your feedback
We’ll be very thankful if you find and report bugs to our issue tracker. We’ll try to fix all the important issues before the final release, which means you won’t need to wait until the next Kotlin release for your issues to be addressed.