Skip to content

Commit 3f65e17

Browse files
authored
Merge pull request #967 from Kotlin/stacktrace-recovery-improvements
Stacktrace recovery improvements and documentation
2 parents 919284b + f528898 commit 3f65e17

File tree

14 files changed

+424
-52
lines changed

14 files changed

+424
-52
lines changed

COMPATIBILITY.md

-25
This file was deleted.

README.md

+2-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
Library support for Kotlin coroutines with [multiplatform](#multiplatform) support.
88
This is a companion version for Kotlin `1.3.20` release.
99

10-
**NOTE**: `0.30.2` was the last release with Kotlin 1.2 and experimental coroutines.
11-
See [COMPATIBILITY.md](COMPATIBILITY.md) for details of migration onto the stable Kotlin 1.3 coroutines.
12-
1310
```kotlin
1411
GlobalScope.launch {
1512
delay(1000)
@@ -57,6 +54,8 @@ GlobalScope.launch {
5754
* [Guide to kotlinx.coroutines by example](docs/coroutines-guide.md) (**read it first**)
5855
* [Guide to UI programming with coroutines](ui/coroutines-guide-ui.md)
5956
* [Guide to reactive streams with coroutines](reactive/coroutines-guide-reactive.md)
57+
* [Debugging capabilities in kotlinx.coroutines](docs/debugging.md)
58+
* [Compatibility policy and experimental annotations](docs/compatibility.md)
6059
* [Change log for kotlinx.coroutines](CHANGES.md)
6160
* [Coroutines design document (KEEP)](https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md)
6261
* [Full kotlinx.coroutines API reference](http://kotlin.github.io/kotlinx.coroutines)

binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt

+4
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ public final class kotlinx/coroutines/CompletionHandlerException : java/lang/Run
135135
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V
136136
}
137137

138+
public abstract interface class kotlinx/coroutines/CopyableThrowable {
139+
public abstract fun createCopy ()Ljava/lang/Throwable;
140+
}
141+
138142
public final class kotlinx/coroutines/CoroutineContextKt {
139143
public static final fun newCoroutineContext (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
140144
}

docs/compatibility.md

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<!---
2+
/*
3+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
4+
*/
5+
--->
6+
7+
<!--- TOC -->
8+
9+
* [Compatibility](#compatibility)
10+
* [Public API types](#public-api-types)
11+
* [Experimental API](#experimental-api)
12+
* [Obsolete API](#obsolete-api)
13+
* [Internal API](#internal-api)
14+
* [Stable API](#stable-api)
15+
* [Deprecation cycle](#deprecation-cycle)
16+
* [Using annotated API](#using-annotated-api)
17+
* [Programmatically](#programmatically)
18+
* [Gradle](#gradle)
19+
* [Maven](#maven)
20+
21+
<!--- END_TOC -->
22+
23+
## Compatibility
24+
This document describes the compatibility policy of `kotlinx.coroutines` library since version 1.0.0 and semantics of compatibility-specific annotations.
25+
26+
27+
## Public API types
28+
`kotlinx.coroutines` public API comes in five flavours: stable, experimental, obsolete, internal and deprecated.
29+
All public API except stable is marked with the corresponding annotation.
30+
31+
### Experimental API
32+
Experimental API is marked with [@ExperimentalCoroutinesApi][ExperimentalCoroutinesApi] annotation.
33+
API is marked experimental when its design has potential open questions which may eventually lead to
34+
either semantics changes of the API or its deprecation.
35+
36+
By default, most of the new API is marked as experimental and becomes stable in one of the next major releases if no new issues arise.
37+
Otherwise, either semantics is fixed without changes in ABI or API goes through deprecation cycle.
38+
39+
When using experimental API may be dangerous:
40+
* You are writing a library which depends on `kotlinx.coroutines` and want to use experimental coroutines API in a stable library API.
41+
It may lead to undesired consequences when end users of your library update their `kotlinx.coroutines` version where experimental API
42+
has slightly different semantics.
43+
* You want to build core infrastructure of the application around experimental API.
44+
45+
### Obsolete API
46+
Obsolete API is marked with [@ObsoleteCoroutinesApi][ObsoleteCoroutinesApi] annotation.
47+
Obsolete API is similar to experimental, but already known to have serious design flaws and its potential replacement,
48+
but replacement is not yet implemented.
49+
50+
The semantics of this API won't be changed, but it will go through a deprecation cycle as soon as the replacement is ready.
51+
52+
### Internal API
53+
Internal API is marked with [@InternalCoroutinesApi][InternalCoroutinesApi] or is part of `kotlinx.coroutines.internal` package.
54+
This API has no guarantees on its stability, can and will be changed and/or removed in the future releases.
55+
If you can't avoid using internal API, please report it to [issue tracker](https://github.com/Kotlin/kotlinx.coroutines/issues/new).
56+
57+
### Stable API
58+
Stable API is guaranteed to preserve its ABI and documented semantics. If at some point unfixable design flaws will be discovered,
59+
this API will go through a deprecation cycle and remain binary compatible as long as possible.
60+
61+
### Deprecation cycle
62+
When some API is deprecated, it goes through multiple stages and there is at least one major release between stages.
63+
* Feature is deprecated with compilation warning. Most of the time, proper replacement
64+
(and corresponding `replaceWith` declaration) is provided to automatically migrate deprecated usages with a help of IntelliJ IDEA.
65+
* Deprecation level is increased to `error` or `hidden`. It is no longer possible to compile new code against deprecated API,
66+
though it is still present in the ABI.
67+
* API is completely removed. While we give our best efforts not to do so and have no plans of removing any API, we still are leaving
68+
this option in case of unforeseen problems such as security holes.
69+
70+
## Using annotated API
71+
All API annotations are [kotlin.Experimental](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-experimental/index.html).
72+
It is done in order to produce compilation warning about using experimental or obsolete API.
73+
Warnings can be disabled either programmatically for a specific call site or globally for the whole module.
74+
75+
### Programmatically
76+
For a specific call-site, warning can be disabled by using [UseExperimental](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-use-experimental/index.html) annotation:
77+
```kotlin
78+
@UseExperimental(ExperimentalCoroutinesApi::class) // Disables warning about experimental coroutines API
79+
fun experimentalApiUsage() {
80+
someKotlinxCoroutinesExperimentalMethod()
81+
}
82+
```
83+
84+
### Gradle
85+
For the Gradle project, a warning can be disabled by passing a compiler flag in your `build.gradle` file:
86+
87+
```groovy
88+
tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all {
89+
kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi"]
90+
}
91+
92+
```
93+
94+
### Maven
95+
For the Maven project, a warning can be disabled by passing a compiler flag in your `pom.xml` file:
96+
```xml
97+
<plugin>
98+
<artifactId>kotlin-maven-plugin</artifactId>
99+
<groupId>org.jetbrains.kotlin</groupId>
100+
... your configuration ...
101+
<configuration>
102+
<args>
103+
<arg>-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi</arg>
104+
</args>
105+
</configuration>
106+
</plugin>
107+
```
108+
109+
110+
<!--- MODULE kotlinx-coroutines-core -->
111+
<!--- INDEX kotlinx.coroutines -->
112+
[ExperimentalCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-experimental-coroutines-api/index.html
113+
[ObsoleteCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-obsolete-coroutines-api/index.html
114+
[InternalCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-internal-coroutines-api/index.html
115+
<!--- END -->

docs/debugging.md

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
**Table of contents**
2+
3+
<!--- TOC -->
4+
5+
* [Debugging coroutines](#debugging-coroutines)
6+
* [Debug mode](#debug-mode)
7+
* [Stacktrace recovery](#stacktrace-recovery)
8+
* [Stacktrace recovery machinery](#stacktrace-recovery-machinery)
9+
* [Debug agent](#debug-agent)
10+
* [Debug agent and Android](#debug-agent-and-android)
11+
12+
<!--- END_TOC -->
13+
14+
15+
## Debugging coroutines
16+
Debugging asynchronous programs is challenging, because multiple concurrent coroutines are typically working at the same time.
17+
To help with that, `kotlinx.coroutines` comes with additional features for debugging: debug mode, stacktrace recovery
18+
and debug agent.
19+
20+
## Debug mode
21+
22+
The first debugging feature of `kotlinx.coroutines` is debug mode.
23+
It can be enabled either by setting system property [DEBUG_PROPERTY_NAME] or by running Java with enabled assertions (`-ea` flag).
24+
The latter is helpful to have debug mode enabled by default in unit tests.
25+
26+
Debug mode attaches a unique [name][CoroutineName] to every launched coroutine.
27+
Coroutine name can be seen in a regular Java debugger,
28+
in a string representation of the coroutine or in the thread name executing named coroutine.
29+
Overhead of this feature is negligible and it can be safely turned on by default to simplify logging and diagnostic.
30+
31+
## Stacktrace recovery
32+
33+
Stacktrace recovery is another useful feature of debug mode. It is enabled by default in the debug mode,
34+
but can be separately disabled by setting `kotlinx.coroutines.stacktrace.recovery` system property to `false`.
35+
36+
Stacktrace recovery tries to stitch asynchronous exception stacktrace with a stacktrace of the receiver by copying it, providing
37+
not only information where an exception was thrown, but also where it was asynchronously rethrown or caught.
38+
39+
It is easy to demonstrate with actual stacktraces of the same program that awaits asynchronous operation in `main` function
40+
(runnable code is [here](../kotlinx-coroutines-debug/test/RecoveryExample.kt)):
41+
42+
| Without recovery | With recovery |
43+
| - | - |
44+
| ![before](images/before.png "before") | ![after](images/after.png "after") |
45+
46+
The only downside of this approach is losing referential transparency of the exception.
47+
48+
### Stacktrace recovery machinery
49+
50+
This section explains the inner mechanism of stacktrace recovery and can be skipped.
51+
52+
When an exception is rethrown between coroutines (e.g. through `withContext` or `Deferred.await` boundary), stacktrace recovery
53+
machinery tries to create a copy of the original exception (with the original exception as the cause), then rewrite stacktrace
54+
of the copy with coroutine-related stack frames (using [Throwable.setStackTrace](https://docs.oracle.com/javase/9/docs/api/java/lang/Throwable.html#setStackTrace-java.lang.StackTraceElement:A-))
55+
and then throws the resulting exception instead of the original one.
56+
57+
Exception copy logic is straightforward:
58+
1) If the exception class implements [CopyableThrowable], [CopyableThrowable.createCopy] is used.
59+
2) If the exception class has class-specific fields not inherited from Throwable, the exception is not copied.
60+
3) Otherwise, one of the public exception's constructor is invoked reflectively with an optional `initCause` call.
61+
62+
## Debug agent
63+
64+
[kotlinx-coroutines-debug](../kotlinx-coroutines-debug) module provides one of the most powerful debug capabilities in `kotlinx.coroutines`.
65+
66+
This is a separate module with a JVM agent that keeps track of all alive coroutines, introspects and dumps them similar to thread dump command,
67+
additionally enhancing stacktraces with information where coroutine was created.
68+
69+
The full tutorial of how to use debug agent can be found in the corresponding [readme](../kotlinx-coroutines-debug/README.md).
70+
71+
### Debug agent and Android
72+
73+
Unfortunately, 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;`.
74+
75+
Nevertheless, it will be possible to support debug agent on Android as soon as [GradleAspectJ-Android](https://github.com/Archinamon/android-gradle-aspectj) will support androin-gradle 3.3
76+
77+
<!---
78+
Make an exception googlable
79+
java.lang.NoClassDefFoundError: Failed resolution of: Ljava/lang/management/ManagementFactory;
80+
at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider$ForCurrentVm$ForLegacyVm.resolve(ByteBuddyAgent.java:1055)
81+
at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent$ProcessProvider$ForCurrentVm.resolve(ByteBuddyAgent.java:1038)
82+
at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:374)
83+
at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:342)
84+
at kotlinx.coroutines.repackaged.net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:328)
85+
at kotlinx.coroutines.debug.internal.DebugProbesImpl.install(DebugProbesImpl.kt:39)
86+
at kotlinx.coroutines.debug.DebugProbes.install(DebugProbes.kt:49)
87+
-->
88+
89+
<!--- MODULE kotlinx-coroutines-core -->
90+
<!--- INDEX kotlinx.coroutines -->
91+
[DEBUG_PROPERTY_NAME]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-d-e-b-u-g_-p-r-o-p-e-r-t-y_-n-a-m-e.html
92+
[CoroutineName]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-name/index.html
93+
[CopyableThrowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-copyable-throwable/index.html
94+
[CopyableThrowable.createCopy]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-copyable-throwable/create-copy.html
95+
<!--- MODULE kotlinx-coroutines-debug -->
96+
<!--- END -->

docs/images/after.png

290 KB
Loading

docs/images/before.png

158 KB
Loading

integration/kotlinx-coroutines-play-services/test/TaskTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class TaskTest : TestBase() {
140140
} catch (e: Exception) {
141141
assertTrue(e is TestException)
142142
assertEquals("something went wrong", e.message)
143-
assertSame(e, deferred.getCompletionExceptionOrNull())
143+
assertSame(e.cause, deferred.getCompletionExceptionOrNull()) // debug mode stack augmentation
144144
}
145145
}
146146

kotlinx-coroutines-core/common/src/Annotations.kt

-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ public annotation class ObsoleteCoroutinesApi
3030
* Marks declarations that are **internal** in coroutines API, which means that should not be used outside of
3131
* `kotlinx.coroutines`, because their signatures and semantics will be changing between release without any
3232
* warnings and without providing any migration aids.
33-
*
34-
* @suppress **This an internal API and should not be used from general code.**
3533
*/
3634
@Retention(value = AnnotationRetention.BINARY)
3735
@Experimental(level = Experimental.Level.ERROR)

kotlinx-coroutines-core/jvm/src/Debug.kt

+27-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
package kotlinx.coroutines
66

7-
import kotlin.coroutines.Continuation
87
import kotlinx.coroutines.internal.*
8+
import kotlin.coroutines.*
99

1010
/**
1111
* Name of the property that controls coroutine debugging. See [newCoroutineContext][CoroutineScope.newCoroutineContext].
@@ -26,6 +26,32 @@ public const val DEBUG_PROPERTY_NAME = "kotlinx.coroutines.debug"
2626
*/
2727
internal const val STACKTRACE_RECOVERY_PROPERTY_NAME = "kotlinx.coroutines.stacktrace.recovery"
2828

29+
/**
30+
* Throwable which can be cloned during stacktrace recovery in a class-specific way.
31+
* For additional information about stacktrace recovery see [STACKTRACE_RECOVERY_PROPERTY_NAME]
32+
*
33+
* Example of usage:
34+
* ```
35+
* class BadResponseCodeException(val responseCode: Int) : Exception(), CopyableThrowable<BadResponseCodeException> {
36+
*
37+
* override fun createCopy(): BadResponseCodeException {
38+
* val result = BadResponseCodeException(responseCode)
39+
* result.initCause(this)
40+
* return result
41+
* }
42+
* ```
43+
*/
44+
@ExperimentalCoroutinesApi
45+
public interface CopyableThrowable<T> where T : Throwable, T : CopyableThrowable<T> {
46+
47+
/**
48+
* Creates a copy of the current instance.
49+
* For better debuggability, it is recommended to use original exception as [cause][Throwable.cause] of the resulting one.
50+
* Stacktrace of copied exception will be overwritten by stacktrace recovery machinery by [Throwable.setStackTrace] call.
51+
*/
52+
public fun createCopy(): T
53+
}
54+
2955
/**
3056
* Automatic debug configuration value for [DEBUG_PROPERTY_NAME]. See [newCoroutineContext][CoroutineScope.newCoroutineContext].
3157
*/

0 commit comments

Comments
 (0)