2
2
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3
3
*/
4
4
5
+ @file:Suppress(" UNCHECKED_CAST" )
6
+
5
7
package kotlinx.coroutines.internal
6
8
7
9
import kotlinx.coroutines.*
@@ -13,7 +15,7 @@ internal actual fun <E : Throwable> recoverStackTrace(exception: E): E {
13
15
if (recoveryDisabled(exception)) {
14
16
return exception
15
17
}
16
-
18
+ // No unwrapping on continuation-less path: exception is not reported multiple times via slow paths
17
19
val copy = tryCopyException(exception) ? : return exception
18
20
return copy.sanitizeStackTrace()
19
21
}
@@ -22,9 +24,9 @@ private fun <E : Throwable> E.sanitizeStackTrace(): E {
22
24
val stackTrace = stackTrace
23
25
val size = stackTrace.size
24
26
25
- val lastIntrinsic = stackTrace.indexOfFirst { " kotlinx.coroutines.internal.ExceptionsKt" == it.className }
27
+ val lastIntrinsic = stackTrace.frameIndex( " kotlinx.coroutines.internal.ExceptionsKt" )
26
28
val startIndex = lastIntrinsic + 1
27
- val endIndex = stackTrace.indexOfFirst { " kotlin.coroutines.jvm.internal.BaseContinuationImpl" == it.className }
29
+ val endIndex = stackTrace.frameIndex( " kotlin.coroutines.jvm.internal.BaseContinuationImpl" )
28
30
val adjustment = if (endIndex == - 1 ) 0 else size - endIndex
29
31
val trace = Array (size - lastIntrinsic - adjustment) {
30
32
if (it == 0 ) {
@@ -47,15 +49,68 @@ internal actual fun <E : Throwable> recoverStackTrace(exception: E, continuation
47
49
}
48
50
49
51
private fun <E : Throwable > recoverFromStackFrame (exception : E , continuation : CoroutineStackFrame ): E {
50
- val newException = tryCopyException(exception) ? : return exception
52
+ /*
53
+ * Here we are checking whether exception has already recovered stacktrace.
54
+ * If so, we extract initial and merge recovered stacktrace and current one
55
+ */
56
+ val (cause, recoveredStacktrace) = exception.causeAndStacktrace()
57
+
58
+ // Try to create new exception of the same type and get stacktrace from continuation
59
+ val newException = tryCopyException(cause) ? : return exception
51
60
val stacktrace = createStackTrace(continuation)
52
61
if (stacktrace.isEmpty()) return exception
53
- val copied = meaningfulActualStackTrace(exception)
54
- stacktrace.add(0 , artificialFrame(" Current coroutine stacktrace" ))
55
- newException.stackTrace = (copied + stacktrace).toTypedArray() // TODO optimizable
62
+
63
+ // Merge if necessary
64
+ if (cause != = exception) {
65
+ mergeRecoveredTraces(recoveredStacktrace, stacktrace)
66
+ }
67
+
68
+ /*
69
+ * Here we partially copy original exception stacktrace to make current one much prettier.
70
+ * E.g. for
71
+ * ```
72
+ * fun foo() = async { error(...) }
73
+ * suspend fun bar() = foo().await()
74
+ * ```
75
+ * we would like to produce following exception:
76
+ * IllegalStateException
77
+ * at foo
78
+ * at kotlinx.coroutines.resumeWith
79
+ * (Current coroutine stacktrace)
80
+ * at bar
81
+ * ...real stacktrace...
82
+ * caused by "IllegalStateException" (original one)
83
+ */
84
+ // TODO optimizable allocations and passes
85
+ stacktrace.addFirst(artificialFrame(" Current coroutine stacktrace" ))
86
+ val copied = meaningfulActualStackTrace(cause)
87
+ newException.stackTrace = (copied + stacktrace).toTypedArray()
56
88
return newException
57
89
}
58
90
91
+ /* *
92
+ * Find initial cause of the exception without restored stacktrace.
93
+ * Returns intermediate stacktrace as well in order to avoid excess cloning of array as an optimization.
94
+ */
95
+ private fun <E : Throwable > E.causeAndStacktrace (): Pair <E , Array <StackTraceElement >> {
96
+ val cause = cause
97
+ return if (cause != null && cause.javaClass == javaClass) {
98
+ val currentTrace = stackTrace
99
+ if (currentTrace.any { it.isArtificial() })
100
+ cause as E to currentTrace
101
+ else this to emptyArray()
102
+ } else {
103
+ this to emptyArray()
104
+ }
105
+ }
106
+
107
+ private fun mergeRecoveredTraces (recoveredStacktrace : Array <StackTraceElement >, result : ArrayDeque <StackTraceElement >) {
108
+ val startIndex = recoveredStacktrace.indexOfFirst { it.isArtificial() } + 1
109
+ for (i in (recoveredStacktrace.size - 1 ) downTo startIndex) {
110
+ result.addFirst(recoveredStacktrace[i])
111
+ }
112
+ }
113
+
59
114
/*
60
115
* Returns slice of the original stacktrace from the original exception.
61
116
* E.g. for
@@ -70,12 +125,11 @@ private fun <E : Throwable> recoverFromStackFrame(exception: E, continuation: Co
70
125
*/
71
126
private fun <E : Throwable > meaningfulActualStackTrace (exception : E ): List <StackTraceElement > {
72
127
val stackTrace = exception.stackTrace
73
- val index = stackTrace.indexOfFirst { " kotlin.coroutines.jvm.internal.BaseContinuationImpl" == it.className }
128
+ val index = stackTrace.frameIndex( " kotlin.coroutines.jvm.internal.BaseContinuationImpl" )
74
129
if (index == - 1 ) return emptyList()
75
130
return stackTrace.slice(0 until index)
76
131
}
77
132
78
-
79
133
@Suppress(" NOTHING_TO_INLINE" )
80
134
internal actual suspend inline fun recoverAndThrow (exception : Throwable ): Nothing {
81
135
if (recoveryDisabled(exception)) throw exception
@@ -107,8 +161,8 @@ internal actual fun <E : Throwable> unwrap(exception: E): E {
107
161
private fun <E : Throwable > recoveryDisabled (exception : E ) =
108
162
! RECOVER_STACKTRACE || ! DEBUG || exception is CancellationException || exception is NonRecoverableThrowable
109
163
110
- private fun createStackTrace (continuation : CoroutineStackFrame ): ArrayList <StackTraceElement > {
111
- val stack = ArrayList <StackTraceElement >()
164
+ private fun createStackTrace (continuation : CoroutineStackFrame ): ArrayDeque <StackTraceElement > {
165
+ val stack = ArrayDeque <StackTraceElement >()
112
166
continuation.getStackTraceElement()?.let { stack.add(sanitize(it)) }
113
167
114
168
var last = continuation
@@ -128,6 +182,7 @@ internal fun sanitize(element: StackTraceElement): StackTraceElement {
128
182
}
129
183
internal fun artificialFrame (message : String ) = java.lang.StackTraceElement (" \b\b\b ($message " , " \b " , " \b " , - 1 )
130
184
internal fun StackTraceElement.isArtificial () = className.startsWith(" \b\b\b " )
185
+ private fun Array<StackTraceElement>.frameIndex (methodName : String ) = indexOfFirst { methodName == it.className }
131
186
132
187
@Suppress(" ACTUAL_WITHOUT_EXPECT" )
133
188
actual typealias CoroutineStackFrame = kotlin.coroutines.jvm.internal.CoroutineStackFrame
0 commit comments