Improve docs for CoroutineExceptionHandler (#1886)

* Further clarifications and better style for exception handling
* Consistent terminology on "uncaught exceptions".
* Clarified special relations of exception handling with supervision.
* Clearer text in CoroutineExceptionHandler examples.
* Minor stylistic corrections.

Fixes #1746
Fixes #871

Co-Authored-By: EdwarDDay <4127904+EdwarDDay@users.noreply.github.com>
diff --git a/docs/exception-handling.md b/docs/exception-handling.md
index 08e63ea..9029e4b 100644
--- a/docs/exception-handling.md
+++ b/docs/exception-handling.md
@@ -18,22 +18,22 @@
 
 ## Exception Handling
 
-
 This section covers exception handling and cancellation on exceptions.
-We already know that cancelled coroutine throws [CancellationException] in suspension points and that it 
-is ignored by coroutines machinery. But what happens if an exception is thrown during cancellation or multiple children of the same 
-coroutine throw an exception?
+We already know that cancelled coroutine throws [CancellationException] in suspension points and that it
+is ignored by the coroutines' machinery. Here we look at what happens if an exception is thrown during cancellation or multiple children of the same
+coroutine throw an exception.
 
 ### Exception propagation
 
-Coroutine builders come in two flavors: propagating exceptions automatically ([launch] and [actor]) or 
+Coroutine builders come in two flavors: propagating exceptions automatically ([launch] and [actor]) or
 exposing them to users ([async] and [produce]).
-The former treat exceptions as unhandled, similar to Java's `Thread.uncaughtExceptionHandler`, 
-while the latter are relying on the user to consume the final 
+When these builders are used to create a _root_ coroutine, that is not a _child_ of another coroutine,
+the former builder treat exceptions as **uncaught** exceptions, similar to Java's `Thread.uncaughtExceptionHandler`,
+while the latter are relying on the user to consume the final
 exception, for example via [await][Deferred.await] or [receive][ReceiveChannel.receive] 
 ([produce] and [receive][ReceiveChannel.receive] are covered later in [Channels](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/channels.md) section).
 
-It can be demonstrated by a simple example that creates coroutines in the [GlobalScope]:
+It can be demonstrated by a simple example that creates root coroutines using the [GlobalScope]:
 
 <div class="sample" markdown="1" theme="idea" data-highlight-only>
 
@@ -41,13 +41,13 @@
 import kotlinx.coroutines.*
 
 fun main() = runBlocking {
-    val job = GlobalScope.launch {
+    val job = GlobalScope.launch { // root coroutine with launch
         println("Throwing exception from launch")
         throw IndexOutOfBoundsException() // Will be printed to the console by Thread.defaultUncaughtExceptionHandler
     }
     job.join()
     println("Joined failed job")
-    val deferred = GlobalScope.async {
+    val deferred = GlobalScope.async { // root coroutine with async
         println("Throwing exception from async")
         throw ArithmeticException() // Nothing is printed, relying on user to call await
     }
@@ -78,9 +78,13 @@
 
 ### CoroutineExceptionHandler
 
-But what if one does not want to print all exceptions to the console?
-[CoroutineExceptionHandler] context element is used as generic `catch` block of coroutine where custom logging or exception handling may take place.
-It is similar to using [`Thread.uncaughtExceptionHandler`](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler)).
+It is possible to customize the default behavior of printing **uncaught** exceptions to the console.
+[CoroutineExceptionHandler] context element on a _root_ coroutine can be used as generic `catch` block for
+this root coroutine and all its children where custom exception handling may take place.
+It is similar to [`Thread.uncaughtExceptionHandler`](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler)).
+You cannot recover from the exception in the `CoroutineExceptionHandler`. The coroutine had already completed
+with the corresponding exception when the handler is called. Normally, the handler is used to
+log the exception, show some kind of error message, terminate, and/or restart the application.
 
 On JVM it is possible to redefine global exception handler for all coroutines by registering [CoroutineExceptionHandler] via
 [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
@@ -89,8 +93,15 @@
 which is used when no more specific handlers are registered.
 On Android, `uncaughtExceptionPreHandler` is installed as a global coroutine exception handler.
 
-[CoroutineExceptionHandler] is invoked only on exceptions which are not expected to be handled by the user, 
-so registering it in [async] builder and the like of it has no effect.
+`CoroutineExceptionHandler` is invoked only on **uncaught** exceptions &mdash; exceptions that were not handled in any other way.
+In particular, all _children_ coroutines (coroutines created in the context of another [Job]) delegate handling of
+their exceptions to their parent coroutine, which also delegates to the parent, and so on until the root,
+so the `CoroutineExceptionHandler` installed in their context is never used. 
+In addition to that, [async] builder always catches all exceptions and represents them in the resulting [Deferred] object, 
+so its `CoroutineExceptionHandler` has no effect either.
+
+> Coroutines running in supervision scope do not propagate exceptions to their parent and are
+excluded from this rule. A further [Supervision](#supervision) section of this document gives more details.  
 
 <div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
 
@@ -100,12 +111,12 @@
 fun main() = runBlocking {
 //sampleStart
     val handler = CoroutineExceptionHandler { _, exception -> 
-        println("Caught $exception") 
+        println("CoroutineExceptionHandler got $exception") 
     }
-    val job = GlobalScope.launch(handler) {
+    val job = GlobalScope.launch(handler) { // root coroutine, running in GlobalScope
         throw AssertionError()
     }
-    val deferred = GlobalScope.async(handler) {
+    val deferred = GlobalScope.async(handler) { // also root, but async instead of launch
         throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await()
     }
     joinAll(job, deferred)
@@ -120,14 +131,14 @@
 The output of this code is:
 
 ```text
-Caught java.lang.AssertionError
+CoroutineExceptionHandler got java.lang.AssertionError
 ```
 
 <!--- TEST-->
 
 ### Cancellation and exceptions
 
-Cancellation is tightly bound with exceptions. Coroutines internally use `CancellationException` for cancellation, these
+Cancellation is closely related to exceptions. Coroutines internally use `CancellationException` for cancellation, these
 exceptions are ignored by all handlers, so they should be used only as the source of additional debug information, which can
 be obtained by `catch` block.
 When a coroutine is cancelled using [Job.cancel], it terminates, but it does not cancel its parent.
@@ -175,15 +186,17 @@
 
 If a coroutine encounters an exception other than `CancellationException`, it cancels its parent with that exception. 
 This behaviour cannot be overridden and is used to provide stable coroutines hierarchies for
-[structured concurrency](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/composing-suspending-functions.md#structured-concurrency-with-async) which do not depend on 
-[CoroutineExceptionHandler] implementation.
-The original exception is handled by the parent when all its children terminate.
+[structured concurrency](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/composing-suspending-functions.md#structured-concurrency-with-async).
+[CoroutineExceptionHandler] implementation is not used for child coroutines.
 
-> This also a reason why, in these examples, [CoroutineExceptionHandler] is always installed to a coroutine
+> In these examples [CoroutineExceptionHandler] is always installed to a coroutine
 that is created in [GlobalScope]. It does not make sense to install an exception handler to a coroutine that
 is launched in the scope of the main [runBlocking], since the main coroutine is going to be always cancelled
 when its child completes with exception despite the installed handler. 
 
+The original exception is handled by the parent only when all its children terminate,
+which is demonstrated by the following example.
+
 <div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
 
 ```kotlin
@@ -192,7 +205,7 @@
 fun main() = runBlocking {
 //sampleStart
     val handler = CoroutineExceptionHandler { _, exception -> 
-        println("Caught $exception") 
+        println("CoroutineExceptionHandler got $exception") 
     }
     val job = GlobalScope.launch(handler) {
         launch { // the first child
@@ -227,22 +240,15 @@
 Second child throws an exception
 Children are cancelled, but exception is not handled until all children terminate
 The first child finished its non cancellable block
-Caught java.lang.ArithmeticException
+CoroutineExceptionHandler got java.lang.ArithmeticException
 ```
 <!--- TEST-->
 
 ### Exceptions aggregation
 
-What happens if multiple children of a coroutine throw an exception?
-The general rule is "the first exception wins", so the first thrown exception is exposed to the handler.
-But that may cause lost exceptions, for example if coroutine throws an exception in its `finally` block.
-So, additional exceptions are suppressed. 
-
-> One of the solutions would have been to report each exception separately, 
-but then [Deferred.await] should have had the same mechanism to avoid behavioural inconsistency and this 
-would cause implementation details of a coroutines (whether it had delegated parts of its work to its children or not)
-to leak to its exception handler.
-
+When multiple children of a coroutine fail with an exception the
+general rule is "the first exception wins", so the first exception gets handled.
+All additional exceptions that happen after the first one are attached to the first exception as suppressed ones. 
 
 <!--- INCLUDE
 import kotlinx.coroutines.exceptions.*
@@ -256,19 +262,19 @@
 
 fun main() = runBlocking {
     val handler = CoroutineExceptionHandler { _, exception ->
-        println("Caught $exception with suppressed ${exception.suppressed.contentToString()}")
+        println("CoroutineExceptionHandler got $exception with suppressed ${exception.suppressed.contentToString()}")
     }
     val job = GlobalScope.launch(handler) {
         launch {
             try {
-                delay(Long.MAX_VALUE)
+                delay(Long.MAX_VALUE) // it gets cancelled when another sibling fails with IOException
             } finally {
-                throw ArithmeticException()
+                throw ArithmeticException() // the second exception
             }
         }
         launch {
             delay(100)
-            throw IOException()
+            throw IOException() // the first exception
         }
         delay(Long.MAX_VALUE)
     }
@@ -285,7 +291,7 @@
 The output of this code is:
 
 ```text
-Caught java.io.IOException with suppressed [java.lang.ArithmeticException]
+CoroutineExceptionHandler got java.io.IOException with suppressed [java.lang.ArithmeticException]
 ```
 
 <!--- TEST-->
@@ -293,7 +299,7 @@
 > Note, this mechanism currently works only on Java version 1.7+. 
 Limitation on JS and Native is temporary and will be fixed in the future.
 
-Cancellation exceptions are transparent and unwrapped by default:
+Cancellation exceptions are transparent and are unwrapped by default:
 
 <div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
 
@@ -304,13 +310,13 @@
 fun main() = runBlocking {
 //sampleStart
     val handler = CoroutineExceptionHandler { _, exception ->
-        println("Caught original $exception")
+        println("CoroutineExceptionHandler got $exception")
     }
     val job = GlobalScope.launch(handler) {
-        val inner = launch {
+        val inner = launch { // all this stack of coroutines will get cancelled
             launch {
                 launch {
-                    throw IOException()
+                    throw IOException() // the original exception
                 }
             }
         }
@@ -318,7 +324,7 @@
             inner.join()
         } catch (e: CancellationException) {
             println("Rethrowing CancellationException with original cause")
-            throw e
+            throw e // cancellation exception is rethrown, yet the original IOException gets to the handler  
         }
     }
     job.join()
@@ -334,25 +340,26 @@
 
 ```text
 Rethrowing CancellationException with original cause
-Caught original java.io.IOException
+CoroutineExceptionHandler got java.io.IOException
 ```
 <!--- TEST-->
 
 ### Supervision
 
 As we have studied before, cancellation is a bidirectional relationship propagating through the whole
-coroutines hierarchy. But what if unidirectional cancellation is required? 
+hierarchy of coroutines. Let us take a look at the case when unidirectional cancellation is required. 
 
 A good example of such a requirement is a UI component with the job defined in its scope. If any of the UI's child tasks
 have failed, it is not always necessary to cancel (effectively kill) the whole UI component,
-but if UI component is destroyed (and its job is cancelled), then it is necessary to fail all child jobs as their results are no longer required.
+but if UI component is destroyed (and its job is cancelled), then it is necessary to fail all child jobs as their results are no longer needed.
 
 Another example is a server process that spawns several children jobs and needs to _supervise_
 their execution, tracking their failures and restarting just those children jobs that had failed.
 
 #### Supervision job
 
-For these purposes [SupervisorJob][SupervisorJob()] can be used. It is similar to a regular [Job][Job()] with the only exception that cancellation is propagated
+For these purposes [SupervisorJob][SupervisorJob()] can be used. 
+It is similar to a regular [Job][Job()] with the only exception that cancellation is propagated
 only downwards. It is easy to demonstrate with an example:
 
 <div class="sample" markdown="1" theme="idea" data-highlight-only>
@@ -406,8 +413,8 @@
 
 #### Supervision scope
 
-For *scoped* concurrency [supervisorScope] can be used instead of [coroutineScope] for the same purpose. It propagates cancellation 
-only in one direction and cancels all children only if it has failed itself. It also waits for all children before completion
+For _scoped_ concurrency [supervisorScope] can be used instead of [coroutineScope] for the same purpose. It propagates cancellation
+in one direction only and cancels all children only if it has failed itself. It also waits for all children before completion
 just like [coroutineScope] does.
 
 <div class="sample" markdown="1" theme="idea" data-highlight-only>
@@ -455,8 +462,11 @@
 #### Exceptions in supervised coroutines
 
 Another crucial difference between regular and supervisor jobs is exception handling.
-Every child should handle its exceptions by itself via exception handling mechanisms.
+Every child should handle its exceptions by itself via exception handling mechanism.
 This difference comes from the fact that child's failure is not propagated to the parent.
+It means that coroutines launched directly inside [supervisorScope] _do_ use the [CoroutineExceptionHandler]
+that is installed in their scope in the same way as root coroutines do
+(see [CoroutineExceptionHandler](#coroutineexceptionhandler) section for details). 
 
 <div class="sample" markdown="1" theme="idea" data-highlight-only>
 
@@ -466,7 +476,7 @@
 
 fun main() = runBlocking {
     val handler = CoroutineExceptionHandler { _, exception -> 
-        println("Caught $exception") 
+        println("CoroutineExceptionHandler got $exception") 
     }
     supervisorScope {
         val child = launch(handler) {
@@ -488,7 +498,7 @@
 ```text
 Scope is completing
 Child throws an exception
-Caught java.lang.AssertionError
+CoroutineExceptionHandler got java.lang.AssertionError
 Scope is completed
 ```
 <!--- TEST-->
@@ -501,6 +511,8 @@
 [Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
 [GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
 [CoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
+[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
+[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
 [Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/cancel.html
 [runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
 [SupervisorJob()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-supervisor-job.html