Skip to content

Commit 90a13a1

Browse files
committed
Improve docs for CoroutineExceptionHandler
Fixes #1746
1 parent 96b41b4 commit 90a13a1

File tree

3 files changed

+40
-12
lines changed

3 files changed

+40
-12
lines changed

docs/exception-handling.md

+14-7
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,10 @@ Caught ArithmeticException
7878

7979
### CoroutineExceptionHandler
8080

81-
But what if one does not want to print all exceptions to the console?
82-
[CoroutineExceptionHandler] context element is used as generic `catch` block of coroutine where custom logging or exception handling may take place.
83-
It is similar to using [`Thread.uncaughtExceptionHandler`](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler)).
81+
What if one does not want to print all exceptions to the console?
82+
[CoroutineExceptionHandler] context element on a _root_ coroutine can be used as generic `catch` block for
83+
this root coroutine and all its children where custom logging or exception handling may take place.
84+
It is similar to [`Thread.uncaughtExceptionHandler`](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler)).
8485

8586
On JVM it is possible to redefine global exception handler for all coroutines by registering [CoroutineExceptionHandler] via
8687
[`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
@@ -89,8 +90,12 @@ Global exception handler is similar to
8990
which is used when no more specific handlers are registered.
9091
On Android, `uncaughtExceptionPreHandler` is installed as a global coroutine exception handler.
9192

92-
[CoroutineExceptionHandler] is invoked only on exceptions which are not expected to be handled by the user,
93-
so registering it in [async] builder and the like of it has no effect.
93+
`CoroutineExceptionHandler` is invoked only on **uncaught** exceptions — exceptions that were not handled in any other way.
94+
In particular, all _children_ coroutines (coroutines created in the context of another [Job]) delegate handling of
95+
their exceptions to their parent coroutine, which also delegates to the parent, and so on until the root,
96+
so the `CoroutineExceptionHandler` installed in their context is never used.
97+
In addition to that, [async] builder always catches all exceptions and represents them in the resulting [Deferred] object,
98+
so its `CoroutineExceptionHandler` has no effect either.
9499

95100
<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
96101

@@ -102,10 +107,10 @@ fun main() = runBlocking {
102107
val handler = CoroutineExceptionHandler { _, exception ->
103108
println("Caught $exception")
104109
}
105-
val job = GlobalScope.launch(handler) {
110+
val job = GlobalScope.launch(handler) { // root coroutine, running in GlobalScope
106111
throw AssertionError()
107112
}
108-
val deferred = GlobalScope.async(handler) {
113+
val deferred = GlobalScope.async(handler) { // also root, but async instead of launch
109114
throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await()
110115
}
111116
joinAll(job, deferred)
@@ -501,6 +506,8 @@ Scope is completed
501506
[Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
502507
[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
503508
[CoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
509+
[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
510+
[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
504511
[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/cancel.html
505512
[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
506513
[SupervisorJob()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-supervisor-job.html

kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt

+24-3
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,32 @@ public inline fun CoroutineExceptionHandler(crossinline handler: (CoroutineConte
5252
}
5353

5454
/**
55-
* An optional element in the coroutine context to handle uncaught exceptions.
55+
* An optional element in the coroutine context to handle **uncaught** exceptions.
5656
*
57-
* Normally, uncaught exceptions can only result from coroutines created using the [launch][CoroutineScope.launch] builder.
57+
* Normally, uncaught exceptions can only result from _root_ coroutines created using the [launch][CoroutineScope.launch] builder.
58+
* All _children_ coroutines (coroutines created in the context of another [Job]) delegate handling of their
59+
* exceptions to their parent coroutine, which also delegates to the parent, and so on until the root,
60+
* so the `CoroutineExceptionHandler` installed in their context is never used.
5861
* A coroutine that was created using [async][CoroutineScope.async] always catches all its exceptions and represents them
59-
* in the resulting [Deferred] object.
62+
* in the resulting [Deferred] object, so it cannot result in uncaught exceptions either.
63+
*
64+
* ### Handling coroutine exceptions
65+
*
66+
* `CoroutineExceptionHandler` is a last-resort mechanism for global "catch all" behavior.
67+
* If you need to handle exception in a specific part of the code, it is recommended to use `try`/`catch` around
68+
* the corresponding code inside your coroutine, like this:
69+
*
70+
* ```
71+
* scope.launch { // launch child coroutine in a scope
72+
* try {
73+
* // do something
74+
* } catch (e: Throwable) {
75+
* // handle exception
76+
* }
77+
* }
78+
* ```
79+
*
80+
* ### Implementation details
6081
*
6182
* By default, when no handler is installed, uncaught exception are handled in the following way:
6283
* * If exception is [CancellationException] then it is ignored

kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ fun main() = runBlocking {
1111
val handler = CoroutineExceptionHandler { _, exception ->
1212
println("Caught $exception")
1313
}
14-
val job = GlobalScope.launch(handler) {
14+
val job = GlobalScope.launch(handler) { // root coroutine, running in GlobalScope
1515
throw AssertionError()
1616
}
17-
val deferred = GlobalScope.async(handler) {
17+
val deferred = GlobalScope.async(handler) { // also root, but async instead of launch
1818
throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await()
1919
}
2020
joinAll(job, deferred)

0 commit comments

Comments
 (0)