@@ -154,10 +154,10 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
154
154
}
155
155
156
156
// ------------ state query ------------
157
-
158
157
/* *
159
158
* 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.
161
161
*/
162
162
internal val state: Any? get() {
163
163
_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
192
192
// Finalizes Finishing -> Completed (terminal state) transition.
193
193
// ## IMPORTANT INVARIANT: Only one thread can be concurrently invoking this method.
194
194
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
+ */
196
201
require(this .state == = state) // consistency check -- it cannot change
197
202
require(! state.isSealed) // consistency check -- cannot be sealed yet
198
203
require(state.isCompleting) // consistency check -- must be marked as completing
@@ -220,7 +225,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
220
225
handleJobException(finalException)
221
226
}
222
227
// 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 " }
224
229
// And process all post-completion actions
225
230
completeStateFinalization(state, finalState, mode, suppressed)
226
231
return true
@@ -254,7 +259,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
254
259
private fun tryFinalizeSimpleState (state : Incomplete , update : Any? , mode : Int ): Boolean {
255
260
check(state is Empty || state is JobNode <* >) // only simple state without lists where children can concurrently add
256
261
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
258
263
completeStateFinalization(state, update, mode, false )
259
264
return true
260
265
}
@@ -1029,7 +1034,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
1029
1034
private val job : JobSupport
1030
1035
) : CancellableContinuationImpl<T>(delegate, MODE_CANCELLABLE ) {
1031
1036
override fun getContinuationCancellationCause (parent : Job ): Throwable {
1032
- val state = job.state
1037
+ val state = job.state.unboxState()
1033
1038
/*
1034
1039
* When the job we are waiting for had already completely completed exceptionally or
1035
1040
* 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
1054
1059
public val isCompletedExceptionally: Boolean get() = state is CompletedExceptionally
1055
1060
1056
1061
public fun getCompletionExceptionOrNull (): Throwable ? {
1057
- val state = this .state
1062
+ val state = this .state.unboxState()
1058
1063
check(state !is Incomplete ) { " This job has not completed yet" }
1059
1064
return state.exceptionOrNull
1060
1065
}
@@ -1063,7 +1068,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
1063
1068
* @suppress **This is unstable API and it is subject to change.**
1064
1069
*/
1065
1070
internal fun getCompletedInternal (): Any? {
1066
- val state = this .state
1071
+ val state = this .state.unboxState()
1067
1072
check(state !is Incomplete ) { " This job has not completed yet" }
1068
1073
if (state is CompletedExceptionally ) throw state.cause
1069
1074
return state
@@ -1075,7 +1080,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
1075
1080
internal suspend fun awaitInternal (): Any? {
1076
1081
// fast-path -- check state (avoid extra object creation)
1077
1082
while (true ) { // lock-free loop on state
1078
- val state = this .state
1083
+ val state = this .state.unboxState()
1079
1084
if (state !is Incomplete ) {
1080
1085
// already complete -- just return result
1081
1086
if (state is CompletedExceptionally ) throw state.cause
@@ -1131,7 +1136,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
1131
1136
*/
1132
1137
@Suppress(" UNCHECKED_CAST" )
1133
1138
internal fun <T , R > selectAwaitCompletion (select : SelectInstance <R >, block : suspend (T ) -> R ) {
1134
- val state = this .state
1139
+ val state = this .state.unboxState()
1135
1140
// Note: await is non-atomic (can be cancelled while dispatched)
1136
1141
if (state is CompletedExceptionally )
1137
1142
select.resumeSelectCancellableWithException(state.cause)
@@ -1140,6 +1145,13 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
1140
1145
}
1141
1146
}
1142
1147
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
+
1143
1155
// --------------- helper classes & constants for job implementation
1144
1156
1145
1157
private const val COMPLETING_ALREADY_COMPLETING = 0
@@ -1232,8 +1244,7 @@ private class ResumeAwaitOnCompletion<T>(
1232
1244
private val continuation : AbstractContinuation <T >
1233
1245
) : JobNode<JobSupport>(job) {
1234
1246
override fun invoke (cause : Throwable ? ) {
1235
- val state = job.state
1236
- check(state !is Incomplete )
1247
+ val state = job.state.unboxState()
1237
1248
if (state is CompletedExceptionally ) {
1238
1249
// Resume with exception in atomic way to preserve exception
1239
1250
continuation.resumeWithExceptionMode(state.cause, MODE_ATOMIC_DEFAULT )
0 commit comments