Skip to content

Commit e3b80d9

Browse files
committed
Debug optimizations:
* Extract call to suspendCoroutineUninterceptedOrReturn behind recoverability check * Cache exceptions constructors into WeakHashMap in order to avoid expensive ctor lookup
1 parent db5335a commit e3b80d9

File tree

3 files changed

+48
-32
lines changed

3 files changed

+48
-32
lines changed

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

+1-3
Original file line numberDiff line numberDiff line change
@@ -1080,9 +1080,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
10801080
if (state !is Incomplete) {
10811081
// already complete -- just return result
10821082
if (state is CompletedExceptionally) { // Slow path to recover stacktrace
1083-
suspendCoroutineUninterceptedOrReturn<Unit> {
1084-
throw recoverStackTrace(state.cause, it)
1085-
}
1083+
recoverAndThrow(state.cause)
10861084
}
10871085
return state
10881086

core/kotlinx-coroutines-core/src/internal/Exceptions.kt

+2-29
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ internal actual suspend inline fun recoverAndThrow(exception: Throwable): Nothin
6565
if (recoveryDisabled(exception)) throw exception
6666
suspendCoroutineUninterceptedOrReturn<Nothing> {
6767
if (it !is CoroutineStackFrame) throw exception
68-
throw recoverFromStackFrame(exception, it)
68+
throw recoverFromStackFrame(exception, it)
6969
}
7070
}
7171

@@ -86,34 +86,7 @@ internal actual fun <E : Throwable> unwrap(exception: E): E {
8686
private fun <E : Throwable> recoveryDisabled(exception: E) =
8787
!RECOVER_STACKTRACE || !DEBUG || exception is CancellationException || exception is NonRecoverableThrowable
8888

89-
@Suppress("UNCHECKED_CAST")
90-
private fun <E : Throwable> tryCopyException(exception: E): E? {
91-
/*
92-
* Try to reflectively find constructor(), constructor(message, cause) or constructor(cause).
93-
* Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace.
94-
*/
95-
var newException: E? = null
96-
try {
97-
val constructors = exception.javaClass.constructors.sortedByDescending { it.parameterTypes.size }
98-
for (constructor in constructors) {
99-
val parameters = constructor.parameterTypes
100-
if (parameters.size == 2 && parameters[0] == String::class.java && parameters[1] == Throwable::class.java) {
101-
newException = constructor.newInstance(exception.message, exception) as E
102-
} else if (parameters.size == 1 && parameters[0] == Throwable::class.java) {
103-
newException = constructor.newInstance(exception) as E
104-
} else if (parameters.isEmpty()) {
105-
newException = (constructor.newInstance() as E).also { it.initCause(exception) }
106-
}
107-
108-
if (newException != null) {
109-
break
110-
}
111-
}
112-
} catch (e: Exception) {
113-
// Do nothing
114-
}
115-
return newException
116-
}
89+
11790

11891
private fun createStackTrace(continuation: CoroutineStackFrame): ArrayList<StackTraceElement> {
11992
val stack = ArrayList<StackTraceElement>()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines.internal
6+
7+
import java.util.*
8+
import java.util.concurrent.locks.*
9+
import kotlin.concurrent.*
10+
11+
private val cacheLock = ReentrantReadWriteLock()
12+
private val exceptionConstructors: WeakHashMap<Class<*>, (Throwable) -> Throwable?> = WeakHashMap()
13+
14+
@Suppress("UNCHECKED_CAST")
15+
internal fun <E : Throwable> tryCopyException(exception: E): E? {
16+
val cachedCtor = cacheLock.read {
17+
exceptionConstructors[exception.javaClass]
18+
}
19+
20+
if (cachedCtor != null) return cachedCtor(exception) as E?
21+
22+
/*
23+
* Try to reflectively find constructor(), constructor(message, cause) or constructor(cause).
24+
* Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace.
25+
*/
26+
var ctor: ((Throwable) -> Throwable?)? = null
27+
28+
val constructors = exception.javaClass.constructors.sortedByDescending { it.parameterTypes.size }
29+
for (constructor in constructors) {
30+
val parameters = constructor.parameterTypes
31+
if (parameters.size == 2 && parameters[0] == String::class.java && parameters[1] == Throwable::class.java) {
32+
ctor = { e -> runCatching { constructor.newInstance(e.message, e) as E }.getOrNull() }
33+
break
34+
} else if (parameters.size == 1 && parameters[0] == Throwable::class.java) {
35+
ctor = { e -> runCatching { constructor.newInstance(e) as E }.getOrNull() }
36+
break
37+
} else if (parameters.isEmpty()) {
38+
ctor = { e -> runCatching { constructor.newInstance() as E }.getOrNull()?.also { it.initCause(e) } }
39+
break
40+
}
41+
}
42+
43+
cacheLock.write { exceptionConstructors[exception.javaClass] = ctor }
44+
return ctor?.invoke(exception) as E?
45+
}

0 commit comments

Comments
 (0)