1<!--- TEST_NAME CancellationGuideTest --> 2 3[//]: # (title: Cancellation and timeouts) 4 5This section covers coroutine cancellation and timeouts. 6 7## Cancelling coroutine execution 8 9In a long-running application you might need fine-grained control on your background coroutines. 10For example, a user might have closed the page that launched a coroutine and now its result 11is no longer needed and its operation can be cancelled. 12The [launch] function returns a [Job] that can be used to cancel the running coroutine: 13 14```kotlin 15import kotlinx.coroutines.* 16 17fun main() = runBlocking { 18//sampleStart 19 val job = launch { 20 repeat(1000) { i -> 21 println("job: I'm sleeping $i ...") 22 delay(500L) 23 } 24 } 25 delay(1300L) // delay a bit 26 println("main: I'm tired of waiting!") 27 job.cancel() // cancels the job 28 job.join() // waits for job's completion 29 println("main: Now I can quit.") 30//sampleEnd 31} 32``` 33{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} 34 35> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt). 36> 37{type="note"} 38 39It produces the following output: 40 41```text 42job: I'm sleeping 0 ... 43job: I'm sleeping 1 ... 44job: I'm sleeping 2 ... 45main: I'm tired of waiting! 46main: Now I can quit. 47``` 48 49<!--- TEST --> 50 51As soon as main invokes `job.cancel`, we don't see any output from the other coroutine because it was cancelled. 52There is also a [Job] extension function [cancelAndJoin] 53that combines [cancel][Job.cancel] and [join][Job.join] invocations. 54 55## Cancellation is cooperative 56 57Coroutine cancellation is _cooperative_. A coroutine code has to cooperate to be cancellable. 58All the suspending functions in `kotlinx.coroutines` are _cancellable_. They check for cancellation of 59coroutine and throw [CancellationException] when cancelled. However, if a coroutine is working in 60a computation and does not check for cancellation, then it cannot be cancelled, like the following 61example shows: 62 63```kotlin 64import kotlinx.coroutines.* 65 66fun main() = runBlocking { 67//sampleStart 68 val startTime = System.currentTimeMillis() 69 val job = launch(Dispatchers.Default) { 70 var nextPrintTime = startTime 71 var i = 0 72 while (i < 5) { // computation loop, just wastes CPU 73 // print a message twice a second 74 if (System.currentTimeMillis() >= nextPrintTime) { 75 println("job: I'm sleeping ${i++} ...") 76 nextPrintTime += 500L 77 } 78 } 79 } 80 delay(1300L) // delay a bit 81 println("main: I'm tired of waiting!") 82 job.cancelAndJoin() // cancels the job and waits for its completion 83 println("main: Now I can quit.") 84//sampleEnd 85} 86``` 87{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} 88 89> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt). 90> 91{type="note"} 92 93Run it to see that it continues to print "I'm sleeping" even after cancellation 94until the job completes by itself after five iterations. 95 96<!--- TEST 97job: I'm sleeping 0 ... 98job: I'm sleeping 1 ... 99job: I'm sleeping 2 ... 100main: I'm tired of waiting! 101job: I'm sleeping 3 ... 102job: I'm sleeping 4 ... 103main: Now I can quit. 104--> 105 106The same problem can be observed by catching a [CancellationException] and not rethrowing it: 107 108```kotlin 109import kotlinx.coroutines.* 110 111fun 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 138While 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, 140which does not rethrow [CancellationException]. 141 142## Making computation code cancellable 143 144There are two approaches to making computation code cancellable. The first one is to periodically 145invoke a suspending function that checks for cancellation. There is a [yield] function that is a good choice for that purpose. 146The other one is to explicitly check the cancellation status. Let us try the latter approach. 147 148Replace `while (i < 5)` in the previous example with `while (isActive)` and rerun it. 149 150```kotlin 151import kotlinx.coroutines.* 152 153fun main() = runBlocking { 154//sampleStart 155 val startTime = System.currentTimeMillis() 156 val job = launch(Dispatchers.Default) { 157 var nextPrintTime = startTime 158 var i = 0 159 while (isActive) { // cancellable computation loop 160 // print a message twice a second 161 if (System.currentTimeMillis() >= nextPrintTime) { 162 println("job: I'm sleeping ${i++} ...") 163 nextPrintTime += 500L 164 } 165 } 166 } 167 delay(1300L) // delay a bit 168 println("main: I'm tired of waiting!") 169 job.cancelAndJoin() // cancels the job and waits for its completion 170 println("main: Now I can quit.") 171//sampleEnd 172} 173``` 174{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} 175 176> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt). 177> 178{type="note"} 179 180As you can see, now this loop is cancelled. [isActive] is an extension property 181available inside the coroutine via the [CoroutineScope] object. 182 183<!--- TEST 184job: I'm sleeping 0 ... 185job: I'm sleeping 1 ... 186job: I'm sleeping 2 ... 187main: I'm tired of waiting! 188main: Now I can quit. 189--> 190 191## Closing resources with `finally` 192 193Cancellable suspending functions throw [CancellationException] on cancellation, which can be handled in 194the usual way. For example, the `try {...} finally {...}` expression and Kotlin's `use` function execute their 195finalization actions normally when a coroutine is cancelled: 196 197```kotlin 198import kotlinx.coroutines.* 199 200fun main() = runBlocking { 201//sampleStart 202 val job = launch { 203 try { 204 repeat(1000) { i -> 205 println("job: I'm sleeping $i ...") 206 delay(500L) 207 } 208 } finally { 209 println("job: I'm running finally") 210 } 211 } 212 delay(1300L) // delay a bit 213 println("main: I'm tired of waiting!") 214 job.cancelAndJoin() // cancels the job and waits for its completion 215 println("main: Now I can quit.") 216//sampleEnd 217} 218``` 219{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} 220 221> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt). 222> 223{type="note"} 224 225Both [join][Job.join] and [cancelAndJoin] wait for all finalization actions to complete, 226so the example above produces the following output: 227 228```text 229job: I'm sleeping 0 ... 230job: I'm sleeping 1 ... 231job: I'm sleeping 2 ... 232main: I'm tired of waiting! 233job: I'm running finally 234main: Now I can quit. 235``` 236 237<!--- TEST --> 238 239## Run non-cancellable block 240 241Any attempt to use a suspending function in the `finally` block of the previous example causes 242[CancellationException], because the coroutine running this code is cancelled. Usually, this is not a 243problem, since all well-behaving closing operations (closing a file, cancelling a job, or closing any kind of a 244communication channel) are usually non-blocking and do not involve any suspending functions. However, in the 245rare case when you need to suspend in a cancelled coroutine you can wrap the corresponding code in 246`withContext(NonCancellable) {...}` using [withContext] function and [NonCancellable] context as the following example shows: 247 248```kotlin 249import kotlinx.coroutines.* 250 251fun main() = runBlocking { 252//sampleStart 253 val job = launch { 254 try { 255 repeat(1000) { i -> 256 println("job: I'm sleeping $i ...") 257 delay(500L) 258 } 259 } finally { 260 withContext(NonCancellable) { 261 println("job: I'm running finally") 262 delay(1000L) 263 println("job: And I've just delayed for 1 sec because I'm non-cancellable") 264 } 265 } 266 } 267 delay(1300L) // delay a bit 268 println("main: I'm tired of waiting!") 269 job.cancelAndJoin() // cancels the job and waits for its completion 270 println("main: Now I can quit.") 271//sampleEnd 272} 273``` 274{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} 275 276> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt). 277> 278{type="note"} 279 280<!--- TEST 281job: I'm sleeping 0 ... 282job: I'm sleeping 1 ... 283job: I'm sleeping 2 ... 284main: I'm tired of waiting! 285job: I'm running finally 286job: And I've just delayed for 1 sec because I'm non-cancellable 287main: Now I can quit. 288--> 289 290## Timeout 291 292The most obvious practical reason to cancel execution of a coroutine 293is because its execution time has exceeded some timeout. 294While you can manually track the reference to the corresponding [Job] and launch a separate coroutine to cancel 295the tracked one after delay, there is a ready to use [withTimeout] function that does it. 296Look at the following example: 297 298```kotlin 299import kotlinx.coroutines.* 300 301fun main() = runBlocking { 302//sampleStart 303 withTimeout(1300L) { 304 repeat(1000) { i -> 305 println("I'm sleeping $i ...") 306 delay(500L) 307 } 308 } 309//sampleEnd 310} 311``` 312{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} 313 314> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt). 315> 316{type="note"} 317 318It produces the following output: 319 320```text 321I'm sleeping 0 ... 322I'm sleeping 1 ... 323I'm sleeping 2 ... 324Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms 325``` 326 327<!--- TEST STARTS_WITH --> 328 329The `TimeoutCancellationException` that is thrown by [withTimeout] is a subclass of [CancellationException]. 330We have not seen its stack trace printed on the console before. That is because 331inside a cancelled coroutine `CancellationException` is considered to be a normal reason for coroutine completion. 332However, in this example we have used `withTimeout` right inside the `main` function. 333 334Since cancellation is just an exception, all resources are closed in the usual way. 335You can wrap the code with timeout in a `try {...} catch (e: TimeoutCancellationException) {...}` block if 336you need to do some additional action specifically on any kind of timeout or use the [withTimeoutOrNull] function 337that is similar to [withTimeout] but returns `null` on timeout instead of throwing an exception: 338 339```kotlin 340import kotlinx.coroutines.* 341 342fun main() = runBlocking { 343//sampleStart 344 val result = withTimeoutOrNull(1300L) { 345 repeat(1000) { i -> 346 println("I'm sleeping $i ...") 347 delay(500L) 348 } 349 "Done" // will get cancelled before it produces this result 350 } 351 println("Result is $result") 352//sampleEnd 353} 354``` 355{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} 356 357> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt). 358> 359{type="note"} 360 361There is no longer an exception when running this code: 362 363```text 364I'm sleeping 0 ... 365I'm sleeping 1 ... 366I'm sleeping 2 ... 367Result is null 368``` 369 370<!--- TEST --> 371 372## Asynchronous timeout and resources 373 374<!-- 375 NOTE: Don't change this section name. It is being referenced to from within KDoc of withTimeout functions. 376--> 377 378The timeout event in [withTimeout] is asynchronous with respect to the code running in its block and may happen at any time, 379even right before the return from inside of the timeout block. Keep this in mind if you open or acquire some 380resource inside the block that needs closing or release outside of the block. 381 382For example, here we imitate a closeable resource with the `Resource` class that simply keeps track of how many times 383it was created by incrementing the `acquired` counter and decrementing the counter in its `close` function. 384Now let us create a lot of coroutines, each of which creates a `Resource` at the end of the `withTimeout` block 385and releases the resource outside the block. We add a small delay so that it is more likely that the timeout occurs 386right when the `withTimeout` block is already finished, which will cause a resource leak. 387 388```kotlin 389import kotlinx.coroutines.* 390 391//sampleStart 392var acquired = 0 393 394class Resource { 395 init { acquired++ } // Acquire the resource 396 fun close() { acquired-- } // Release the resource 397} 398 399fun main() { 400 runBlocking { 401 repeat(10_000) { // Launch 10K coroutines 402 launch { 403 val resource = withTimeout(60) { // Timeout of 60 ms 404 delay(50) // Delay for 50 ms 405 Resource() // Acquire a resource and return it from withTimeout block 406 } 407 resource.close() // Release the resource 408 } 409 } 410 } 411 // Outside of runBlocking all coroutines have completed 412 println(acquired) // Print the number of resources still acquired 413} 414//sampleEnd 415``` 416{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} 417 418> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt). 419> 420{type="note"} 421 422<!--- CLEAR --> 423 424If you run the above code, you'll see that it does not always print zero, though it may depend on the timings 425of your machine. You may need to tweak the timeout in this example to actually see non-zero values. 426 427> Note that incrementing and decrementing `acquired` counter here from 10K coroutines is completely thread-safe, 428> since it always happens from the same thread, the one used by `runBlocking`. 429> More on that will be explained in the chapter on coroutine context. 430> 431{type="note"} 432 433To work around this problem you can store a reference to the resource in a variable instead of returning it 434from the `withTimeout` block. 435 436```kotlin 437import kotlinx.coroutines.* 438 439var acquired = 0 440 441class Resource { 442 init { acquired++ } // Acquire the resource 443 fun close() { acquired-- } // Release the resource 444} 445 446fun main() { 447//sampleStart 448 runBlocking { 449 repeat(10_000) { // Launch 10K coroutines 450 launch { 451 var resource: Resource? = null // Not acquired yet 452 try { 453 withTimeout(60) { // Timeout of 60 ms 454 delay(50) // Delay for 50 ms 455 resource = Resource() // Store a resource to the variable if acquired 456 } 457 // We can do something else with the resource here 458 } finally { 459 resource?.close() // Release the resource if it was acquired 460 } 461 } 462 } 463 } 464 // Outside of runBlocking all coroutines have completed 465 println(acquired) // Print the number of resources still acquired 466//sampleEnd 467} 468``` 469{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} 470 471> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-10.kt). 472> 473{type="note"} 474 475This example always prints zero. Resources do not leak. 476 477<!--- TEST 4780 479--> 480 481<!--- MODULE kotlinx-coroutines-core --> 482<!--- INDEX kotlinx.coroutines --> 483 484[launch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html 485[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html 486[cancelAndJoin]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel-and-join.html 487[Job.cancel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html 488[Job.join]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html 489[CancellationException]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html 490[yield]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html 491[isActive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/is-active.html 492[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html 493[withContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html 494[NonCancellable]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-non-cancellable/index.html 495[withTimeout]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout.html 496[withTimeoutOrNull]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout-or-null.html 497 498<!--- END --> 499