@@ -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
@@ -255,7 +260,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
255
260
private fun tryFinalizeSimpleState (state : Incomplete , update : Any? , mode : Int ): Boolean {
256
261
check(state is Empty || state is JobNode <* >) // only simple state without lists where children can concurrently add
257
262
check(update !is CompletedExceptionally ) // only for normal completion
258
- if (! _state .compareAndSet(state, update)) return false
263
+ if (! _state .compareAndSet(state, update.boxIncomplete() )) return false
259
264
completeStateFinalization(state, update, mode, false )
260
265
return true
261
266
}
@@ -1067,7 +1072,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
1067
1072
val state = this .state
1068
1073
check(state !is Incomplete ) { " This job has not completed yet" }
1069
1074
if (state is CompletedExceptionally ) throw state.cause
1070
- return state
1075
+ return state.unboxState()
1071
1076
}
1072
1077
1073
1078
/* *
@@ -1082,7 +1087,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
1082
1087
if (state is CompletedExceptionally ) { // Slow path to recover stacktrace
1083
1088
recoverAndThrow(state.cause)
1084
1089
}
1085
- return state
1090
+ return state.unboxState()
1086
1091
1087
1092
}
1088
1093
if (startInternal(state) >= 0 ) break // break unless needs to retry
@@ -1114,10 +1119,12 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
1114
1119
if (state !is Incomplete ) {
1115
1120
// already complete -- select result
1116
1121
if (select.trySelect(null )) {
1117
- if (state is CompletedExceptionally )
1122
+ if (state is CompletedExceptionally ) {
1118
1123
select.resumeSelectCancellableWithException(state.cause)
1119
- else
1120
- block.startCoroutineUnintercepted(state as T , select.completion)
1124
+ }
1125
+ else {
1126
+ block.startCoroutineUnintercepted(state.unboxState() as T , select.completion)
1127
+ }
1121
1128
}
1122
1129
return
1123
1130
}
@@ -1139,10 +1146,17 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
1139
1146
if (state is CompletedExceptionally )
1140
1147
select.resumeSelectCancellableWithException(state.cause)
1141
1148
else
1142
- block.startCoroutineCancellable(state as T , select.completion)
1149
+ block.startCoroutineCancellable(state.unboxState() as T , select.completion)
1143
1150
}
1144
1151
}
1145
1152
1153
+ /*
1154
+ * Class to represent object as the final state of the Job
1155
+ */
1156
+ private class IncompleteStateBox (@JvmField val state : Incomplete )
1157
+ private fun Any?.boxIncomplete (): Any? = if (this is Incomplete ) IncompleteStateBox (this ) else this
1158
+ internal fun Any?.unboxState (): Any? = (this as ? IncompleteStateBox )?.state ? : this
1159
+
1146
1160
// --------------- helper classes & constants for job implementation
1147
1161
1148
1162
private const val COMPLETING_ALREADY_COMPLETING = 0
@@ -1154,9 +1168,11 @@ private const val RETRY = -1
1154
1168
private const val FALSE = 0
1155
1169
private const val TRUE = 1
1156
1170
1171
+ @SharedImmutable
1157
1172
private val SEALED = Symbol (" SEALED" )
1158
-
1173
+ @SharedImmutable
1159
1174
private val EMPTY_NEW = Empty (false )
1175
+ @SharedImmutable
1160
1176
private val EMPTY_ACTIVE = Empty (true )
1161
1177
1162
1178
private class Empty (override val isActive : Boolean ) : Incomplete {
@@ -1241,7 +1257,7 @@ private class ResumeAwaitOnCompletion<T>(
1241
1257
} else {
1242
1258
// Resuming with value in a cancellable way (AwaitContinuation is configured for this mode).
1243
1259
@Suppress(" UNCHECKED_CAST" )
1244
- continuation.resume(state as T )
1260
+ continuation.resume(state.unboxState() as T )
1245
1261
}
1246
1262
}
1247
1263
override fun toString () = " ResumeAwaitOnCompletion[$continuation ]"
0 commit comments