Skip to content

Commit 030fdc9

Browse files
committed
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. Fixes #1574
1 parent 8fa07b5 commit 030fdc9

File tree

5 files changed

+130
-103
lines changed

5 files changed

+130
-103
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
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
193193

194194
// Finalizes Finishing -> Completed (terminal state) transition.
195195
// ## IMPORTANT INVARIANT: Only one thread can be concurrently invoking this method.
196-
private fun tryFinalizeFinishingState(state: Finishing, proposedUpdate: Any?, mode: Int): Boolean {
196+
// Returns final state that was created and updated to
197+
private fun tryFinalizeFinishingState(state: Finishing, proposedUpdate: Any?): Any? {
197198
/*
198199
* Note: proposed state can be Incomplete, e.g.
199200
* async {
@@ -234,8 +235,8 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
234235
// Then CAS to completed state -> it must succeed
235236
require(_state.compareAndSet(state, finalState.boxIncomplete())) { "Unexpected state: ${_state.value}, expected: $state, update: $finalState" }
236237
// And process all post-completion actions
237-
completeStateFinalization(state, finalState, mode)
238-
return true
238+
completeStateFinalization(state, finalState)
239+
return finalState
239240
}
240241

241242
private fun getFinalRootCause(state: Finishing, exceptions: List<Throwable>): Throwable? {
@@ -268,18 +269,19 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
268269
}
269270

270271
// fast-path method to finalize normally completed coroutines without children
271-
private fun tryFinalizeSimpleState(state: Incomplete, update: Any?, mode: Int): Boolean {
272+
// returns true if complete, and afterCompletionInternal(update, mode) shall be called
273+
private fun tryFinalizeSimpleState(state: Incomplete, update: Any?): Boolean {
272274
assert { state is Empty || state is JobNode<*> } // only simple state without lists where children can concurrently add
273275
assert { update !is CompletedExceptionally } // only for normal completion
274276
if (!_state.compareAndSet(state, update.boxIncomplete())) return false
275277
onCancelling(null) // simple state is not a failure
276278
onCompletionInternal(update)
277-
completeStateFinalization(state, update, mode)
279+
completeStateFinalization(state, update)
278280
return true
279281
}
280282

281283
// suppressed == true when any exceptions were suppressed while building the final completion cause
282-
private fun completeStateFinalization(state: Incomplete, update: Any?, mode: Int) {
284+
private fun completeStateFinalization(state: Incomplete, update: Any?) {
283285
/*
284286
* Now the job in THE FINAL state. We need to properly handle the resulting state.
285287
* Order of various invocations here is important.
@@ -304,11 +306,6 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
304306
} else {
305307
state.list?.notifyCompletion(cause)
306308
}
307-
/*
308-
* 3) Resumes the rest of the code in scoped coroutines
309-
* (runBlocking, coroutineScope, withContext, withTimeout, etc)
310-
*/
311-
afterCompletionInternal(update, mode)
312309
}
313310

314311
private fun notifyCancelling(list: NodeList, cause: Throwable) {
@@ -641,28 +638,41 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
641638
// cause is Throwable or ParentJob when cancelChild was invoked
642639
// returns true is exception was handled, false otherwise
643640
internal fun cancelImpl(cause: Any?): Boolean {
641+
var finalState: Any? = COMPLETING_ALREADY
644642
if (onCancelComplete) {
645-
// make sure it is completing, if cancelMakeCompleting returns true it means it had make it
643+
// make sure it is completing, if cancelMakeCompleting returns state it means it had make it
646644
// completing and had recorded exception
647-
if (cancelMakeCompleting(cause)) return true
648-
// otherwise just record exception via makeCancelling below
645+
finalState = cancelMakeCompleting(cause)
646+
if (finalState === COMPLETING_WAITING_CHILDREN) return true
647+
}
648+
if (finalState === COMPLETING_ALREADY) {
649+
finalState = makeCancelling(cause)
650+
}
651+
return when {
652+
finalState === COMPLETING_ALREADY -> true
653+
finalState === COMPLETING_WAITING_CHILDREN -> true
654+
finalState === TOO_LATE_TO_CANCEL -> false
655+
else -> {
656+
afterCompletionInternal(finalState, MODE_ATOMIC_DEFAULT)
657+
true
658+
}
649659
}
650-
return makeCancelling(cause)
651660
}
652661

653662
// cause is Throwable or ParentJob when cancelChild was invoked
654-
private fun cancelMakeCompleting(cause: Any?): Boolean {
663+
// It contains a loop and never returns COMPLETING_RETRY, can return
664+
// COMPLETING_ALREADY -- if already completed/completing
665+
// COMPLETING_WAITING_CHILDREN -- if started waiting for children
666+
// final state -- when completed, for call to afterCompletionInternal
667+
private fun cancelMakeCompleting(cause: Any?): Any? {
655668
loopOnState { state ->
656669
if (state !is Incomplete || state is Finishing && state.isCompleting) {
657-
return false // already completed/completing, do not even propose update
670+
// already completed/completing, do not even create exception to propose update
671+
return COMPLETING_ALREADY
658672
}
659673
val proposedUpdate = CompletedExceptionally(createCauseException(cause))
660-
when (tryMakeCompleting(state, proposedUpdate, mode = MODE_ATOMIC_DEFAULT)) {
661-
COMPLETING_ALREADY_COMPLETING -> return false
662-
COMPLETING_COMPLETED, COMPLETING_WAITING_CHILDREN -> return true
663-
COMPLETING_RETRY -> return@loopOnState
664-
else -> error("unexpected result")
665-
}
674+
val finalState = tryMakeCompleting(state, proposedUpdate)
675+
if (finalState !== COMPLETING_RETRY) return finalState
666676
}
667677
}
668678

@@ -689,13 +699,18 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
689699

690700
// transitions to Cancelling state
691701
// cause is Throwable or ParentJob when cancelChild was invoked
692-
private fun makeCancelling(cause: Any?): Boolean {
702+
// It contains a loop and never returns COMPLETING_RETRY, can return
703+
// COMPLETING_ALREADY -- if already completing or successfully made cancelling, added exception
704+
// COMPLETING_WAITING_CHILDREN -- if started waiting for children, added exception
705+
// TOO_LATE_TO_CANCEL -- too late to cancel, did not add exception
706+
// final state -- when completed, for call to afterCompletionInternal
707+
private fun makeCancelling(cause: Any?): Any? {
693708
var causeExceptionCache: Throwable? = null // lazily init result of createCauseException(cause)
694709
loopOnState { state ->
695710
when (state) {
696711
is Finishing -> { // already finishing -- collect exceptions
697712
val notifyRootCause = synchronized(state) {
698-
if (state.isSealed) return false // too late, already sealed -- cannot add exception nor mark cancelled
713+
if (state.isSealed) return TOO_LATE_TO_CANCEL // already sealed -- cannot add exception nor mark cancelled
699714
// add exception, do nothing is parent is cancelling child that is already being cancelled
700715
val wasCancelling = state.isCancelling // will notify if was not cancelling
701716
// Materialize missing exception if it is the first exception (otherwise -- don't)
@@ -707,25 +722,25 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
707722
state.rootCause.takeIf { !wasCancelling }
708723
}
709724
notifyRootCause?.let { notifyCancelling(state.list, it) }
710-
return true
725+
return COMPLETING_ALREADY
711726
}
712727
is Incomplete -> {
713728
// Not yet finishing -- try to make it cancelling
714729
val causeException = causeExceptionCache ?: createCauseException(cause).also { causeExceptionCache = it }
715730
if (state.isActive) {
716731
// active state becomes cancelling
717-
if (tryMakeCancelling(state, causeException)) return true
732+
if (tryMakeCancelling(state, causeException)) return COMPLETING_ALREADY
718733
} else {
719734
// non active state starts completing
720-
when (tryMakeCompleting(state, CompletedExceptionally(causeException), mode = MODE_ATOMIC_DEFAULT)) {
721-
COMPLETING_ALREADY_COMPLETING -> error("Cannot happen in $state")
722-
COMPLETING_COMPLETED, COMPLETING_WAITING_CHILDREN -> return true // ok
723-
COMPLETING_RETRY -> return@loopOnState
724-
else -> error("unexpected result")
735+
val finalState = tryMakeCompleting(state, CompletedExceptionally(causeException))
736+
when {
737+
finalState === COMPLETING_ALREADY -> error("Cannot happen in $state")
738+
finalState === COMPLETING_RETRY -> return@loopOnState
739+
else -> return finalState
725740
}
726741
}
727742
}
728-
else -> return false // already complete
743+
else -> return TOO_LATE_TO_CANCEL // already complete
729744
}
730745
}
731746
}
@@ -759,60 +774,78 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
759774
}
760775

761776
/**
762-
* This function is used by [CompletableDeferred.complete] (and exceptionally) and by [JobImpl.cancel].
763-
* It returns `false` on repeated invocation (when this job is already completing).
764-
*
765-
* @suppress **This is unstable API and it is subject to change.**
777+
* Completes this job. Used by [CompletableDeferred.complete] (and exceptionally)
778+
* and by [JobImpl.cancel]. It returns `false` on repeated invocation
779+
* (when this job is already completing).
766780
*/
767-
internal fun makeCompleting(proposedUpdate: Any?): Boolean = loopOnState { state ->
768-
when (tryMakeCompleting(state, proposedUpdate, mode = MODE_ATOMIC_DEFAULT)) {
769-
COMPLETING_ALREADY_COMPLETING -> return false
770-
COMPLETING_COMPLETED, COMPLETING_WAITING_CHILDREN -> return true
771-
COMPLETING_RETRY -> return@loopOnState
772-
else -> error("unexpected result")
781+
internal fun makeCompleting(proposedUpdate: Any?): Boolean {
782+
loopOnState { state ->
783+
val finalState = tryMakeCompleting(state, proposedUpdate)
784+
when {
785+
finalState === COMPLETING_ALREADY -> return false
786+
finalState === COMPLETING_WAITING_CHILDREN -> return true
787+
finalState === COMPLETING_RETRY -> return@loopOnState
788+
else -> {
789+
afterCompletionInternal(finalState, MODE_ATOMIC_DEFAULT)
790+
return true
791+
}
792+
}
773793
}
774-
}
794+
}
775795

776796
/**
777-
* This function is used by [AbstractCoroutine.resume].
778-
* It throws exception on repeated invocation (when this job is already completing).
779-
*
797+
* Completes this job. Used by [AbstractCoroutine.resume].
798+
* It throws [IllegalStateException] on repeated invocation (when this job is already completing).
780799
* Returns:
781-
* * `true` if state was updated to completed/cancelled;
782-
* * `false` if made completing or it is cancelling and is waiting for children.
783-
*
784-
* @throws IllegalStateException if job is already complete or completing
785-
* @suppress **This is unstable API and it is subject to change.**
800+
* * [COMPLETING_WAITING_CHILDREN] if started waiting for children.
801+
* * Final state otherwise (caller should do [afterCompletionInternal])
786802
*/
787-
internal fun makeCompletingOnce(proposedUpdate: Any?, mode: Int): Boolean = loopOnState { state ->
788-
when (tryMakeCompleting(state, proposedUpdate, mode)) {
789-
COMPLETING_ALREADY_COMPLETING -> throw IllegalStateException("Job $this is already complete or completing, " +
790-
"but is being completed with $proposedUpdate", proposedUpdate.exceptionOrNull)
791-
COMPLETING_COMPLETED -> return true
792-
COMPLETING_WAITING_CHILDREN -> return false
793-
COMPLETING_RETRY -> return@loopOnState
794-
else -> error("unexpected result")
803+
internal fun makeCompletingOnce(proposedUpdate: Any?): Any? {
804+
loopOnState { state ->
805+
val finalState = tryMakeCompleting(state, proposedUpdate)
806+
when {
807+
finalState === COMPLETING_ALREADY ->
808+
throw IllegalStateException(
809+
"Job $this is already complete or completing, " +
810+
"but is being completed with $proposedUpdate", proposedUpdate.exceptionOrNull
811+
)
812+
finalState === COMPLETING_RETRY -> return@loopOnState
813+
else -> return finalState // COMPLETING_WAITING_CHILDREN or final state
814+
}
795815
}
796816
}
797817

798-
private fun tryMakeCompleting(state: Any?, proposedUpdate: Any?, mode: Int): Int {
818+
// Returns one of COMPLETING symbols or final state:
819+
// COMPLETING_ALREADY -- when already complete or completing
820+
// COMPLETING_RETRY -- when need to retry due to interference
821+
// COMPLETING_WAITING_CHILDREN -- when made completing and is waiting for children
822+
// final state -- when completed, for call to afterCompletionInternal
823+
private fun tryMakeCompleting(state: Any?, proposedUpdate: Any?): Any? {
799824
if (state !is Incomplete)
800-
return COMPLETING_ALREADY_COMPLETING
825+
return COMPLETING_ALREADY
801826
/*
802827
* FAST PATH -- no children to wait for && simple state (no list) && not cancelling => can complete immediately
803828
* Cancellation (failures) always have to go through Finishing state to serialize exception handling.
804829
* Otherwise, there can be a race between (completed state -> handled exception and newly attached child/join)
805830
* which may miss unhandled exception.
806831
*/
807832
if ((state is Empty || state is JobNode<*>) && state !is ChildHandleNode && proposedUpdate !is CompletedExceptionally) {
808-
if (!tryFinalizeSimpleState(state, proposedUpdate, mode)) return COMPLETING_RETRY
809-
return COMPLETING_COMPLETED
833+
if (tryFinalizeSimpleState(state, proposedUpdate)) {
834+
// Completed successfully on fast path -- return updated state
835+
return proposedUpdate
836+
}
837+
return COMPLETING_RETRY
810838
}
811839
// The separate slow-path function to simplify profiling
812-
return tryMakeCompletingSlowPath(state, proposedUpdate, mode)
840+
return tryMakeCompletingSlowPath(state, proposedUpdate)
813841
}
814842

815-
private fun tryMakeCompletingSlowPath(state: Incomplete, proposedUpdate: Any?, mode: Int): Int {
843+
// Returns one of COMPLETING symbols or final state:
844+
// COMPLETING_ALREADY -- when already complete or completing
845+
// COMPLETING_RETRY -- when need to retry due to interference
846+
// COMPLETING_WAITING_CHILDREN -- when made completing and is waiting for children
847+
// final state -- when completed, for call to afterCompletionInternal
848+
private fun tryMakeCompletingSlowPath(state: Incomplete, proposedUpdate: Any?): Any? {
816849
// get state's list or else promote to list to correctly operate on child lists
817850
val list = getOrPromoteCancellingList(state) ?: return COMPLETING_RETRY
818851
// promote to Finishing state if we are not in it yet
@@ -823,7 +856,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
823856
var notifyRootCause: Throwable? = null
824857
synchronized(finishing) {
825858
// check if this state is already completing
826-
if (finishing.isCompleting) return COMPLETING_ALREADY_COMPLETING
859+
if (finishing.isCompleting) return COMPLETING_ALREADY
827860
// mark as completing
828861
finishing.isCompleting = true
829862
// if we need to promote to finishing then atomically do it here.
@@ -847,10 +880,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
847880
if (child != null && tryWaitForChild(finishing, child, proposedUpdate))
848881
return COMPLETING_WAITING_CHILDREN
849882
// otherwise -- we have not children left (all were already cancelled?)
850-
if (tryFinalizeFinishingState(finishing, proposedUpdate, mode))
851-
return COMPLETING_COMPLETED
852-
// otherwise retry
853-
return COMPLETING_RETRY
883+
return tryFinalizeFinishingState(finishing, proposedUpdate)
854884
}
855885

856886
private val Any?.exceptionOrNull: Throwable?
@@ -879,7 +909,8 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
879909
// try wait for next child
880910
if (waitChild != null && tryWaitForChild(state, waitChild, proposedUpdate)) return // waiting for next child
881911
// no more children to wait -- try update state
882-
if (tryFinalizeFinishingState(state, proposedUpdate, MODE_ATOMIC_DEFAULT)) return
912+
val finalState = tryFinalizeFinishingState(state, proposedUpdate)
913+
afterCompletionInternal(finalState, MODE_ATOMIC_DEFAULT)
883914
}
884915

885916
private fun LockFreeLinkedListNode.nextChild(): ChildHandleNode? {
@@ -1233,10 +1264,15 @@ internal fun Any?.unboxState(): Any? = (this as? IncompleteStateBox)?.state ?: t
12331264

12341265
// --------------- helper classes & constants for job implementation
12351266

1236-
private const val COMPLETING_ALREADY_COMPLETING = 0
1237-
private const val COMPLETING_COMPLETED = 1
1238-
private const val COMPLETING_WAITING_CHILDREN = 2
1239-
private const val COMPLETING_RETRY = 3
1267+
@SharedImmutable
1268+
private val COMPLETING_ALREADY = Symbol("COMPLETING_ALREADY")
1269+
@JvmField
1270+
@SharedImmutable
1271+
internal val COMPLETING_WAITING_CHILDREN = Symbol("COMPLETING_WAITING_CHILDREN")
1272+
@SharedImmutable
1273+
private val COMPLETING_RETRY = Symbol("COMPLETING_RETRY")
1274+
@SharedImmutable
1275+
private val TOO_LATE_TO_CANCEL = Symbol("TOO_LATE_TO_CANCEL")
12401276

12411277
private const val RETRY = -1
12421278
private const val FALSE = 0

0 commit comments

Comments
 (0)