From e14869679dd1157e3152e37807f58cad69d525ee Mon Sep 17 00:00:00 2001 From: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> Date: Wed, 25 May 2022 12:54:57 +0300 Subject: [PATCH] Mention swallowing CancellationExceptions in the docs (#3302) --- docs/topics/cancellation-and-timeouts.md | 50 ++++++++++++++++--- .../jvm/test/guide/example-cancel-03.kt | 16 +++--- .../jvm/test/guide/example-cancel-04.kt | 16 +++--- .../jvm/test/guide/example-cancel-05.kt | 6 +-- .../jvm/test/guide/example-cancel-06.kt | 20 ++++++-- .../jvm/test/guide/example-cancel-07.kt | 4 +- .../jvm/test/guide/example-cancel-08.kt | 26 +++------- .../jvm/test/guide/example-cancel-09.kt | 13 ++--- .../jvm/test/guide/example-cancel-10.kt | 36 +++++++++++++ .../test/guide/test/CancellationGuideTest.kt | 24 ++++----- 10 files changed, 137 insertions(+), 74 deletions(-) create mode 100644 kotlinx-coroutines-core/jvm/test/guide/example-cancel-10.kt diff --git a/docs/topics/cancellation-and-timeouts.md b/docs/topics/cancellation-and-timeouts.md index 43977a9b44..f2e642ab22 100644 --- a/docs/topics/cancellation-and-timeouts.md +++ b/docs/topics/cancellation-and-timeouts.md @@ -103,6 +103,42 @@ job: I'm sleeping 4 ... main: Now I can quit. --> +The same problem can be observed by catching a [CancellationException] and not rethrowing it: + +```kotlin +import kotlinx.coroutines.* + +fun main() = runBlocking { +//sampleStart + val job = launch(Dispatchers.Default) { + repeat(5) { i -> + try { + // print a message twice a second + println("job: I'm sleeping $i ...") + delay(500) + } catch (e: Exception) { + // log the exception + println(e) + } + } + } + delay(1300L) // delay a bit + println("main: I'm tired of waiting!") + job.cancelAndJoin() // cancels the job and waits for its completion + println("main: Now I can quit.") +//sampleEnd +} +``` +{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} + +> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt). +> +{type="note"} + +While catching `Exception` is an anti-pattern, this issue may surface in more subtle ways, like when using the +[`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) function, +which does not know rethrow [CancellationException]. + ## Making computation code cancellable There are two approaches to making computation code cancellable. The first one is to periodically @@ -137,7 +173,7 @@ fun main() = runBlocking { ``` {kotlin-runnable="true" kotlin-min-compiler-version="1.3"} -> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt). +> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt). > {type="note"} @@ -182,7 +218,7 @@ fun main() = runBlocking { ``` {kotlin-runnable="true" kotlin-min-compiler-version="1.3"} -> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt). +> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt). > {type="note"} @@ -237,7 +273,7 @@ fun main() = runBlocking { ``` {kotlin-runnable="true" kotlin-min-compiler-version="1.3"} -> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt). +> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt). > {type="note"} @@ -275,7 +311,7 @@ fun main() = runBlocking { ``` {kotlin-runnable="true" kotlin-min-compiler-version="1.3"} -> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt). +> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt). > {type="note"} @@ -318,7 +354,7 @@ fun main() = runBlocking { ``` {kotlin-runnable="true" kotlin-min-compiler-version="1.3"} -> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt). +> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt). > {type="note"} @@ -378,7 +414,7 @@ fun main() { ``` {kotlin-runnable="true" kotlin-min-compiler-version="1.3"} -> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt). +> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt). > {type="note"} @@ -431,7 +467,7 @@ fun main() { ``` {kotlin-runnable="true" kotlin-min-compiler-version="1.3"} -> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt). +> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-10.kt). > {type="note"} diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt index 3daaf49b36..2cf0770a7d 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt @@ -8,15 +8,15 @@ package kotlinx.coroutines.guide.exampleCancel03 import kotlinx.coroutines.* fun main() = runBlocking { - val startTime = currentTimeMillis() val job = launch(Dispatchers.Default) { - var nextPrintTime = startTime - var i = 0 - while (isActive) { // cancellable computation loop - // print a message twice a second - if (currentTimeMillis() >= nextPrintTime) { - println("job: I'm sleeping ${i++} ...") - nextPrintTime += 500L + repeat(5) { i -> + try { + // print a message twice a second + println("job: I'm sleeping $i ...") + delay(500) + } catch (e: Exception) { + // log the exception + println(e) } } } diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt index b1b9a9bfd5..7d7f3bdee2 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt @@ -8,14 +8,16 @@ package kotlinx.coroutines.guide.exampleCancel04 import kotlinx.coroutines.* fun main() = runBlocking { - val job = launch { - try { - repeat(1000) { i -> - println("job: I'm sleeping $i ...") - delay(500L) + val startTime = currentTimeMillis() + val job = launch(Dispatchers.Default) { + var nextPrintTime = startTime + var i = 0 + while (isActive) { // cancellable computation loop + // print a message twice a second + if (currentTimeMillis() >= nextPrintTime) { + println("job: I'm sleeping ${i++} ...") + nextPrintTime += 500L } - } finally { - println("job: I'm running finally") } } delay(1300L) // delay a bit diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt index 9772ae5739..97ea956ec3 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt @@ -15,11 +15,7 @@ fun main() = runBlocking { delay(500L) } } finally { - withContext(NonCancellable) { - println("job: I'm running finally") - delay(1000L) - println("job: And I've just delayed for 1 sec because I'm non-cancellable") - } + println("job: I'm running finally") } } delay(1300L) // delay a bit diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt index e1afd057c4..ef66f5df09 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt @@ -8,10 +8,22 @@ package kotlinx.coroutines.guide.exampleCancel06 import kotlinx.coroutines.* fun main() = runBlocking { - withTimeout(1300L) { - repeat(1000) { i -> - println("I'm sleeping $i ...") - delay(500L) + val job = launch { + try { + repeat(1000) { i -> + println("job: I'm sleeping $i ...") + delay(500L) + } + } finally { + withContext(NonCancellable) { + println("job: I'm running finally") + delay(1000L) + println("job: And I've just delayed for 1 sec because I'm non-cancellable") + } } } + delay(1300L) // delay a bit + println("main: I'm tired of waiting!") + job.cancelAndJoin() // cancels the job and waits for its completion + println("main: Now I can quit.") } diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt index 8c57b42903..ea4a10353f 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt @@ -8,12 +8,10 @@ package kotlinx.coroutines.guide.exampleCancel07 import kotlinx.coroutines.* fun main() = runBlocking { - val result = withTimeoutOrNull(1300L) { + withTimeout(1300L) { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } - "Done" // will get cancelled before it produces this result } - println("Result is $result") } diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt index e7def132ae..79d08091c2 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt @@ -7,25 +7,13 @@ package kotlinx.coroutines.guide.exampleCancel08 import kotlinx.coroutines.* -var acquired = 0 - -class Resource { - init { acquired++ } // Acquire the resource - fun close() { acquired-- } // Release the resource -} - -fun main() { - runBlocking { - repeat(100_000) { // Launch 100K coroutines - launch { - val resource = withTimeout(60) { // Timeout of 60 ms - delay(50) // Delay for 50 ms - Resource() // Acquire a resource and return it from withTimeout block - } - resource.close() // Release the resource - } +fun main() = runBlocking { + val result = withTimeoutOrNull(1300L) { + repeat(1000) { i -> + println("I'm sleeping $i ...") + delay(500L) } + "Done" // will get cancelled before it produces this result } - // Outside of runBlocking all coroutines have completed - println(acquired) // Print the number of resources still acquired + println("Result is $result") } diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt index 95424f5108..13910a6c8a 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt @@ -18,16 +18,11 @@ fun main() { runBlocking { repeat(100_000) { // Launch 100K coroutines launch { - var resource: Resource? = null // Not acquired yet - try { - withTimeout(60) { // Timeout of 60 ms - delay(50) // Delay for 50 ms - resource = Resource() // Store a resource to the variable if acquired - } - // We can do something else with the resource here - } finally { - resource?.close() // Release the resource if it was acquired + val resource = withTimeout(60) { // Timeout of 60 ms + delay(50) // Delay for 50 ms + Resource() // Acquire a resource and return it from withTimeout block } + resource.close() // Release the resource } } } diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-10.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-10.kt new file mode 100644 index 0000000000..336f5e3a3a --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-10.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +// This file was automatically generated from cancellation-and-timeouts.md by Knit tool. Do not edit. +package kotlinx.coroutines.guide.exampleCancel10 + +import kotlinx.coroutines.* + +var acquired = 0 + +class Resource { + init { acquired++ } // Acquire the resource + fun close() { acquired-- } // Release the resource +} + +fun main() { + runBlocking { + repeat(100_000) { // Launch 100K coroutines + launch { + var resource: Resource? = null // Not acquired yet + try { + withTimeout(60) { // Timeout of 60 ms + delay(50) // Delay for 50 ms + resource = Resource() // Store a resource to the variable if acquired + } + // We can do something else with the resource here + } finally { + resource?.close() // Release the resource if it was acquired + } + } + } + } + // Outside of runBlocking all coroutines have completed + println(acquired) // Print the number of resources still acquired +} diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/CancellationGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/CancellationGuideTest.kt index 0cff63a834..871e4911e8 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/test/CancellationGuideTest.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/test/CancellationGuideTest.kt @@ -34,8 +34,8 @@ class CancellationGuideTest { } @Test - fun testExampleCancel03() { - test("ExampleCancel03") { kotlinx.coroutines.guide.exampleCancel03.main() }.verifyLines( + fun testExampleCancel04() { + test("ExampleCancel04") { kotlinx.coroutines.guide.exampleCancel04.main() }.verifyLines( "job: I'm sleeping 0 ...", "job: I'm sleeping 1 ...", "job: I'm sleeping 2 ...", @@ -45,8 +45,8 @@ class CancellationGuideTest { } @Test - fun testExampleCancel04() { - test("ExampleCancel04") { kotlinx.coroutines.guide.exampleCancel04.main() }.verifyLines( + fun testExampleCancel05() { + test("ExampleCancel05") { kotlinx.coroutines.guide.exampleCancel05.main() }.verifyLines( "job: I'm sleeping 0 ...", "job: I'm sleeping 1 ...", "job: I'm sleeping 2 ...", @@ -57,8 +57,8 @@ class CancellationGuideTest { } @Test - fun testExampleCancel05() { - test("ExampleCancel05") { kotlinx.coroutines.guide.exampleCancel05.main() }.verifyLines( + fun testExampleCancel06() { + test("ExampleCancel06") { kotlinx.coroutines.guide.exampleCancel06.main() }.verifyLines( "job: I'm sleeping 0 ...", "job: I'm sleeping 1 ...", "job: I'm sleeping 2 ...", @@ -70,8 +70,8 @@ class CancellationGuideTest { } @Test - fun testExampleCancel06() { - test("ExampleCancel06") { kotlinx.coroutines.guide.exampleCancel06.main() }.verifyLinesStartWith( + fun testExampleCancel07() { + test("ExampleCancel07") { kotlinx.coroutines.guide.exampleCancel07.main() }.verifyLinesStartWith( "I'm sleeping 0 ...", "I'm sleeping 1 ...", "I'm sleeping 2 ...", @@ -80,8 +80,8 @@ class CancellationGuideTest { } @Test - fun testExampleCancel07() { - test("ExampleCancel07") { kotlinx.coroutines.guide.exampleCancel07.main() }.verifyLines( + fun testExampleCancel08() { + test("ExampleCancel08") { kotlinx.coroutines.guide.exampleCancel08.main() }.verifyLines( "I'm sleeping 0 ...", "I'm sleeping 1 ...", "I'm sleeping 2 ...", @@ -90,8 +90,8 @@ class CancellationGuideTest { } @Test - fun testExampleCancel09() { - test("ExampleCancel09") { kotlinx.coroutines.guide.exampleCancel09.main() }.verifyLines( + fun testExampleCancel10() { + test("ExampleCancel10") { kotlinx.coroutines.guide.exampleCancel10.main() }.verifyLines( "0" ) }