Skip to content

Commit 8bc7bb6

Browse files
committed
Simplify reusable continuations ever further
* Leverage the fact that it's non-atomic and do not check it for cancellation prematurely. It increases the performance of fast-path, but potentially affects rare cancellation cases * Fix AwaitContinuation that has parentHandle == null but is not reusable
1 parent 9306f8c commit 8bc7bb6

File tree

4 files changed

+14
-37
lines changed

4 files changed

+14
-37
lines changed

kotlinx-coroutines-core/common/src/CancellableContinuation.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public interface CancellableContinuation<in T> : Continuation<T> {
107107
* Internal function that setups cancellation behavior in [suspendCancellableCoroutine].
108108
* It's illegal to call this function in any non-`kotlinx.coroutines` code and
109109
* such calls lead to undefined behaviour.
110-
* Exposes in our ABI since 1.0.0 withing `suspendCancellableCoroutine` body.
110+
* Exposed in our ABI since 1.0.0 withing `suspendCancellableCoroutine` body.
111111
*
112112
* @suppress **This is unstable API and it is subject to change.**
113113
*/

kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt

+6-34
Original file line numberDiff line numberDiff line change
@@ -255,37 +255,9 @@ internal open class CancellableContinuationImpl<in T>(
255255
}
256256
}
257257

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-
281258
@PublishedApi
282259
internal fun getResult(): Any? {
283260
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()
289261
// trySuspend may fail either if 'block' has resumed/cancelled a continuation
290262
// or we got async cancellation from parent.
291263
if (trySuspend()) {
@@ -295,7 +267,7 @@ internal open class CancellableContinuationImpl<in T>(
295267
* so CC could be properly resumed on parent cancellation.
296268
*/
297269
if (parentHandle == null) {
298-
installParentHandleReusable(parentJob)
270+
installParentHandleReusable()
299271
} else if (isReusable) {
300272
releaseClaimedReusableContinuation()
301273
}
@@ -321,14 +293,13 @@ internal open class CancellableContinuationImpl<in T>(
321293
return getSuccessfulResult(state)
322294
}
323295

324-
private fun installParentHandleReusable(parent: Job?) {
325-
if (parent == null) return // don't do anything without parent or if completed
296+
private fun installParentHandleReusable() {
297+
val parent = context[Job] ?: return // don't do anything without parent or if completed
326298
// Install the handle
327-
val handle = parent.invokeOnCompletion(
299+
parentHandle = parent.invokeOnCompletion(
328300
onCancelling = true,
329301
handler = ChildContinuation(this).asHandler
330302
)
331-
parentHandle = handle
332303
/*
333304
* Finally release the continuation after installing the handle. If we were successful, then
334305
* do nothing, it's ok to reuse the instance now.
@@ -338,7 +309,8 @@ internal open class CancellableContinuationImpl<in T>(
338309
}
339310

340311
private fun releaseClaimedReusableContinuation() {
341-
val cancellationCause = (delegate as DispatchedContinuation<*>).tryReleaseClaimedContinuation(this) ?: return
312+
// Cannot be casted if e.g. invoked from `installParentHandleReusable` for context without dispatchers, but with Job in it
313+
val cancellationCause = (delegate as? DispatchedContinuation<*>)?.tryReleaseClaimedContinuation(this) ?: return
342314
parentHandle?.let {
343315
it.dispose()
344316
parentHandle = NonDisposableHandle

kotlinx-coroutines-core/common/src/JobSupport.kt

+5
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,11 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
11601160
delegate: Continuation<T>,
11611161
private val job: JobSupport
11621162
) : CancellableContinuationImpl<T>(delegate, MODE_CANCELLABLE) {
1163+
1164+
init {
1165+
initCancellability()
1166+
}
1167+
11631168
override fun getContinuationCancellationCause(parent: Job): Throwable {
11641169
val state = job.state
11651170
/*

kotlinx-coroutines-core/jvm/test/CancelledAwaitStressTest.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package kotlinx.coroutines
@@ -52,4 +52,4 @@ class CancelledAwaitStressTest : TestBase() {
5252
private fun keepMe(a: ByteArray) {
5353
// does nothing, makes sure the variable is kept in state-machine
5454
}
55-
}
55+
}

0 commit comments

Comments
 (0)