Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions kotlinx-coroutines-core/common/src/CancellableContinuation.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kotlinx.coroutines

import kotlinx.coroutines.internal.*
import kotlin.contracts.*
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*

Expand Down Expand Up @@ -424,10 +425,12 @@ internal fun <T> CancellableContinuation<T>.invokeOnCancellation(handler: Cancel
* [CoroutineDispatcher] class, then there is no prompt cancellation guarantee. A custom continuation interceptor
* can resume execution of a previously suspended coroutine even if its job was already cancelled.
*/
@OptIn(ExperimentalContracts::class)
public suspend inline fun <T> suspendCancellableCoroutine(
crossinline block: (CancellableContinuation<T>) -> Unit
): T =
suspendCoroutineUninterceptedOrReturn { uCont ->
): T {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return suspendCoroutineUninterceptedOrReturn { uCont ->
val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
/*
* For non-atomic cancellation we setup parent-child relationship immediately
Expand All @@ -438,6 +441,7 @@ public suspend inline fun <T> suspendCancellableCoroutine(
block(cancellable)
cancellable.getResult()
}
}

/**
* Suspends the coroutine similar to [suspendCancellableCoroutine], but an instance of
Expand Down
11 changes: 11 additions & 0 deletions kotlinx-coroutines-core/common/test/CancellableContinuationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,15 @@ class CancellableContinuationTest : TestBase() {
})
finish(5)
}

/** Tests that the compiler recognizes that [suspendCancellableCoroutine] invokes its block exactly once. */
Copy link
Contributor

@murfel murfel Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by allowing the val variable

(the point of focus could be not immediately clear in future)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the whole test is five lines, there isn't a lot of room for misinterpreting what the test does, especially given that the KDoc describes that we're testing the EXACTLY_ONCE contract.

@Test
fun testSuspendCancellableCoroutineContract() = runTest {
val i: Int
suspendCancellableCoroutine { cont ->
i = 1
cont.resume(Unit)
}
assertEquals(1, i)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you set your editor to add newlines?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not at all straightforward, I'm afraid. Of course, I will make sure to do that if I learn that the lack of newlines in Kotlin files leads to some actual problems.