Skip to content

Commit 3bda22c

Browse files
qwwdfsadelizarov
authored andcommitted
JobSupport stylistic improvements:
Add notion of job state finalization Provide separate package for exception tests
1 parent 6e12fc4 commit 3bda22c

File tree

3 files changed

+52
-54
lines changed

3 files changed

+52
-54
lines changed

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ open class CompletedExceptionally(
2727
* A specific subclass of [CompletedExceptionally] for cancelled jobs.
2828
*
2929
* **Note: This class cannot be used outside of internal coroutines framework**.
30-
* TODO rename to CancelledJob?
3130
*
3231
* @param job the job that was cancelled.
3332
* @param cause the exceptional completion cause. If `cause` is null, then a [JobCancellationException] is created.

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

Lines changed: 52 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)