@@ -60,7 +60,7 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
6060 * [CoroutineStart.LAZY]. Such a job can be made _active_ by invoking [start] or [join].
6161 *
6262 * A job can be _cancelled_ at any time with [cancel] function that forces it to transition to
63- * _cancelling_ state immediately. Job that is not backed by a coroutine and does not have
63+ * _cancelling_ state immediately. Job that is not backed by a coroutine (see `Job()` function) and does not have
6464 * [children] becomes _cancelled_ on [cancel] immediately.
6565 * Otherwise, job becomes _cancelled_ when it finishes executing its code and
6666 * when all its children [complete][isCompleted].
@@ -841,7 +841,7 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
841841 promoteSingleToNodeList(state as JobNode <* >)
842842 } else {
843843 if (state is Finishing && state.cancelled != null && onCancelling) {
844- check(hasCancellingState ) // cannot be in this state unless were support cancelling state
844+ check(onCancelMode != ON_CANCEL_MAKE_CANCELLED ) // cannot be in this state unless were support cancelling state
845845 // installing cancellation handler on job that is being cancelled
846846 if (invokeImmediately) handler(state.cancelled.cause)
847847 return NonDisposableHandle
@@ -858,14 +858,15 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
858858 }
859859 }
860860
861- private fun makeNode (handler : CompletionHandler , onCancelling : Boolean ): JobNode <* > =
862- if (onCancelling && hasCancellingState)
861+ private fun makeNode (handler : CompletionHandler , onCancelling : Boolean ): JobNode <* > {
862+ val hasCancellingState = onCancelMode != ON_CANCEL_MAKE_CANCELLED
863+ return if (onCancelling && hasCancellingState)
863864 (handler as ? JobCancellationNode <* >)?.also { require(it.job == = this ) }
864865 ? : InvokeOnCancellation (this , handler)
865866 else
866867 (handler as ? JobNode <* >)?.also { require(it.job == = this && (! hasCancellingState || it !is JobCancellationNode )) }
867868 ? : InvokeOnCompletion (this , handler)
868-
869+ }
869870
870871 private fun addLastAtomic (expect : Any , list : NodeList , node : JobNode <* >) =
871872 list.addLastIf(node) { this .state == = expect }
@@ -948,12 +949,14 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
948949 }
949950 }
950951
951- protected open val hasCancellingState : Boolean get() = false
952+ protected open val onCancelMode : Int get() = ON_CANCEL_MAKE_CANCELLING
952953
953- public override fun cancel (cause : Throwable ? ): Boolean =
954- if (hasCancellingState)
955- makeCancelling(cause) else
956- makeCancelled(cause)
954+ public override fun cancel (cause : Throwable ? ): Boolean = when (onCancelMode) {
955+ ON_CANCEL_MAKE_CANCELLED -> makeCancelled(cause)
956+ ON_CANCEL_MAKE_CANCELLING -> makeCancelling(cause)
957+ ON_CANCEL_MAKE_COMPLETING -> makeCompletingOnCancel(cause)
958+ else -> error(" Invalid onCancelMode $onCancelMode " )
959+ }
957960
958961 // we will be dispatching coroutine to process its cancellation exception, so there is no need for
959962 // an extra check for Job status in MODE_CANCELLABLE
@@ -1013,6 +1016,15 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
10131016 return true
10141017 }
10151018
1019+ private fun makeCompletingOnCancel (cause : Throwable ? ): Boolean =
1020+ makeCompleting(Cancelled (this , cause))
1021+
1022+ internal fun makeCompleting (proposedUpdate : Any? ): Boolean =
1023+ when (makeCompletingInternal(proposedUpdate, mode = MODE_ATOMIC_DEFAULT )) {
1024+ COMPLETING_ALREADY_COMPLETING -> false
1025+ else -> true
1026+ }
1027+
10161028 /* *
10171029 * Returns:
10181030 * * `true` if state was updated to completed/cancelled;
@@ -1021,30 +1033,38 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
10211033 * @throws IllegalStateException if job is already complete or completing
10221034 * @suppress **This is unstable API and it is subject to change.**
10231035 */
1024- internal fun makeCompleting (proposedUpdate : Any? , mode : Int ): Boolean {
1036+ internal fun makeCompletingOnce (proposedUpdate : Any? , mode : Int ): Boolean =
1037+ when (makeCompletingInternal(proposedUpdate, mode)) {
1038+ COMPLETING_COMPLETED -> true
1039+ COMPLETING_WAITING_CHILDREN -> false
1040+ else -> throw IllegalStateException (" Job $this is already complete or completing, " +
1041+ " but is being completed with $proposedUpdate " , proposedUpdate.exceptionOrNull)
1042+ }
1043+
1044+ private fun makeCompletingInternal (proposedUpdate : Any? , mode : Int ): Int {
10251045 loopOnState { state ->
10261046 if (state !is Incomplete )
1027- throw IllegalStateException ( " Job $this is already complete, but is being completed with $proposedUpdate " , proposedUpdate.exceptionOrNull)
1047+ return COMPLETING_ALREADY_COMPLETING
10281048 if (state is Finishing && state.completing)
1029- throw IllegalStateException ( " Job $this is already completing, but is being completed with $proposedUpdate " , proposedUpdate.exceptionOrNull)
1049+ return COMPLETING_ALREADY_COMPLETING
10301050 val child: Child = firstChild(state) ? : // or else complete immediately w/o children
1031- if (updateState(state, proposedUpdate, mode)) return true else return @loopOnState
1051+ if (updateState(state, proposedUpdate, mode)) return COMPLETING_COMPLETED else return @loopOnState
10321052 // must promote to list to correct operate on child lists
10331053 if (state is JobNode <* >) {
10341054 promoteSingleToNodeList(state)
10351055 return @loopOnState // retry
10361056 }
10371057 // cancel all children in list on exceptional completion
1038- if (proposedUpdate is CompletedExceptionally
1039- )
1058+ if (proposedUpdate is CompletedExceptionally )
10401059 child.cancelChildrenInternal(proposedUpdate.exception)
10411060 // switch to completing state
1042- val completing = Finishing (state.list!! , (state as ? Finishing )?.cancelled, true )
1061+ val cancelled = (state as ? Finishing )?.cancelled ? : (proposedUpdate as ? Cancelled )
1062+ val completing = Finishing (state.list!! , cancelled, true )
10431063 if (_state .compareAndSet(state, completing)) {
10441064 if (tryWaitForChild(child, proposedUpdate))
1045- return false
1046- if (updateState(completing, proposedUpdate, MODE_ATOMIC_DEFAULT ))
1047- return true
1065+ return COMPLETING_WAITING_CHILDREN
1066+ if (updateState(completing, proposedUpdate, mode = MODE_ATOMIC_DEFAULT ))
1067+ return COMPLETING_COMPLETED
10481068 }
10491069 }
10501070 }
@@ -1251,8 +1271,7 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
12511271 cont.disposeOnCompletion(invokeOnCompletion {
12521272 val state = this .state
12531273 check(state !is Incomplete )
1254- if (state is CompletedExceptionally
1255- )
1274+ if (state is CompletedExceptionally )
12561275 cont.resumeWithException(state.exception)
12571276 else
12581277 cont.resume(state)
@@ -1267,8 +1286,7 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
12671286 if (state !is Incomplete ) {
12681287 // already complete -- select result
12691288 if (select.trySelect(null )) {
1270- if (state is CompletedExceptionally
1271- )
1289+ if (state is CompletedExceptionally )
12721290 select.resumeSelectCancellableWithException(state.exception)
12731291 else
12741292 block.startCoroutineUndispatched(state, select.completion)
@@ -1286,14 +1304,21 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
12861304 internal fun <R > selectAwaitCompletion (select : SelectInstance <R >, block : suspend (Any? ) -> R ) {
12871305 val state = this .state
12881306 // Note: await is non-atomic (can be cancelled while dispatched)
1289- if (state is CompletedExceptionally
1290- )
1307+ if (state is CompletedExceptionally )
12911308 select.resumeSelectCancellableWithException(state.exception)
12921309 else
12931310 block.startCoroutineCancellable(state, select.completion)
12941311 }
12951312}
12961313
1314+ internal const val ON_CANCEL_MAKE_CANCELLED = 0
1315+ internal const val ON_CANCEL_MAKE_CANCELLING = 1
1316+ internal const val ON_CANCEL_MAKE_COMPLETING = 2
1317+
1318+ private const val COMPLETING_ALREADY_COMPLETING = 0
1319+ private const val COMPLETING_COMPLETED = 1
1320+ private const val COMPLETING_WAITING_CHILDREN = 2
1321+
12971322private const val RETRY = - 1
12981323private const val FALSE = 0
12991324private const val TRUE = 1
@@ -1310,6 +1335,7 @@ private class Empty(override val isActive: Boolean) : JobSupport.Incomplete {
13101335
13111336private class JobImpl (parent : Job ? = null ) : JobSupport(true ) {
13121337 init { initParentJob(parent) }
1338+ override val onCancelMode: Int get() = ON_CANCEL_MAKE_COMPLETING
13131339}
13141340
13151341// -------- invokeOnCompletion nodes
0 commit comments