From aa2c17d1970476d6c0e53be3ac0be5d5b564fcb0 Mon Sep 17 00:00:00 2001 From: Louis CAD Date: Wed, 2 Sep 2020 02:04:28 +0200 Subject: [PATCH 1/5] Add awaitCancellation() Resolves issue #2213 --- kotlinx-coroutines-core/common/src/Await.kt | 8 ++++++ .../common/test/AwaitCancellationTest.kt | 27 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 kotlinx-coroutines-core/common/test/AwaitCancellationTest.kt diff --git a/kotlinx-coroutines-core/common/src/Await.kt b/kotlinx-coroutines-core/common/src/Await.kt index dd1e1771f2..232a01d4a9 100644 --- a/kotlinx-coroutines-core/common/src/Await.kt +++ b/kotlinx-coroutines-core/common/src/Await.kt @@ -7,6 +7,14 @@ package kotlinx.coroutines import kotlinx.atomicfu.* import kotlin.coroutines.* +/** + * Suspends until cancellation, in which case it will throw a [CancellationException]. + * + * Handy because it returns [Nothing], allowing it to be used in any coroutine, + * regardless of the required return type. + */ +public suspend inline fun awaitCancellation(): Nothing = suspendCancellableCoroutine {} + /** * Awaits for completion of given deferred values without blocking a thread and resumes normally with the list of values * when all deferred computations are complete or resumes with the first thrown exception if any of computations diff --git a/kotlinx-coroutines-core/common/test/AwaitCancellationTest.kt b/kotlinx-coroutines-core/common/test/AwaitCancellationTest.kt new file mode 100644 index 0000000000..2fe0c914e6 --- /dev/null +++ b/kotlinx-coroutines-core/common/test/AwaitCancellationTest.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import kotlin.test.* + +class AwaitCancellationTest : TestBase() { + + @Test + fun testCancellation() = runTest(expected = { it is CancellationException }) { + expect(1) + coroutineScope { + val deferred: Deferred = async { + expect(2) + awaitCancellation() + } + yield() + expect(3) + require(deferred.isActive) + deferred.cancel() + finish(4) + deferred.await() + } + } +} From 6e692f5b5a2af74e50ded124928a785e348cfcd2 Mon Sep 17 00:00:00 2001 From: Louis CAD Date: Mon, 14 Sep 2020 03:02:42 +0200 Subject: [PATCH 2/5] Move awaitCancellation from Await.kt to Delay.kt --- kotlinx-coroutines-core/common/src/Await.kt | 8 -------- kotlinx-coroutines-core/common/src/Delay.kt | 8 ++++++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/Await.kt b/kotlinx-coroutines-core/common/src/Await.kt index 232a01d4a9..dd1e1771f2 100644 --- a/kotlinx-coroutines-core/common/src/Await.kt +++ b/kotlinx-coroutines-core/common/src/Await.kt @@ -7,14 +7,6 @@ package kotlinx.coroutines import kotlinx.atomicfu.* import kotlin.coroutines.* -/** - * Suspends until cancellation, in which case it will throw a [CancellationException]. - * - * Handy because it returns [Nothing], allowing it to be used in any coroutine, - * regardless of the required return type. - */ -public suspend inline fun awaitCancellation(): Nothing = suspendCancellableCoroutine {} - /** * Awaits for completion of given deferred values without blocking a thread and resumes normally with the list of values * when all deferred computations are complete or resumes with the first thrown exception if any of computations diff --git a/kotlinx-coroutines-core/common/src/Delay.kt b/kotlinx-coroutines-core/common/src/Delay.kt index ab80912269..f10becbe63 100644 --- a/kotlinx-coroutines-core/common/src/Delay.kt +++ b/kotlinx-coroutines-core/common/src/Delay.kt @@ -58,6 +58,14 @@ public interface Delay { DefaultDelay.invokeOnTimeout(timeMillis, block) } +/** + * Suspends until cancellation, in which case it will throw a [CancellationException]. + * + * Handy because it returns [Nothing], allowing it to be used in any coroutine, + * regardless of the required return type. + */ +public suspend inline fun awaitCancellation(): Nothing = suspendCancellableCoroutine {} + /** * Delays coroutine for a given time without blocking a thread and resumes it after a specified time. * This suspending function is cancellable. From 4e139ce649b52094c432b596445c36112ea35bb3 Mon Sep 17 00:00:00 2001 From: Louis CAD Date: Mon, 14 Sep 2020 03:25:00 +0200 Subject: [PATCH 3/5] Mention awaitCancellation in the Kdoc of delay --- kotlinx-coroutines-core/api/kotlinx-coroutines-core.api | 1 + kotlinx-coroutines-core/common/src/Delay.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index 36cbdb6960..5df13700d0 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -267,6 +267,7 @@ public final class kotlinx/coroutines/Delay$DefaultImpls { } public final class kotlinx/coroutines/DelayKt { + public static final fun awaitCancellation (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun delay-p9JZ4hM (DLkotlin/coroutines/Continuation;)Ljava/lang/Object; } diff --git a/kotlinx-coroutines-core/common/src/Delay.kt b/kotlinx-coroutines-core/common/src/Delay.kt index f10becbe63..07c2a95163 100644 --- a/kotlinx-coroutines-core/common/src/Delay.kt +++ b/kotlinx-coroutines-core/common/src/Delay.kt @@ -64,7 +64,7 @@ public interface Delay { * Handy because it returns [Nothing], allowing it to be used in any coroutine, * regardless of the required return type. */ -public suspend inline fun awaitCancellation(): Nothing = suspendCancellableCoroutine {} +public suspend fun awaitCancellation(): Nothing = suspendCancellableCoroutine {} /** * Delays coroutine for a given time without blocking a thread and resumes it after a specified time. From 87cb68e9821299b9ef21b7c94e8e8ce817a03e91 Mon Sep 17 00:00:00 2001 From: Louis CAD Date: Mon, 14 Sep 2020 03:25:17 +0200 Subject: [PATCH 4/5] Add usage examples to awaitCancellation() --- kotlinx-coroutines-core/common/src/Delay.kt | 28 +++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/kotlinx-coroutines-core/common/src/Delay.kt b/kotlinx-coroutines-core/common/src/Delay.kt index 07c2a95163..f41c817f85 100644 --- a/kotlinx-coroutines-core/common/src/Delay.kt +++ b/kotlinx-coroutines-core/common/src/Delay.kt @@ -63,6 +63,34 @@ public interface Delay { * * Handy because it returns [Nothing], allowing it to be used in any coroutine, * regardless of the required return type. + * + * Usage example in callback adapting code: + * + * ```kotlin + * fun currentTemperature(): Flow = callbackFlow { + * val callback = SensorCallback { degreesCelsius: Double -> + * trySend(Temperature.celsius(degreesCelsius)) + * } + * try { + * registerSensorCallback(callback) + * awaitCancellation() // Suspends to keep getting updates until cancellation. + * } finally { + * unregisterSensorCallback(callback) + * } + * } + * ``` + * + * Usage example in (non declarative) UI code: + * + * ```kotlin + * suspend fun showStuffUntilCancelled(content: Stuff): Nothing { + * someSubView.text = content.title + * anotherSubView.text = content.description + * someView.visibleInScope { + * awaitCancellation() // Suspends so the view stays visible. + * } + * } + * ``` */ public suspend fun awaitCancellation(): Nothing = suspendCancellableCoroutine {} From ed52823810a2e4c3edc6d5d85b9d91f5fa9d57f7 Mon Sep 17 00:00:00 2001 From: Louis CAD Date: Mon, 14 Sep 2020 03:25:29 +0200 Subject: [PATCH 5/5] Mention awaitCancellation in the Kdoc of delay --- kotlinx-coroutines-core/common/src/Delay.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kotlinx-coroutines-core/common/src/Delay.kt b/kotlinx-coroutines-core/common/src/Delay.kt index f41c817f85..95ef883b82 100644 --- a/kotlinx-coroutines-core/common/src/Delay.kt +++ b/kotlinx-coroutines-core/common/src/Delay.kt @@ -100,6 +100,8 @@ public suspend fun awaitCancellation(): Nothing = suspendCancellableCoroutine {} * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function * immediately resumes with [CancellationException]. * + * If you want to delay forever (until cancellation), consider using [awaitCancellation] instead. + * * Note that delay can be used in [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause. * * Implementation note: how exactly time is tracked is an implementation detail of [CoroutineDispatcher] in the context. @@ -118,6 +120,8 @@ public suspend fun delay(timeMillis: Long) { * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function * immediately resumes with [CancellationException]. * + * If you want to delay forever (until cancellation), consider using [awaitCancellation] instead. + * * Note that delay can be used in [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause. * * Implementation note: how exactly time is tracked is an implementation detail of [CoroutineDispatcher] in the context.