@@ -154,10 +154,10 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
154154 }
155155
156156 // ------------ state query ------------
157-
158157 /* *
159158 * Returns current state of this job.
160- * @suppress **This is unstable API and it is subject to change.**
159+ * If final state of the job is [Incomplete], then it is boxed into [IncompleteStateBox]
160+ * and should be [unboxed][unboxState] before returning to user code.
161161 */
162162 internal val state: Any? get() {
163163 _state .loop { state -> // helper loop on state (complete in-progress atomic operations)
@@ -192,7 +192,12 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
192192 // Finalizes Finishing -> Completed (terminal state) transition.
193193 // ## IMPORTANT INVARIANT: Only one thread can be concurrently invoking this method.
194194 private fun tryFinalizeFinishingState (state : Finishing , proposedUpdate : Any? , mode : Int ): Boolean {
195- require(proposedUpdate !is Incomplete ) // only incomplete -> completed transition is allowed
195+ /*
196+ * Note: proposed state can be Incompleted, e.g.
197+ * async {
198+ * smth.invokeOnCompletion {} // <- returns handle which implements Incomplete under the hood
199+ * }
200+ */
196201 require(this .state == = state) // consistency check -- it cannot change
197202 require(! state.isSealed) // consistency check -- cannot be sealed yet
198203 require(state.isCompleting) // consistency check -- must be marked as completing
@@ -220,7 +225,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
220225 handleJobException(finalException)
221226 }
222227 // Then CAS to completed state -> it must succeed
223- require(_state .compareAndSet(state, finalState)) { " Unexpected state: ${_state .value} , expected: $state , update: $finalState " }
228+ require(_state .compareAndSet(state, finalState.boxIncomplete() )) { " Unexpected state: ${_state .value} , expected: $state , update: $finalState " }
224229 // And process all post-completion actions
225230 completeStateFinalization(state, finalState, mode, suppressed)
226231 return true
@@ -254,7 +259,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
254259 private fun tryFinalizeSimpleState (state : Incomplete , update : Any? , mode : Int ): Boolean {
255260 check(state is Empty || state is JobNode <* >) // only simple state without lists where children can concurrently add
256261 check(update !is CompletedExceptionally ) // only for normal completion
257- if (! _state .compareAndSet(state, update)) return false
262+ if (! _state .compareAndSet(state, update.boxIncomplete() )) return false
258263 completeStateFinalization(state, update, mode, false )
259264 return true
260265 }
@@ -1029,7 +1034,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
10291034 private val job : JobSupport
10301035 ) : CancellableContinuationImpl<T>(delegate, MODE_CANCELLABLE ) {
10311036 override fun getContinuationCancellationCause (parent : Job ): Throwable {
1032- val state = job.state
1037+ val state = job.state.unboxState()
10331038 /*
10341039 * When the job we are waiting for had already completely completed exceptionally or
10351040 * is failing, we shall use its root/completion cause for await's result.
@@ -1054,7 +1059,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
10541059 public val isCompletedExceptionally: Boolean get() = state is CompletedExceptionally
10551060
10561061 public fun getCompletionExceptionOrNull (): Throwable ? {
1057- val state = this .state
1062+ val state = this .state.unboxState()
10581063 check(state !is Incomplete ) { " This job has not completed yet" }
10591064 return state.exceptionOrNull
10601065 }
@@ -1063,7 +1068,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
10631068 * @suppress **This is unstable API and it is subject to change.**
10641069 */
10651070 internal fun getCompletedInternal (): Any? {
1066- val state = this .state
1071+ val state = this .state.unboxState()
10671072 check(state !is Incomplete ) { " This job has not completed yet" }
10681073 if (state is CompletedExceptionally ) throw state.cause
10691074 return state
@@ -1075,7 +1080,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
10751080 internal suspend fun awaitInternal (): Any? {
10761081 // fast-path -- check state (avoid extra object creation)
10771082 while (true ) { // lock-free loop on state
1078- val state = this .state
1083+ val state = this .state.unboxState()
10791084 if (state !is Incomplete ) {
10801085 // already complete -- just return result
10811086 if (state is CompletedExceptionally ) throw state.cause
@@ -1131,7 +1136,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
11311136 */
11321137 @Suppress(" UNCHECKED_CAST" )
11331138 internal fun <T , R > selectAwaitCompletion (select : SelectInstance <R >, block : suspend (T ) -> R ) {
1134- val state = this .state
1139+ val state = this .state.unboxState()
11351140 // Note: await is non-atomic (can be cancelled while dispatched)
11361141 if (state is CompletedExceptionally )
11371142 select.resumeSelectCancellableWithException(state.cause)
@@ -1140,6 +1145,13 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
11401145 }
11411146}
11421147
1148+ /*
1149+ * Class to represent object as the final state of the Job
1150+ */
1151+ private class IncompleteStateBox (@JvmField val state : Incomplete )
1152+ private fun Any?.boxIncomplete (): Any? = if (this is Incomplete ) IncompleteStateBox (this ) else this
1153+ internal fun Any?.unboxState (): Any? = (this as ? IncompleteStateBox )?.state ? : this
1154+
11431155// --------------- helper classes & constants for job implementation
11441156
11451157private const val COMPLETING_ALREADY_COMPLETING = 0
@@ -1232,8 +1244,7 @@ private class ResumeAwaitOnCompletion<T>(
12321244 private val continuation : AbstractContinuation <T >
12331245) : JobNode<JobSupport>(job) {
12341246 override fun invoke (cause : Throwable ? ) {
1235- val state = job.state
1236- check(state !is Incomplete )
1247+ val state = job.state.unboxState()
12371248 if (state is CompletedExceptionally ) {
12381249 // Resume with exception in atomic way to preserve exception
12391250 continuation.resumeWithExceptionMode(state.cause, MODE_ATOMIC_DEFAULT )
0 commit comments