@@ -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,25 @@ 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 parent = context[Job ] ? : return // fast path -- don't do anything without parent
99
+ val handle = parent.invokeOnCompletion(
100
+ onCancelling = true ,
101
+ handler = ChildContinuation (this ).asHandler
102
+ )
103
+ parentHandle = handle
104
+ // now check our state _after_ registering, could have completed while we were registering,
105
+ // but only if parent was cancelled. Parent could be in a "cancelling" state for a while,
106
+ // so we are helping him and cleaning the node ourselves
107
+ if (isCompleted) {
108
+ // Can be invoked concurrently in 'parentCancelled', no problems here
109
+ handle.dispose()
110
+ parentHandle = NonDisposableHandle
111
+ }
97
112
}
98
113
99
114
private fun isReusable (): Boolean = delegate is DispatchedContinuation <* > && delegate.isReusable(this )
@@ -118,40 +133,6 @@ internal open class CancellableContinuationImpl<in T>(
118
133
return true
119
134
}
120
135
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
136
public override val callerFrame: CoroutineStackFrame ?
156
137
get() = delegate as ? CoroutineStackFrame
157
138
@@ -216,7 +197,7 @@ internal open class CancellableContinuationImpl<in T>(
216
197
217
198
private inline fun callCancelHandlerSafely (block : () -> Unit ) {
218
199
try {
219
- block()
200
+ block()
220
201
} catch (ex: Throwable ) {
221
202
// Handler should never fail, if it does -- it is an unhandled exception
222
203
handleCoroutineException(
@@ -274,10 +255,54 @@ internal open class CancellableContinuationImpl<in T>(
274
255
}
275
256
}
276
257
258
+ private fun checkCancellation (): Job ? {
259
+ // Don't need to check for non-reusable continuations, handle is already installed
260
+ if (! resumeMode.isReusableMode) return null
261
+ val parent: Job ?
262
+ if (parentHandle == null ) {
263
+ // No parent -- no postponed and no async cancellations
264
+ parent = context[Job ] ? : return null
265
+ /*
266
+ * Rare slow-path: parent handle is not yet installed in reusable CC,
267
+ * but parent is cancelled. Just let already existing machinery to figure everything out
268
+ * and advance state machine for us.
269
+ */
270
+ if (parent.isCancelled) {
271
+ installParentHandleReusable(parent)
272
+ return parent
273
+ }
274
+ } else {
275
+ // Parent handle is not null, no need to lookup it
276
+ parent = null
277
+ }
278
+ return parent
279
+ }
280
+
277
281
@PublishedApi
278
282
internal fun getResult (): Any? {
279
- setupCancellation()
280
- if (trySuspend()) return COROUTINE_SUSPENDED
283
+ val isReusable = isReusable()
284
+ /*
285
+ * Check postponed or async cancellation for reusable continuations.
286
+ * Returns job to avoid looking it up twice
287
+ */
288
+ val parentJob = checkCancellation()
289
+ // trySuspend may fail either if 'block' has resumed/cancelled a continuation
290
+ // or we got async cancellation from parent.
291
+ if (trySuspend()) {
292
+ /*
293
+ * We were neither resumed nor cancelled, time to suspend.
294
+ * But first we have to install parent cancellation handle (if we didn't yet),
295
+ * so CC could be properly resumed on parent cancellation.
296
+ */
297
+ if (parentHandle == null ) {
298
+ installParentHandleReusable(parentJob)
299
+ } else if (isReusable) {
300
+ releaseClaimedReusableContinuation()
301
+ }
302
+ return COROUTINE_SUSPENDED
303
+ } else if (isReusable) {
304
+ releaseClaimedReusableContinuation()
305
+ }
281
306
// otherwise, onCompletionInternal was already invoked & invoked tryResume, and the result is in the state
282
307
val state = this .state
283
308
if (state is CompletedExceptionally ) throw recoverStackTrace(state.cause, this )
@@ -296,6 +321,31 @@ internal open class CancellableContinuationImpl<in T>(
296
321
return getSuccessfulResult(state)
297
322
}
298
323
324
+ private fun installParentHandleReusable (parent : Job ? ) {
325
+ if (parent == null ) return // don't do anything without parent or if completed
326
+ // Install the handle
327
+ val handle = parent.invokeOnCompletion(
328
+ onCancelling = true ,
329
+ handler = ChildContinuation (this ).asHandler
330
+ )
331
+ parentHandle = handle
332
+ /*
333
+ * Finally release the continuation after installing the handle. If we were successful, then
334
+ * do nothing, it's ok to reuse the instance now.
335
+ * Otherwise, dispose the handle by ourselves.
336
+ */
337
+ releaseClaimedReusableContinuation()
338
+ }
339
+
340
+ private fun releaseClaimedReusableContinuation () {
341
+ val cancellationCause = (delegate as DispatchedContinuation <* >).tryReleaseClaimedContinuation(this ) ? : return
342
+ parentHandle?.let {
343
+ it.dispose()
344
+ parentHandle = NonDisposableHandle
345
+ }
346
+ cancel(cancellationCause)
347
+ }
348
+
299
349
override fun resumeWith (result : Result <T >) =
300
350
resumeImpl(result.toState(this ), resumeMode)
301
351
@@ -462,12 +512,15 @@ internal open class CancellableContinuationImpl<in T>(
462
512
463
513
/* *
464
514
* Detaches from the parent.
465
- * Invariant: used from [CoroutineDispatcher.releaseInterceptedContinuation] iff [isReusable] is `true`
515
+ * * Used from [CoroutineDispatcher.releaseInterceptedContinuation] iff [isReusable] is `true`
516
+ * * Used from [parentCancelled] iff [isReusable] is `false`
466
517
*/
467
518
internal fun detachChild () {
468
519
val handle = parentHandle
469
- handle?.dispose()
470
- parentHandle = NonDisposableHandle
520
+ if (handle != null ) {
521
+ handle.dispose()
522
+ parentHandle = NonDisposableHandle
523
+ }
471
524
}
472
525
473
526
// Note: Always returns RESUME_TOKEN | null
0 commit comments