xref: /aosp_15_r20/external/kotlinx.coroutines/docs/topics/cancellation-and-timeouts.md (revision 7a7160fed73afa6648ef8aa100d4a336fe921d9a)
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