@@ -19,7 +19,7 @@ package kotlinx.coroutines.experimental
19
19
import kotlinx.coroutines.experimental.internal.LockFreeLinkedListNode
20
20
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater
21
21
import kotlin.coroutines.experimental.Continuation
22
- import kotlin.coroutines.experimental.ContinuationInterceptor
22
+ import kotlin.coroutines.experimental.CoroutineContext
23
23
import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED
24
24
import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
25
25
import kotlin.coroutines.experimental.suspendCoroutine
@@ -57,18 +57,27 @@ public interface CancellableContinuation<in T> : Continuation<T>, Job {
57
57
* Tries to resume this continuation with a given value and returns non-null object token if it was successful,
58
58
* or `null` otherwise (it was already resumed or cancelled). When non-null object was returned,
59
59
* [completeResume] must be invoked with it.
60
+ *
61
+ * When [idempotent] is not `null`, this function performs _idempotent_ operation, so that
62
+ * further invocations with the same non-null reference produce the same result.
63
+ *
64
+ * @suppress **This is unstable API and it is subject to change.**
60
65
*/
61
- public fun tryResume (value : T ): Any?
66
+ public fun tryResume (value : T , idempotent : Any? = null ): Any?
62
67
63
68
/* *
64
69
* Tries to resume this continuation with a given exception and returns non-null object token if it was successful,
65
70
* or `null` otherwise (it was already resumed or cancelled). When non-null object was returned,
66
71
* [completeResume] must be invoked with it.
72
+ *
73
+ * @suppress **This is unstable API and it is subject to change.**
67
74
*/
68
75
public fun tryResumeWithException (exception : Throwable ): Any?
69
76
70
77
/* *
71
78
* Completes the execution of [tryResume] or [tryResumeWithException] on its non-null result.
79
+ *
80
+ * @suppress **This is unstable API and it is subject to change.**
72
81
*/
73
82
public fun completeResume (token : Any )
74
83
@@ -113,7 +122,7 @@ public inline suspend fun <T> suspendCancellableCoroutine(
113
122
crossinline block : (CancellableContinuation <T >) -> Unit
114
123
): T =
115
124
suspendCoroutineOrReturn { cont ->
116
- val cancellable = CancellableContinuationImpl (cont, getParentJobOrAbort(cont), active = true )
125
+ val cancellable = CancellableContinuationImpl (cont, active = true )
117
126
if (! holdCancellability) cancellable.initCancellability()
118
127
block(cancellable)
119
128
cancellable.getResult()
@@ -140,58 +149,67 @@ private class RemoveOnCancel(
140
149
override fun toString () = " RemoveOnCancel[$node ]"
141
150
}
142
151
143
- @PublishedApi
144
- internal fun getParentJobOrAbort (cont : Continuation <* >): Job ? {
145
- val job = cont.context[Job ]
146
- // fast path when parent job is already complete (we don't even construct CancellableContinuationImpl object)
147
- if (job != null && ! job.isActive) throw job.getCompletionException()
148
- return job
149
- }
150
-
151
152
@PublishedApi
152
153
internal open class CancellableContinuationImpl <in T >(
153
- private val delegate : Continuation < T >,
154
- private val parentJob : Job ? ,
154
+ @JvmField
155
+ protected val delegate : Continuation < T > ,
155
156
active : Boolean
156
- ) : AbstractCoroutine<T>(delegate.context, active), CancellableContinuation<T> {
157
+ ) : AbstractCoroutine<T>(active), CancellableContinuation<T> {
157
158
@Volatile
158
159
private var decision = UNDECIDED
159
160
160
- private companion object {
161
+ override val parentContext: CoroutineContext
162
+ get() = delegate.context
163
+
164
+ protected companion object {
165
+ @JvmStatic
161
166
val DECISION : AtomicIntegerFieldUpdater <CancellableContinuationImpl <* >> =
162
167
AtomicIntegerFieldUpdater .newUpdater(CancellableContinuationImpl ::class .java, " decision" )
163
168
164
169
const val UNDECIDED = 0
165
170
const val SUSPENDED = 1
166
171
const val RESUMED = 2
167
- const val YIELD = 3 // used by cancellable "yield"
168
- const val UNDISPATCHED = 4 // used by "undispatchedXXX"
172
+
173
+ const val MODE_UNDISPATCHED = 1
174
+ const val MODE_DIRECT = 2
175
+
176
+ @Suppress(" UNCHECKED_CAST" )
177
+ fun <T > getSuccessfulResult (state : Any? ): T = if (state is CompletedIdempotentResult ) state.result as T else state as T
169
178
}
170
179
171
180
override fun initCancellability () {
172
- initParentJob(parentJob )
181
+ initParentJob(delegate.context[ Job ] )
173
182
}
174
183
175
184
@PublishedApi
176
185
internal fun getResult (): Any? {
177
186
val decision = this .decision // volatile read
178
- when (decision) {
179
- UNDECIDED -> if (DECISION .compareAndSet(this , UNDECIDED , SUSPENDED )) return COROUTINE_SUSPENDED
180
- YIELD -> return COROUTINE_SUSPENDED
181
- }
187
+ if (decision == UNDECIDED && DECISION .compareAndSet(this , UNDECIDED , SUSPENDED )) return COROUTINE_SUSPENDED
182
188
// otherwise, afterCompletion was already invoked, and the result is in the state
183
189
val state = this .state
184
190
if (state is CompletedExceptionally ) throw state.exception
185
- return state
191
+ return getSuccessfulResult( state)
186
192
}
187
193
188
194
override val isCancelled: Boolean get() = state is Cancelled
189
195
190
- override fun tryResume (value : T ): Any? {
196
+ override fun tryResume (value : T , idempotent : Any? ): Any? {
191
197
while (true ) { // lock-free loop on state
192
198
val state = this .state // atomic read
193
199
when (state) {
194
- is Incomplete -> if (tryUpdateState(state, value)) return state
200
+ is Incomplete -> {
201
+ val idempotentStart = state.idempotentStart
202
+ val update: Any? = if (idempotent == null && idempotentStart == null ) value else
203
+ CompletedIdempotentResult (idempotentStart, idempotent, value, state)
204
+ if (tryUpdateState(state, update)) return state
205
+ }
206
+ is CompletedIdempotentResult -> {
207
+ if (state.idempotentResume == = idempotent) {
208
+ check(state.result == = value) { " Non-idempotent resume" }
209
+ return state.token
210
+ } else
211
+ return null
212
+ }
195
213
else -> return null // cannot resume -- not active anymore
196
214
}
197
215
}
@@ -201,56 +219,69 @@ internal open class CancellableContinuationImpl<in T>(
201
219
while (true ) { // lock-free loop on state
202
220
val state = this .state // atomic read
203
221
when (state) {
204
- is Incomplete -> if (tryUpdateState(state, CompletedExceptionally (exception))) return state
222
+ is Incomplete -> {
223
+ if (tryUpdateState(state, CompletedExceptionally (state.idempotentStart, exception))) return state
224
+ }
205
225
else -> return null // cannot resume -- not active anymore
206
226
}
207
227
}
208
228
}
209
229
210
230
override fun completeResume (token : Any ) {
211
- completeUpdateState(token, state)
231
+ completeUpdateState(token, state, mode = 0 )
212
232
}
213
233
214
- @Suppress(" UNCHECKED_CAST" )
215
- override fun afterCompletion (state : Any? ) {
234
+ override fun afterCompletion (state : Any? , mode : Int ) {
216
235
val decision = this .decision // volatile read
217
236
if (decision == UNDECIDED && DECISION .compareAndSet(this , UNDECIDED , RESUMED )) return // will get result in getResult
218
237
// otherwise, getResult has already commenced, i.e. it was resumed later or in other thread
219
- when {
220
- decision == UNDISPATCHED -> undispatchedCompletion(state)
221
- state is CompletedExceptionally -> delegate.resumeWithException(state.exception)
222
- decision == YIELD && delegate is DispatchedContinuation -> delegate.resumeYield(parentJob, state as T )
223
- else -> delegate.resume(state as T )
238
+ if (state is CompletedExceptionally ) {
239
+ val exception = state.exception
240
+ when (mode) {
241
+ 0 -> delegate.resumeWithException(exception)
242
+ MODE_UNDISPATCHED -> (delegate as DispatchedContinuation ).resumeUndispatchedWithException(exception)
243
+ MODE_DIRECT -> {
244
+ if (delegate is DispatchedContinuation )
245
+ delegate.continuation.resumeWithException(exception)
246
+ else
247
+ delegate.resumeWithException(exception)
248
+ }
249
+ else -> error(" Invalid mode $mode " )
250
+ }
251
+ } else {
252
+ val value = getSuccessfulResult<T >(state)
253
+ when (mode) {
254
+ 0 -> delegate.resume(value)
255
+ MODE_UNDISPATCHED -> (delegate as DispatchedContinuation ).resumeUndispatched(value)
256
+ MODE_DIRECT -> {
257
+ if (delegate is DispatchedContinuation )
258
+ delegate.continuation.resume(value)
259
+ else
260
+ delegate.resume(value)
261
+ }
262
+ else -> error(" Invalid mode $mode " )
263
+ }
224
264
}
225
265
}
226
266
227
- @Suppress(" UNCHECKED_CAST" )
228
- private fun undispatchedCompletion (state : Any? ) {
229
- delegate as DispatchedContinuation // type assertion -- was checked in resumeUndispatched
230
- if (state is CompletedExceptionally )
231
- delegate.resumeUndispatchedWithException(state.exception)
232
- else
233
- delegate.resumeUndispatched(state as T )
234
- }
235
-
236
- // can only be invoked in the same thread as getResult (see "yield"), afterCompletion may be concurrent
237
- fun resumeYield (value : T ) {
238
- if ((context[ContinuationInterceptor ] as ? CoroutineDispatcher )?.isDispatchNeeded(context) == true )
239
- DECISION .compareAndSet(this , UNDECIDED , YIELD ) // try mark as needing dispatch
240
- resume(value)
241
- }
242
-
243
267
override fun CoroutineDispatcher.resumeUndispatched (value : T ) {
244
268
val dc = delegate as ? DispatchedContinuation ? : throw IllegalArgumentException (" Must be used with DispatchedContinuation" )
245
269
check(dc.dispatcher == = this ) { " Must be invoked from the context CoroutineDispatcher" }
246
- DECISION .compareAndSet(this @CancellableContinuationImpl, SUSPENDED , UNDISPATCHED )
247
- resume(value)
270
+ resume(value, MODE_UNDISPATCHED )
248
271
}
249
272
250
273
override fun CoroutineDispatcher.resumeUndispatchedWithException (exception : Throwable ) {
251
274
val dc = delegate as ? DispatchedContinuation ? : throw IllegalArgumentException (" Must be used with DispatchedContinuation" )
252
275
check(dc.dispatcher == = this ) { " Must be invoked from the context CoroutineDispatcher" }
253
- DECISION .compareAndSet(this @CancellableContinuationImpl, SUSPENDED , UNDISPATCHED )
254
- resumeWithException(exception)
276
+ resumeWithException(exception, MODE_UNDISPATCHED )
277
+ }
278
+
279
+ private class CompletedIdempotentResult (
280
+ idempotentStart : Any? ,
281
+ @JvmField val idempotentResume : Any? ,
282
+ @JvmField val result : Any? ,
283
+ @JvmField val token : Incomplete
284
+ ) : CompletedIdempotentStart(idempotentStart) {
285
+ override fun toString (): String = " CompletedIdempotentResult[$result ]"
255
286
}
256
287
}
0 commit comments