@@ -72,10 +72,7 @@ internal open class CancellableContinuationImpl<in T>(
72
72
*/
73
73
private val _state = atomic<Any ?>(Active )
74
74
75
- private val _parentHandle = atomic<DisposableHandle ?>(null )
76
- private var parentHandle: DisposableHandle ?
77
- get() = _parentHandle .value
78
- set(value) { _parentHandle .value = value }
75
+ private var parentHandle: DisposableHandle ? = null
79
76
80
77
internal val state: Any? get() = _state .value
81
78
@@ -93,7 +90,21 @@ internal open class CancellableContinuationImpl<in T>(
93
90
}
94
91
95
92
public override fun initCancellability () {
96
- setupCancellation()
93
+ /*
94
+ * Invariant: at the moment of invocation, `this` has not yet
95
+ * leaked to user code and no one is able to invoke `resume` or `cancel`
96
+ * on it yet. Also, this function is not invoked for reusable continuations.
97
+ */
98
+ val handle = installParentHandle()
99
+ ? : return // fast path -- don't do anything without parent
100
+ // now check our state _after_ registering, could have completed while we were registering,
101
+ // but only if parent was cancelled. Parent could be in a "cancelling" state for a while,
102
+ // so we are helping it and cleaning the node ourselves
103
+ if (isCompleted) {
104
+ // Can be invoked concurrently in 'parentCancelled', no problems here
105
+ handle.dispose()
106
+ parentHandle = NonDisposableHandle
107
+ }
97
108
}
98
109
99
110
private fun isReusable (): Boolean = delegate is DispatchedContinuation <* > && delegate.isReusable(this )
@@ -118,40 +129,6 @@ internal open class CancellableContinuationImpl<in T>(
118
129
return true
119
130
}
120
131
121
- /* *
122
- * Setups parent cancellation and checks for postponed cancellation in the case of reusable continuations.
123
- * It is only invoked from an internal [getResult] function for reusable continuations
124
- * and from [suspendCancellableCoroutine] to establish a cancellation before registering CC anywhere.
125
- */
126
- private fun setupCancellation () {
127
- if (checkCompleted()) return
128
- if (parentHandle != = null ) return // fast path 2 -- was already initialized
129
- val parent = delegate.context[Job ] ? : return // fast path 3 -- don't do anything without parent
130
- val handle = parent.invokeOnCompletion(
131
- onCancelling = true ,
132
- handler = ChildContinuation (this ).asHandler
133
- )
134
- parentHandle = handle
135
- // now check our state _after_ registering (could have completed while we were registering)
136
- // Also note that we do not dispose parent for reusable continuations, dispatcher will do that for us
137
- if (isCompleted && ! isReusable()) {
138
- handle.dispose() // it is Ok to call dispose twice -- here and in disposeParentHandle
139
- parentHandle = NonDisposableHandle // release it just in case, to aid GC
140
- }
141
- }
142
-
143
- private fun checkCompleted (): Boolean {
144
- val completed = isCompleted
145
- if (! resumeMode.isReusableMode) return completed // Do not check postponed cancellation for non-reusable continuations
146
- val dispatched = delegate as ? DispatchedContinuation <* > ? : return completed
147
- val cause = dispatched.checkPostponedCancellation(this ) ? : return completed
148
- if (! completed) {
149
- // Note: this cancel may fail if one more concurrent cancel is currently being invoked
150
- cancel(cause)
151
- }
152
- return true
153
- }
154
-
155
132
public override val callerFrame: CoroutineStackFrame ?
156
133
get() = delegate as ? CoroutineStackFrame
157
134
@@ -188,7 +165,9 @@ internal open class CancellableContinuationImpl<in T>(
188
165
*/
189
166
private fun cancelLater (cause : Throwable ): Boolean {
190
167
if (! resumeMode.isReusableMode) return false
191
- val dispatched = (delegate as ? DispatchedContinuation <* >) ? : return false
168
+ // Ensure that we are postponing cancellation to the right instance
169
+ if (! isReusable()) return false
170
+ val dispatched = delegate as DispatchedContinuation <* >
192
171
return dispatched.postponeCancellation(cause)
193
172
}
194
173
@@ -216,7 +195,7 @@ internal open class CancellableContinuationImpl<in T>(
216
195
217
196
private inline fun callCancelHandlerSafely (block : () -> Unit ) {
218
197
try {
219
- block()
198
+ block()
220
199
} catch (ex: Throwable ) {
221
200
// Handler should never fail, if it does -- it is an unhandled exception
222
201
handleCoroutineException(
@@ -276,9 +255,33 @@ internal open class CancellableContinuationImpl<in T>(
276
255
277
256
@PublishedApi
278
257
internal fun getResult (): Any? {
279
- setupCancellation()
280
- if (trySuspend()) return COROUTINE_SUSPENDED
258
+ val isReusable = isReusable()
259
+ // trySuspend may fail either if 'block' has resumed/cancelled a continuation
260
+ // or we got async cancellation from parent.
261
+ if (trySuspend()) {
262
+ /*
263
+ * We were neither resumed nor cancelled, time to suspend.
264
+ * But first we have to install parent cancellation handle (if we didn't yet),
265
+ * so CC could be properly resumed on parent cancellation.
266
+ */
267
+ if (parentHandle == null ) {
268
+ installParentHandle()
269
+ }
270
+ /*
271
+ * Release the continuation after installing the handle (if needed).
272
+ * If we were successful, then do nothing, it's ok to reuse the instance now.
273
+ * Otherwise, dispose the handle by ourselves.
274
+ */
275
+ if (isReusable) {
276
+ releaseClaimedReusableContinuation()
277
+ }
278
+ return COROUTINE_SUSPENDED
279
+ }
281
280
// otherwise, onCompletionInternal was already invoked & invoked tryResume, and the result is in the state
281
+ if (isReusable) {
282
+ // release claimed reusable continuation for the future reuse
283
+ releaseClaimedReusableContinuation()
284
+ }
282
285
val state = this .state
283
286
if (state is CompletedExceptionally ) throw recoverStackTrace(state.cause, this )
284
287
// if the parent job was already cancelled, then throw the corresponding cancellation exception
@@ -296,6 +299,28 @@ internal open class CancellableContinuationImpl<in T>(
296
299
return getSuccessfulResult(state)
297
300
}
298
301
302
+ private fun installParentHandle (): DisposableHandle ? {
303
+ val parent = context[Job ] ? : return null // don't do anything without a parent
304
+ // Install the handle
305
+ val handle = parent.invokeOnCompletion(
306
+ onCancelling = true ,
307
+ handler = ChildContinuation (this ).asHandler
308
+ )
309
+ parentHandle = handle
310
+ return handle
311
+ }
312
+
313
+ /* *
314
+ * Tries to release reusable continuation. It can fail is there was an asynchronous cancellation,
315
+ * in which case it detaches from the parent and cancels this continuation.
316
+ */
317
+ private fun releaseClaimedReusableContinuation () {
318
+ // Cannot be casted if e.g. invoked from `installParentHandleReusable` for context without dispatchers, but with Job in it
319
+ val cancellationCause = (delegate as ? DispatchedContinuation <* >)?.tryReleaseClaimedContinuation(this ) ? : return
320
+ detachChild()
321
+ cancel(cancellationCause)
322
+ }
323
+
299
324
override fun resumeWith (result : Result <T >) =
300
325
resumeImpl(result.toState(this ), resumeMode)
301
326
@@ -462,11 +487,10 @@ internal open class CancellableContinuationImpl<in T>(
462
487
463
488
/* *
464
489
* Detaches from the parent.
465
- * Invariant: used from [CoroutineDispatcher.releaseInterceptedContinuation] iff [isReusable] is `true`
466
490
*/
467
491
internal fun detachChild () {
468
- val handle = parentHandle
469
- handle? .dispose()
492
+ val handle = parentHandle ? : return
493
+ handle.dispose()
470
494
parentHandle = NonDisposableHandle
471
495
}
472
496
0 commit comments