Skip to content

Commit

Permalink
Mention swallowing CancellationExceptions in the docs (Kotlin#3302)
Browse files Browse the repository at this point in the history
  • Loading branch information
dkhalanskyjb authored and pablobaxter committed Sep 14, 2022
1 parent 8c657ee commit e148696
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 74 deletions.
50 changes: 43 additions & 7 deletions docs/topics/cancellation-and-timeouts.md
Expand Up @@ -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
Expand Down Expand Up @@ -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"}

Expand Down Expand Up @@ -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"}

Expand Down Expand Up @@ -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"}

Expand Down Expand Up @@ -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"}

Expand Down Expand Up @@ -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"}

Expand Down Expand Up @@ -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"}

Expand Down Expand Up @@ -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"}

Expand Down
16 changes: 8 additions & 8 deletions kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt
Expand Up @@ -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)
}
}
}
Expand Down
16 changes: 9 additions & 7 deletions kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt
Expand Up @@ -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
Expand Down
6 changes: 1 addition & 5 deletions kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt
Expand Up @@ -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
Expand Down
20 changes: 16 additions & 4 deletions kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt
Expand Up @@ -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.")
}
4 changes: 1 addition & 3 deletions kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt
Expand Up @@ -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")
}
26 changes: 7 additions & 19 deletions kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt
Expand Up @@ -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")
}
13 changes: 4 additions & 9 deletions kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt
Expand Up @@ -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
}
}
}
Expand Down
36 changes: 36 additions & 0 deletions 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
}
Expand Up @@ -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 ...",
Expand All @@ -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 ...",
Expand All @@ -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 ...",
Expand All @@ -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 ...",
Expand All @@ -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 ...",
Expand All @@ -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"
)
}
Expand Down

0 comments on commit e148696

Please sign in to comment.