Careeroza
CancellationException Pitfalls
Swallowing Cancellation
A catch (e: Exception) also catches CancellationException. The coroutine then "forgets" that it was cancelled and keeps going. The same happens with runCatching { }.
Example
The bad loop keeps running after cancel; each delay fails immediately and is swallowed.
import kotlinx.coroutines.*
fun main() = runBlocking {
val bad = launch {
repeat(4) { i ->
try {
delay(100)
println("bad: tick $i")
} catch (e: Exception) {
println("bad: swallowed ${e::class.simpleName}")
}
}
println("bad: loop finished anyway")
}
delay(150)
bad.cancelAndJoin()
val good = launch {
repeat(4) { i ->
try {
delay(100)
println("good: tick $i")
} catch (e: CancellationException) {
throw e // always rethrow
} catch (e: Exception) {
println("good: real error ${e.message}")
}
}
println("good: never reached")
}
delay(150)
good.cancelAndJoin()
println("Done")
}Output
bad: tick 0 bad: swallowed JobCancellationException bad: swallowed JobCancellationException bad: swallowed JobCancellationException bad: loop finished anyway good: tick 0 Done
Rules
- Catch specific exceptions (
IOException,HttpException) instead ofException. - If you must catch
Exception, rethrowCancellationExceptionfirst, or callcoroutineContext.ensureActive()in the catch. - Do not throw
CancellationExceptionyourself to signal errors; it is treated as a normal cancel and will not reach exception handlers.