Skip to content

Commit 9ea4fea

Browse files
authored
Mention swallowing CancellationExceptions in the docs (#3302)
1 parent 937f430 commit 9ea4fea

10 files changed

+137
-74
lines changed

docs/topics/cancellation-and-timeouts.md

+43-7
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,42 @@ job: I'm sleeping 4 ...
103103
main: Now I can quit.
104104
-->
105105

106+
The same problem can be observed by catching a [CancellationException] and not rethrowing it:
107+
108+
```kotlin
109+
import kotlinx.coroutines.*
110+
111+
fun main() = runBlocking {
112+
//sampleStart
113+
val job = launch(Dispatchers.Default) {
114+
repeat(5) { i ->
115+
try {
116+
// print a message twice a second
117+
println("job: I'm sleeping $i ...")
118+
delay(500)
119+
} catch (e: Exception) {
120+
// log the exception
121+
println(e)
122+
}
123+
}
124+
}
125+
delay(1300L) // delay a bit
126+
println("main: I'm tired of waiting!")
127+
job.cancelAndJoin() // cancels the job and waits for its completion
128+
println("main: Now I can quit.")
129+
//sampleEnd
130+
}
131+
```
132+
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
133+
134+
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt).
135+
>
136+
{type="note"}
137+
138+
While catching `Exception` is an anti-pattern, this issue may surface in more subtle ways, like when using the
139+
[`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) function,
140+
which does not know rethrow [CancellationException].
141+
106142
## Making computation code cancellable
107143

108144
There are two approaches to making computation code cancellable. The first one is to periodically
@@ -137,7 +173,7 @@ fun main() = runBlocking {
137173
```
138174
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
139175

140-
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt).
176+
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt).
141177
>
142178
{type="note"}
143179

@@ -182,7 +218,7 @@ fun main() = runBlocking {
182218
```
183219
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
184220

185-
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt).
221+
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt).
186222
>
187223
{type="note"}
188224

@@ -237,7 +273,7 @@ fun main() = runBlocking {
237273
```
238274
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
239275

240-
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt).
276+
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt).
241277
>
242278
{type="note"}
243279

@@ -275,7 +311,7 @@ fun main() = runBlocking {
275311
```
276312
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
277313

278-
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt).
314+
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt).
279315
>
280316
{type="note"}
281317

@@ -318,7 +354,7 @@ fun main() = runBlocking {
318354
```
319355
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
320356

321-
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt).
357+
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt).
322358
>
323359
{type="note"}
324360

@@ -378,7 +414,7 @@ fun main() {
378414
```
379415
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
380416

381-
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt).
417+
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt).
382418
>
383419
{type="note"}
384420

@@ -431,7 +467,7 @@ fun main() {
431467
```
432468
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
433469

434-
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt).
470+
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-10.kt).
435471
>
436472
{type="note"}
437473

kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt

+8-8
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ package kotlinx.coroutines.guide.exampleCancel03
88
import kotlinx.coroutines.*
99

1010
fun main() = runBlocking {
11-
val startTime = currentTimeMillis()
1211
val job = launch(Dispatchers.Default) {
13-
var nextPrintTime = startTime
14-
var i = 0
15-
while (isActive) { // cancellable computation loop
16-
// print a message twice a second
17-
if (currentTimeMillis() >= nextPrintTime) {
18-
println("job: I'm sleeping ${i++} ...")
19-
nextPrintTime += 500L
12+
repeat(5) { i ->
13+
try {
14+
// print a message twice a second
15+
println("job: I'm sleeping $i ...")
16+
delay(500)
17+
} catch (e: Exception) {
18+
// log the exception
19+
println(e)
2020
}
2121
}
2222
}

kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt

+9-7
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ package kotlinx.coroutines.guide.exampleCancel04
88
import kotlinx.coroutines.*
99

1010
fun main() = runBlocking {
11-
val job = launch {
12-
try {
13-
repeat(1000) { i ->
14-
println("job: I'm sleeping $i ...")
15-
delay(500L)
11+
val startTime = currentTimeMillis()
12+
val job = launch(Dispatchers.Default) {
13+
var nextPrintTime = startTime
14+
var i = 0
15+
while (isActive) { // cancellable computation loop
16+
// print a message twice a second
17+
if (currentTimeMillis() >= nextPrintTime) {
18+
println("job: I'm sleeping ${i++} ...")
19+
nextPrintTime += 500L
1620
}
17-
} finally {
18-
println("job: I'm running finally")
1921
}
2022
}
2123
delay(1300L) // delay a bit

kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt

+1-5
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,7 @@ fun main() = runBlocking {
1515
delay(500L)
1616
}
1717
} finally {
18-
withContext(NonCancellable) {
19-
println("job: I'm running finally")
20-
delay(1000L)
21-
println("job: And I've just delayed for 1 sec because I'm non-cancellable")
22-
}
18+
println("job: I'm running finally")
2319
}
2420
}
2521
delay(1300L) // delay a bit

kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt

+16-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,22 @@ package kotlinx.coroutines.guide.exampleCancel06
88
import kotlinx.coroutines.*
99

1010
fun main() = runBlocking {
11-
withTimeout(1300L) {
12-
repeat(1000) { i ->
13-
println("I'm sleeping $i ...")
14-
delay(500L)
11+
val job = launch {
12+
try {
13+
repeat(1000) { i ->
14+
println("job: I'm sleeping $i ...")
15+
delay(500L)
16+
}
17+
} finally {
18+
withContext(NonCancellable) {
19+
println("job: I'm running finally")
20+
delay(1000L)
21+
println("job: And I've just delayed for 1 sec because I'm non-cancellable")
22+
}
1523
}
1624
}
25+
delay(1300L) // delay a bit
26+
println("main: I'm tired of waiting!")
27+
job.cancelAndJoin() // cancels the job and waits for its completion
28+
println("main: Now I can quit.")
1729
}

kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt

+1-3
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,10 @@ package kotlinx.coroutines.guide.exampleCancel07
88
import kotlinx.coroutines.*
99

1010
fun main() = runBlocking {
11-
val result = withTimeoutOrNull(1300L) {
11+
withTimeout(1300L) {
1212
repeat(1000) { i ->
1313
println("I'm sleeping $i ...")
1414
delay(500L)
1515
}
16-
"Done" // will get cancelled before it produces this result
1716
}
18-
println("Result is $result")
1917
}

kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt

+7-19
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,13 @@ package kotlinx.coroutines.guide.exampleCancel08
77

88
import kotlinx.coroutines.*
99

10-
var acquired = 0
11-
12-
class Resource {
13-
init { acquired++ } // Acquire the resource
14-
fun close() { acquired-- } // Release the resource
15-
}
16-
17-
fun main() {
18-
runBlocking {
19-
repeat(100_000) { // Launch 100K coroutines
20-
launch {
21-
val resource = withTimeout(60) { // Timeout of 60 ms
22-
delay(50) // Delay for 50 ms
23-
Resource() // Acquire a resource and return it from withTimeout block
24-
}
25-
resource.close() // Release the resource
26-
}
10+
fun main() = runBlocking {
11+
val result = withTimeoutOrNull(1300L) {
12+
repeat(1000) { i ->
13+
println("I'm sleeping $i ...")
14+
delay(500L)
2715
}
16+
"Done" // will get cancelled before it produces this result
2817
}
29-
// Outside of runBlocking all coroutines have completed
30-
println(acquired) // Print the number of resources still acquired
18+
println("Result is $result")
3119
}

kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt

+4-9
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,11 @@ fun main() {
1818
runBlocking {
1919
repeat(100_000) { // Launch 100K coroutines
2020
launch {
21-
var resource: Resource? = null // Not acquired yet
22-
try {
23-
withTimeout(60) { // Timeout of 60 ms
24-
delay(50) // Delay for 50 ms
25-
resource = Resource() // Store a resource to the variable if acquired
26-
}
27-
// We can do something else with the resource here
28-
} finally {
29-
resource?.close() // Release the resource if it was acquired
21+
val resource = withTimeout(60) { // Timeout of 60 ms
22+
delay(50) // Delay for 50 ms
23+
Resource() // Acquire a resource and return it from withTimeout block
3024
}
25+
resource.close() // Release the resource
3126
}
3227
}
3328
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
// This file was automatically generated from cancellation-and-timeouts.md by Knit tool. Do not edit.
6+
package kotlinx.coroutines.guide.exampleCancel10
7+
8+
import kotlinx.coroutines.*
9+
10+
var acquired = 0
11+
12+
class Resource {
13+
init { acquired++ } // Acquire the resource
14+
fun close() { acquired-- } // Release the resource
15+
}
16+
17+
fun main() {
18+
runBlocking {
19+
repeat(100_000) { // Launch 100K coroutines
20+
launch {
21+
var resource: Resource? = null // Not acquired yet
22+
try {
23+
withTimeout(60) { // Timeout of 60 ms
24+
delay(50) // Delay for 50 ms
25+
resource = Resource() // Store a resource to the variable if acquired
26+
}
27+
// We can do something else with the resource here
28+
} finally {
29+
resource?.close() // Release the resource if it was acquired
30+
}
31+
}
32+
}
33+
}
34+
// Outside of runBlocking all coroutines have completed
35+
println(acquired) // Print the number of resources still acquired
36+
}

kotlinx-coroutines-core/jvm/test/guide/test/CancellationGuideTest.kt

+12-12
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ class CancellationGuideTest {
3434
}
3535

3636
@Test
37-
fun testExampleCancel03() {
38-
test("ExampleCancel03") { kotlinx.coroutines.guide.exampleCancel03.main() }.verifyLines(
37+
fun testExampleCancel04() {
38+
test("ExampleCancel04") { kotlinx.coroutines.guide.exampleCancel04.main() }.verifyLines(
3939
"job: I'm sleeping 0 ...",
4040
"job: I'm sleeping 1 ...",
4141
"job: I'm sleeping 2 ...",
@@ -45,8 +45,8 @@ class CancellationGuideTest {
4545
}
4646

4747
@Test
48-
fun testExampleCancel04() {
49-
test("ExampleCancel04") { kotlinx.coroutines.guide.exampleCancel04.main() }.verifyLines(
48+
fun testExampleCancel05() {
49+
test("ExampleCancel05") { kotlinx.coroutines.guide.exampleCancel05.main() }.verifyLines(
5050
"job: I'm sleeping 0 ...",
5151
"job: I'm sleeping 1 ...",
5252
"job: I'm sleeping 2 ...",
@@ -57,8 +57,8 @@ class CancellationGuideTest {
5757
}
5858

5959
@Test
60-
fun testExampleCancel05() {
61-
test("ExampleCancel05") { kotlinx.coroutines.guide.exampleCancel05.main() }.verifyLines(
60+
fun testExampleCancel06() {
61+
test("ExampleCancel06") { kotlinx.coroutines.guide.exampleCancel06.main() }.verifyLines(
6262
"job: I'm sleeping 0 ...",
6363
"job: I'm sleeping 1 ...",
6464
"job: I'm sleeping 2 ...",
@@ -70,8 +70,8 @@ class CancellationGuideTest {
7070
}
7171

7272
@Test
73-
fun testExampleCancel06() {
74-
test("ExampleCancel06") { kotlinx.coroutines.guide.exampleCancel06.main() }.verifyLinesStartWith(
73+
fun testExampleCancel07() {
74+
test("ExampleCancel07") { kotlinx.coroutines.guide.exampleCancel07.main() }.verifyLinesStartWith(
7575
"I'm sleeping 0 ...",
7676
"I'm sleeping 1 ...",
7777
"I'm sleeping 2 ...",
@@ -80,8 +80,8 @@ class CancellationGuideTest {
8080
}
8181

8282
@Test
83-
fun testExampleCancel07() {
84-
test("ExampleCancel07") { kotlinx.coroutines.guide.exampleCancel07.main() }.verifyLines(
83+
fun testExampleCancel08() {
84+
test("ExampleCancel08") { kotlinx.coroutines.guide.exampleCancel08.main() }.verifyLines(
8585
"I'm sleeping 0 ...",
8686
"I'm sleeping 1 ...",
8787
"I'm sleeping 2 ...",
@@ -90,8 +90,8 @@ class CancellationGuideTest {
9090
}
9191

9292
@Test
93-
fun testExampleCancel09() {
94-
test("ExampleCancel09") { kotlinx.coroutines.guide.exampleCancel09.main() }.verifyLines(
93+
fun testExampleCancel10() {
94+
test("ExampleCancel10") { kotlinx.coroutines.guide.exampleCancel10.main() }.verifyLines(
9595
"0"
9696
)
9797
}

0 commit comments

Comments
 (0)