@@ -104,7 +104,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
104104 @Suppress(" DEPRECATION" )
105105 val handle = parent.attachChild(this )
106106 parentHandle = handle
107- // now check our state _after_ registering (see updateState order of actions)
107+ // now check our state _after_ registering (see tryFinalizeState order of actions)
108108 if (isCompleted) {
109109 handle.dispose()
110110 parentHandle = NonDisposableHandle // release it just in case, to aid GC
@@ -148,62 +148,39 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
148148 // ------------ state update ------------
149149
150150 /* *
151- * Updates current [state] of this job. Returns `false` if current state is not equal to expected.
152- * @suppress **This is unstable API and it is subject to change.**
151+ * Updates current [state] of this job to the final state, invoking all necessary handlers
152+ * and/or `on*` methods.
153+ *
154+ * Returns `false` if current state is not equal to expected.
155+ * If this method succeeds, state of this job will never be changed again
153156 */
154- private fun updateState (expect : Incomplete , proposedUpdate : Any? , mode : Int ): Boolean {
157+ private fun tryFinalizeState (expect : Incomplete , proposedUpdate : Any? , mode : Int ): Boolean {
155158 val update = coerceProposedUpdate(expect, proposedUpdate)
156- if (! tryUpdateState (expect, update)) return false
157- completeUpdateState (expect, update, mode)
159+ if (! tryFinalizeState (expect, update)) return false
160+ completeStateFinalization (expect, update, mode)
158161 return true
159162 }
160163
161- // when Job is in Cancelling state, it can only be promoted to Cancelled state,
162- // so if the proposed Update is not an appropriate Cancelled (preserving the cancellation cause),
163- // then the corresponding Cancelled state is constructed.
164- private fun coerceProposedUpdate (expect : Incomplete , proposedUpdate : Any? ): Any? =
165- if (expect is Finishing && expect.cancelled != null && ! isCorrespondinglyCancelled(expect.cancelled, proposedUpdate))
166- createCancelled(expect.cancelled, proposedUpdate) else proposedUpdate
167-
168- private fun isCorrespondinglyCancelled (cancelled : Cancelled , proposedUpdate : Any? ): Boolean {
169- if (proposedUpdate !is Cancelled ) return false
170- // NOTE: equality comparison of causes is performed here by design, see equals of JobCancellationException
171- return proposedUpdate.cause == cancelled.cause || proposedUpdate.cause is JobCancellationException
172- }
173-
174- private fun createCancelled (cancelled : Cancelled , proposedUpdate : Any? ): Cancelled {
175- if (proposedUpdate !is CompletedExceptionally ) return cancelled // not exception -- just use original cancelled
176- val exception = proposedUpdate.cause
177- if (cancelled.cause == exception) return cancelled // that is the cancelled we need already!
178- // todo: We need to rework this logic to keep original cancellation cause in the state and suppress other exceptions
179- // that could have occurred while coroutine is being cancelled.
180- // Do not spam with JCE in suppressed exceptions
181- if (cancelled.cause !is JobCancellationException ) {
182- exception.addSuppressedThrowable(cancelled.cause)
183- }
184- return Cancelled (this , exception)
185- }
186-
187164 /* *
188- * Tries to initiate update of the current [state ] of this job.
189- * @suppress **This is unstable API and it is subject to change.**
165+ * Tries to update [_state ] of this job to the final state and, if
166+ * succeeds, disposes parent handle (de-attaching child from parent)
190167 */
191- private fun tryUpdateState (expect : Incomplete , update : Any? ): Boolean {
168+ private fun tryFinalizeState (expect : Incomplete , update : Any? ): Boolean {
192169 require(update !is Incomplete ) // only incomplete -> completed transition is allowed
193170 if (! _state .compareAndSet(expect, update)) return false
194171 // Unregister from parent job
195172 parentHandle?.let {
196173 it.dispose() // volatile read parentHandle _after_ state was updated
197174 parentHandle = NonDisposableHandle // release it just in case, to aid GC
198175 }
199- return true // continues in completeUpdateState
176+ return true // continues in completeStateFinalization
200177 }
201178
202179 /* *
203180 * Completes update of the current [state] of this job.
204181 * @suppress **This is unstable API and it is subject to change.**
205182 */
206- private fun completeUpdateState (expect : Incomplete , update : Any? , mode : Int ) {
183+ private fun completeStateFinalization (expect : Incomplete , update : Any? , mode : Int ) {
207184 val exceptionally = update as ? CompletedExceptionally
208185 // Do overridable processing before completion handlers
209186
@@ -228,11 +205,42 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
228205 } else {
229206 expect.list?.notifyCompletion(cause)
230207 }
231-
232208 onCompletionInternal(update, mode)
233209 }
234210
235- private inline fun <reified T : JobNode <* >> notifyHandlers (list : NodeList , cause : Throwable ? ) {
211+ // when Job is in Cancelling state, it can only be promoted to Cancelled state,
212+ // so if the proposed Update is not an appropriate Cancelled (preserving the cancellation cause),
213+ // then the corresponding Cancelled state is constructed.
214+ private fun coerceProposedUpdate (expect : Incomplete , proposedUpdate : Any? ): Any? =
215+ if (expect is Finishing && expect.cancelled != null && ! isCorrespondinglyCancelled(expect.cancelled, proposedUpdate))
216+ createCancelled(expect.cancelled, proposedUpdate) else proposedUpdate
217+
218+ private fun isCorrespondinglyCancelled (cancelled : Cancelled , proposedUpdate : Any? ): Boolean {
219+ if (proposedUpdate !is Cancelled ) return false
220+ // NOTE: equality comparison of causes is performed here by design, see equals of JobCancellationException
221+ return proposedUpdate.cause == cancelled.cause || proposedUpdate.cause is JobCancellationException
222+ }
223+
224+ private fun createCancelled (cancelled : Cancelled , proposedUpdate : Any? ): Cancelled {
225+ if (proposedUpdate !is CompletedExceptionally ) return cancelled // not exception -- just use original cancelled
226+ val exception = proposedUpdate.cause
227+ if (cancelled.cause == exception) return cancelled // that is the cancelled we need already!
228+ // todo: We need to rework this logic to keep original cancellation cause in the state and suppress other exceptions
229+ // that could have occurred while coroutine is being cancelled.
230+ // Do not spam with JCE in suppressed exceptions
231+ if (cancelled.cause !is JobCancellationException ) {
232+ exception.addSuppressedThrowable(cancelled.cause)
233+ }
234+ return Cancelled (this , exception)
235+ }
236+
237+ private fun NodeList.notifyCompletion (cause : Throwable ? ) =
238+ notifyHandlers<JobNode <* >>(this , cause)
239+
240+ private fun notifyCancellation (list : NodeList , cause : Throwable ? ) =
241+ notifyHandlers<JobCancellationNode <* >>(list, cause)
242+
243+ private inline fun <reified T : JobNode <* >> notifyHandlers (list : NodeList , cause : Throwable ? ) {
236244 var exception: Throwable ? = null
237245 list.forEach<T > { node ->
238246 try {
@@ -246,12 +254,6 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
246254 exception?.let { handleException(it) }
247255 }
248256
249- private fun NodeList.notifyCompletion (cause : Throwable ? ) =
250- notifyHandlers<JobNode <* >>(this , cause)
251-
252- private fun notifyCancellation (list : NodeList , cause : Throwable ? ) =
253- notifyHandlers<JobCancellationNode <* >>(list, cause)
254-
255257 public final override fun start (): Boolean {
256258 loopOnState { state ->
257259 when (startInternal(state)) {
@@ -472,14 +474,14 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
472474
473475 public override fun cancel (cause : Throwable ? ): Boolean = when (onCancelMode) {
474476 ON_CANCEL_MAKE_CANCELLING -> makeCancelling(cause)
475- ON_CANCEL_MAKE_COMPLETING -> makeCompletingOnCancel( cause)
477+ ON_CANCEL_MAKE_COMPLETING -> makeCompleting( Cancelled ( this , cause) )
476478 else -> error(" Invalid onCancelMode $onCancelMode " )
477479 }
478480
479481 // we will be dispatching coroutine to process its cancellation exception, so there is no need for
480482 // an extra check for Job status in MODE_CANCELLABLE
481483 private fun updateStateCancelled (state : Incomplete , cause : Throwable ? ) =
482- updateState (state, Cancelled (this , cause), mode = MODE_ATOMIC_DEFAULT )
484+ tryFinalizeState (state, Cancelled (this , cause), mode = MODE_ATOMIC_DEFAULT )
483485
484486 // transitions to Cancelling state
485487 private fun makeCancelling (cause : Throwable ? ): Boolean {
@@ -528,9 +530,6 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
528530 return true
529531 }
530532
531- private fun makeCompletingOnCancel (cause : Throwable ? ): Boolean =
532- makeCompleting(Cancelled (this , cause))
533-
534533 /* *
535534 * @suppress **This is unstable API and it is subject to change.**
536535 */
@@ -565,7 +564,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
565564 val child: ChildJob ? = firstChild(state) ? : // or else complete immediately w/o children
566565 when {
567566 state !is Finishing && hasOnFinishingHandler(proposedUpdate) -> null // unless it has onFinishing handler
568- updateState (state, proposedUpdate, mode) -> return COMPLETING_COMPLETED
567+ tryFinalizeState (state, proposedUpdate, mode) -> return COMPLETING_COMPLETED
569568 else -> return @loopOnState
570569 }
571570 val list = state.list ? : // must promote to list to correctly operate on child lists
@@ -590,7 +589,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
590589 if (state !is Finishing ) onFinishingInternal(proposedUpdate)
591590 if (child != null && tryWaitForChild(child, proposedUpdate))
592591 return COMPLETING_WAITING_CHILDREN
593- if (updateState (completing, proposedUpdate, mode = MODE_ATOMIC_DEFAULT ))
592+ if (tryFinalizeState (completing, proposedUpdate, mode = MODE_ATOMIC_DEFAULT ))
594593 return COMPLETING_COMPLETED
595594 }
596595 }
@@ -628,7 +627,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
628627 // try wait for next child
629628 if (waitChild != null && tryWaitForChild(waitChild, proposedUpdate)) return // waiting for next child
630629 // no more children to wait -- try update state
631- if (updateState (state, proposedUpdate, MODE_ATOMIC_DEFAULT )) return
630+ if (tryFinalizeState (state, proposedUpdate, MODE_ATOMIC_DEFAULT )) return
632631 }
633632 }
634633
0 commit comments