Skip to content

Commit 64e6d00

Browse files
elizarovqwwdfsad
authored andcommitted
Shorten resume call stack
Calls of afterCompletionInternal are pulled up the call stack. This is very important, since scoped coroutines do "uCont.resumeWith" from afterCompletionInternal, which makes all the JVM method visible in the debugger call frames and in exceptions. Additionally, this allows for some simplification of the JobSupport code, as a number of methods do not need "mode" parameter anymore. Moreover, the kludge of MODE_IGNORE is no longer needed and is dropped. Make TimeoutCoroutine extends ScopedCoroutines. Fixes #1574
1 parent 8fa07b5 commit 64e6d00

File tree

10 files changed

+174
-227
lines changed

10 files changed

+174
-227
lines changed

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44
@file:Suppress("DEPRECATION_ERROR")
55

@@ -108,7 +108,9 @@ public abstract class AbstractCoroutine<in T>(
108108
* Completes execution of this with coroutine with the specified result.
109109
*/
110110
public final override fun resumeWith(result: Result<T>) {
111-
makeCompletingOnce(result.toState(), defaultResumeMode)
111+
val state = makeCompletingOnce(result.toState())
112+
if (state === COMPLETING_WAITING_CHILDREN) return
113+
afterCompletionInternal(state, defaultResumeMode)
112114
}
113115

114116
internal final override fun handleOnCompletionException(exception: Throwable) {

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

+112-76
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,13 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package kotlinx.coroutines
66

7-
import kotlin.coroutines.*
8-
import kotlin.coroutines.intrinsics.*
9-
107
@PublishedApi internal const val MODE_ATOMIC_DEFAULT = 0 // schedule non-cancellable dispatch for suspendCoroutine
118
@PublishedApi internal const val MODE_CANCELLABLE = 1 // schedule cancellable dispatch for suspendCancellableCoroutine
129
@PublishedApi internal const val MODE_DIRECT = 2 // when the context is right just invoke the delegate continuation direct
1310
@PublishedApi internal const val MODE_UNDISPATCHED = 3 // when the thread is right, but need to mark it with current coroutine
14-
@PublishedApi internal const val MODE_IGNORE = 4 // don't do anything
1511

1612
internal val Int.isCancellableMode get() = this == MODE_CANCELLABLE
1713
internal val Int.isDispatchedMode get() = this == MODE_ATOMIC_DEFAULT || this == MODE_CANCELLABLE
18-
19-
internal fun <T> Continuation<T>.resumeMode(value: T, mode: Int) {
20-
when (mode) {
21-
MODE_ATOMIC_DEFAULT -> resume(value)
22-
MODE_CANCELLABLE -> resumeCancellable(value)
23-
MODE_DIRECT -> resumeDirect(value)
24-
MODE_UNDISPATCHED -> (this as DispatchedContinuation).resumeUndispatched(value)
25-
MODE_IGNORE -> {}
26-
else -> error("Invalid mode $mode")
27-
}
28-
}
29-
30-
internal fun <T> Continuation<T>.resumeWithExceptionMode(exception: Throwable, mode: Int) {
31-
when (mode) {
32-
MODE_ATOMIC_DEFAULT -> resumeWithException(exception)
33-
MODE_CANCELLABLE -> resumeCancellableWithException(exception)
34-
MODE_DIRECT -> resumeDirectWithException(exception)
35-
MODE_UNDISPATCHED -> (this as DispatchedContinuation).resumeUndispatchedWithException(exception)
36-
MODE_IGNORE -> {}
37-
else -> error("Invalid mode $mode")
38-
}
39-
}
40-
41-
internal fun <T> Continuation<T>.resumeUninterceptedMode(value: T, mode: Int) {
42-
when (mode) {
43-
MODE_ATOMIC_DEFAULT -> intercepted().resume(value)
44-
MODE_CANCELLABLE -> intercepted().resumeCancellable(value)
45-
MODE_DIRECT -> resume(value)
46-
MODE_UNDISPATCHED -> withCoroutineContext(context, null) { resume(value) }
47-
MODE_IGNORE -> {}
48-
else -> error("Invalid mode $mode")
49-
}
50-
}
51-
52-
internal fun <T> Continuation<T>.resumeUninterceptedWithExceptionMode(exception: Throwable, mode: Int) {
53-
when (mode) {
54-
MODE_ATOMIC_DEFAULT -> intercepted().resumeWithException(exception)
55-
MODE_CANCELLABLE -> intercepted().resumeCancellableWithException(exception)
56-
MODE_DIRECT -> resumeWithException(exception)
57-
MODE_UNDISPATCHED -> withCoroutineContext(context, null) { resumeWithException(exception) }
58-
MODE_IGNORE -> {}
59-
else -> error("Invalid mode $mode")
60-
}
61-
}

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

+3-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package kotlinx.coroutines
@@ -80,26 +80,12 @@ private fun <U, T: U> setupTimeout(
8080

8181
private open class TimeoutCoroutine<U, in T: U>(
8282
@JvmField val time: Long,
83-
@JvmField val uCont: Continuation<U> // unintercepted continuation
84-
) : AbstractCoroutine<T>(uCont.context, active = true), Runnable, Continuation<T>, CoroutineStackFrame {
85-
override val defaultResumeMode: Int get() = MODE_DIRECT
86-
override val callerFrame: CoroutineStackFrame? get() = (uCont as? CoroutineStackFrame)
87-
override fun getStackTraceElement(): StackTraceElement? = null
88-
override val isScopedCoroutine: Boolean get() = true
89-
90-
@Suppress("LeakingThis", "Deprecation")
83+
uCont: Continuation<U> // unintercepted continuation
84+
) : ScopeCoroutine<T>(uCont.context, uCont), Runnable {
9185
override fun run() {
9286
cancelCoroutine(TimeoutCancellationException(time, this))
9387
}
9488

95-
@Suppress("UNCHECKED_CAST")
96-
override fun afterCompletionInternal(state: Any?, mode: Int) {
97-
if (state is CompletedExceptionally)
98-
uCont.resumeUninterceptedWithExceptionMode(state.cause, mode)
99-
else
100-
uCont.resumeUninterceptedMode(state as T, mode)
101-
}
102-
10389
override fun nameString(): String =
10490
"${super.nameString()}(timeMillis=$time)"
10591
}

kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt

+12-29
Original file line numberDiff line numberDiff line change
@@ -175,32 +175,16 @@ internal class DispatchedContinuation<in T>(
175175
}
176176

177177
@Suppress("NOTHING_TO_INLINE") // we need it inline to save us an entry on the stack
178-
inline fun resumeCancellable(value: T) {
179-
if (dispatcher.isDispatchNeeded(context)) {
180-
_state = value
181-
resumeMode = MODE_CANCELLABLE
182-
dispatcher.dispatch(context, this)
183-
} else {
184-
executeUnconfined(value, MODE_CANCELLABLE) {
185-
if (!resumeCancelled()) {
186-
resumeUndispatched(value)
187-
}
188-
}
189-
}
190-
}
191-
192-
@Suppress("NOTHING_TO_INLINE") // we need it inline to save us an entry on the stack
193-
inline fun resumeCancellableWithException(exception: Throwable) {
194-
val context = continuation.context
195-
val state = CompletedExceptionally(exception)
178+
inline fun resumeCancellableWith(result: Result<T>) {
179+
val state = result.toState()
196180
if (dispatcher.isDispatchNeeded(context)) {
197-
_state = CompletedExceptionally(exception)
181+
_state = state
198182
resumeMode = MODE_CANCELLABLE
199183
dispatcher.dispatch(context, this)
200184
} else {
201185
executeUnconfined(state, MODE_CANCELLABLE) {
202186
if (!resumeCancelled()) {
203-
resumeUndispatchedWithException(exception)
187+
resumeUndispatchedWith(result)
204188
}
205189
}
206190
}
@@ -218,16 +202,9 @@ internal class DispatchedContinuation<in T>(
218202
}
219203

220204
@Suppress("NOTHING_TO_INLINE") // we need it inline to save us an entry on the stack
221-
inline fun resumeUndispatched(value: T) {
222-
withCoroutineContext(context, countOrElement) {
223-
continuation.resume(value)
224-
}
225-
}
226-
227-
@Suppress("NOTHING_TO_INLINE") // we need it inline to save us an entry on the stack
228-
inline fun resumeUndispatchedWithException(exception: Throwable) {
205+
inline fun resumeUndispatchedWith(result: Result<T>) {
229206
withCoroutineContext(context, countOrElement) {
230-
continuation.resumeWithStackTrace(exception)
207+
continuation.resumeWith(result)
231208
}
232209
}
233210

@@ -243,6 +220,12 @@ internal class DispatchedContinuation<in T>(
243220
"DispatchedContinuation[$dispatcher, ${continuation.toDebugString()}]"
244221
}
245222

223+
@Suppress("NOTHING_TO_INLINE") // we need it inline to save us an entry on the stack
224+
internal inline fun <T> Continuation<T>.resumeCancellableWith(result: Result<T>) = when (this) {
225+
is DispatchedContinuation -> resumeCancellableWith(result)
226+
else -> resumeWith(result)
227+
}
228+
246229
internal fun DispatchedContinuation<Unit>.yieldUndispatched(): Boolean =
247230
executeUnconfined(Unit, MODE_CANCELLABLE, doYield = true) {
248231
run()

kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt

+14-27
Original file line numberDiff line numberDiff line change
@@ -105,21 +105,29 @@ internal fun <T> DispatchedTask<T>.dispatch(mode: Int = MODE_CANCELLABLE) {
105105
}
106106
}
107107

108+
@Suppress("UNCHECKED_CAST")
108109
internal fun <T> DispatchedTask<T>.resume(delegate: Continuation<T>, useMode: Int) {
109110
// slow-path - use delegate
110111
val state = takeState()
111-
val exception = getExceptionalResult(state)
112-
if (exception != null) {
112+
val exception = getExceptionalResult(state)?.let {
113113
/*
114114
* Recover stacktrace for non-dispatched tasks.
115115
* We usually do not recover stacktrace in a `resume` as all resumes go through `DispatchedTask.run`
116116
* and we recover stacktraces there, but this is not the case for a `suspend fun main()` that knows nothing about
117117
* kotlinx.coroutines and DispatchedTask
118118
*/
119-
val recovered = if (delegate is DispatchedTask<*>) exception else recoverStackTrace(exception, delegate)
120-
delegate.resumeWithExceptionMode(recovered, useMode)
121-
} else {
122-
delegate.resumeMode(getSuccessfulResult(state), useMode)
119+
if (delegate is DispatchedTask<*>) it else recoverStackTrace(it, delegate)
120+
}
121+
val result = if (exception != null)
122+
Result.failure(exception)
123+
else
124+
Result.success(state as T)
125+
when (useMode) {
126+
MODE_ATOMIC_DEFAULT -> delegate.resumeWith(result)
127+
MODE_CANCELLABLE -> delegate.resumeCancellableWith(result)
128+
MODE_DIRECT -> ((delegate as? DispatchedContinuation)?.continuation ?: delegate).resumeWith(result)
129+
MODE_UNDISPATCHED -> (delegate as DispatchedContinuation).resumeUndispatchedWith(result)
130+
else -> error("Invalid mode $useMode")
123131
}
124132
}
125133

@@ -158,27 +166,6 @@ internal inline fun DispatchedTask<*>.runUnconfinedEventLoop(
158166
}
159167
}
160168

161-
162-
internal fun <T> Continuation<T>.resumeCancellable(value: T) = when (this) {
163-
is DispatchedContinuation -> resumeCancellable(value)
164-
else -> resume(value)
165-
}
166-
167-
internal fun <T> Continuation<T>.resumeCancellableWithException(exception: Throwable) = when (this) {
168-
is DispatchedContinuation -> resumeCancellableWithException(exception)
169-
else -> resumeWithStackTrace(exception)
170-
}
171-
172-
internal fun <T> Continuation<T>.resumeDirect(value: T) = when (this) {
173-
is DispatchedContinuation -> continuation.resume(value)
174-
else -> resume(value)
175-
}
176-
177-
internal fun <T> Continuation<T>.resumeDirectWithException(exception: Throwable) = when (this) {
178-
is DispatchedContinuation -> continuation.resumeWithStackTrace(exception)
179-
else -> resumeWithStackTrace(exception)
180-
}
181-
182169
@Suppress("NOTHING_TO_INLINE")
183170
internal inline fun Continuation<*>.resumeWithStackTrace(exception: Throwable) {
184171
resumeWith(Result.failure(recoverStackTrace(exception, this)))

kotlinx-coroutines-core/common/src/internal/Scopes.kt

+12-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package kotlinx.coroutines.internal
66

77
import kotlinx.coroutines.*
88
import kotlin.coroutines.*
9+
import kotlin.coroutines.intrinsics.*
910
import kotlin.jvm.*
1011

1112
/**
@@ -25,11 +26,16 @@ internal open class ScopeCoroutine<in T>(
2526

2627
@Suppress("UNCHECKED_CAST")
2728
override fun afterCompletionInternal(state: Any?, mode: Int) {
28-
if (state is CompletedExceptionally) {
29-
val exception = if (mode == MODE_IGNORE) state.cause else recoverStackTrace(state.cause, uCont)
30-
uCont.resumeUninterceptedWithExceptionMode(exception, mode)
31-
} else {
32-
uCont.resumeUninterceptedMode(state as T, mode)
29+
val result = if (state is CompletedExceptionally)
30+
Result.failure(recoverStackTrace(state.cause, uCont))
31+
else
32+
Result.success(state as T)
33+
when (mode) {
34+
MODE_ATOMIC_DEFAULT -> uCont.intercepted().resumeWith(result)
35+
MODE_CANCELLABLE -> uCont.intercepted().resumeCancellableWith(result)
36+
MODE_DIRECT -> uCont.resumeWith(result)
37+
MODE_UNDISPATCHED -> withCoroutineContext(uCont.context, null) { uCont.resumeWith(result) }
38+
else -> error("Invalid mode $mode")
3339
}
3440
}
3541
}

kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package kotlinx.coroutines.intrinsics
@@ -14,7 +14,7 @@ import kotlin.coroutines.intrinsics.*
1414
*/
1515
@InternalCoroutinesApi
1616
public fun <T> (suspend () -> T).startCoroutineCancellable(completion: Continuation<T>) = runSafely(completion) {
17-
createCoroutineUnintercepted(completion).intercepted().resumeCancellable(Unit)
17+
createCoroutineUnintercepted(completion).intercepted().resumeCancellableWith(Result.success(Unit))
1818
}
1919

2020
/**
@@ -23,7 +23,7 @@ public fun <T> (suspend () -> T).startCoroutineCancellable(completion: Continuat
2323
*/
2424
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) =
2525
runSafely(completion) {
26-
createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellable(Unit)
26+
createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit))
2727
}
2828

2929
/**
@@ -32,7 +32,7 @@ internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, co
3232
*/
3333
internal fun Continuation<Unit>.startCoroutineCancellable(fatalCompletion: Continuation<*>) =
3434
runSafely(fatalCompletion) {
35-
intercepted().resumeCancellable(Unit)
35+
intercepted().resumeCancellableWith(Result.success(Unit))
3636
}
3737

3838
/**

kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt

+11-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package kotlinx.coroutines.intrinsics
@@ -112,7 +112,6 @@ private inline fun <T> AbstractCoroutine<T>.undispatchedResult(
112112
} catch (e: Throwable) {
113113
CompletedExceptionally(e)
114114
}
115-
116115
/*
117116
* We're trying to complete our undispatched block here and have three code-paths:
118117
* 1) Suspended.
@@ -127,20 +126,16 @@ private inline fun <T> AbstractCoroutine<T>.undispatchedResult(
127126
* If timeout is exceeded, but withTimeout() block was not suspended, we would like to return block value,
128127
* not a timeout exception.
129128
*/
130-
return when {
131-
result === COROUTINE_SUSPENDED -> COROUTINE_SUSPENDED
132-
makeCompletingOnce(result, MODE_IGNORE) -> {
133-
val state = state
134-
if (state is CompletedExceptionally) {
135-
when {
136-
shouldThrow(state.cause) -> throw tryRecover(state.cause)
137-
result is CompletedExceptionally -> throw tryRecover(result.cause)
138-
else -> result
139-
}
140-
} else {
141-
state.unboxState()
142-
}
129+
if (result === COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
130+
val state = makeCompletingOnce(result)
131+
if (state === COMPLETING_WAITING_CHILDREN) return COROUTINE_SUSPENDED
132+
return if (state is CompletedExceptionally) {
133+
when {
134+
shouldThrow(state.cause) -> throw tryRecover(state.cause)
135+
result is CompletedExceptionally -> throw tryRecover(result.cause)
136+
else -> result
143137
}
144-
else -> COROUTINE_SUSPENDED
138+
} else {
139+
state.unboxState()
145140
}
146141
}

kotlinx-coroutines-core/common/src/selects/Select.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ internal class SelectBuilderImpl<in R>(
285285
// Resumes in MODE_CANCELLABLE
286286
override fun resumeSelectCancellableWithException(exception: Throwable) {
287287
doResume({ CompletedExceptionally(exception) }) {
288-
uCont.intercepted().resumeCancellableWithException(exception)
288+
uCont.intercepted().resumeCancellableWith(Result.failure(exception))
289289
}
290290
}
291291

0 commit comments

Comments
 (0)