1 /*
2 * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 */
4
5 package kotlinx.coroutines
6
7 import kotlin.coroutines.*
8
handleCoroutineExceptionImplnull9 internal expect fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable)
10
11 /**
12 * Helper function for coroutine builder implementations to handle uncaught and unexpected exceptions in coroutines,
13 * that could not be otherwise handled in a normal way through structured concurrency, saving them to a future, and
14 * cannot be rethrown. This is a last resort handler to prevent lost exceptions.
15 *
16 * If there is [CoroutineExceptionHandler] in the context, then it is used. If it throws an exception during handling
17 * or is absent, all instances of [CoroutineExceptionHandler] found via [ServiceLoader] and
18 * [Thread.uncaughtExceptionHandler] are invoked.
19 */
20 @InternalCoroutinesApi
21 public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
22 // Invoke an exception handler from the context if present
23 try {
24 context[CoroutineExceptionHandler]?.let {
25 it.handleException(context, exception)
26 return
27 }
28 } catch (t: Throwable) {
29 handleCoroutineExceptionImpl(context, handlerException(exception, t))
30 return
31 }
32 // If a handler is not present in the context or an exception was thrown, fallback to the global handler
33 handleCoroutineExceptionImpl(context, exception)
34 }
35
handlerExceptionnull36 internal fun handlerException(originalException: Throwable, thrownException: Throwable): Throwable {
37 if (originalException === thrownException) return originalException
38 return RuntimeException("Exception while trying to handle coroutine exception", thrownException).apply {
39 addSuppressedThrowable(originalException)
40 }
41 }
42
43 /**
44 * Creates a [CoroutineExceptionHandler] instance.
45 * @param handler a function which handles exception thrown by a coroutine
46 */
47 @Suppress("FunctionName")
CoroutineExceptionHandlernull48 public inline fun CoroutineExceptionHandler(crossinline handler: (CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler =
49 object : AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler {
50 override fun handleException(context: CoroutineContext, exception: Throwable) =
51 handler.invoke(context, exception)
52 }
53
54 /**
55 * An optional element in the coroutine context to handle **uncaught** exceptions.
56 *
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.
61 * Coroutines running with [SupervisorJob] do not propagate exceptions to their parent and are treated like root coroutines.
62 * A coroutine that was created using [async][CoroutineScope.async] always catches all its exceptions and represents them
63 * in the resulting [Deferred] object, so it cannot result in uncaught exceptions.
64 *
65 * ### Handling coroutine exceptions
66 *
67 * `CoroutineExceptionHandler` is a last-resort mechanism for global "catch all" behavior.
68 * You cannot recover from the exception in the `CoroutineExceptionHandler`. The coroutine had already completed
69 * with the corresponding exception when the handler is called. Normally, the handler is used to
70 * log the exception, show some kind of error message, terminate, and/or restart the application.
71 *
72 * If you need to handle exception in a specific part of the code, it is recommended to use `try`/`catch` around
73 * the corresponding code inside your coroutine. This way you can prevent completion of the coroutine
74 * with the exception (exception is now _caught_), retry the operation, and/or take other arbitrary actions:
75 *
76 * ```
77 * scope.launch { // launch child coroutine in a scope
78 * try {
79 * // do something
80 * } catch (e: Throwable) {
81 * // handle exception
82 * }
83 * }
84 * ```
85 *
86 * ### Implementation details
87 *
88 * By default, when no handler is installed, uncaught exception are handled in the following way:
89 * * If exception is [CancellationException] then it is ignored
90 * (because that is the supposed mechanism to cancel the running coroutine)
91 * * Otherwise:
92 * * if there is a [Job] in the context, then [Job.cancel] is invoked;
93 * * Otherwise, all instances of [CoroutineExceptionHandler] found via [ServiceLoader]
94 * * and current thread's [Thread.uncaughtExceptionHandler] are invoked.
95 *
96 * [CoroutineExceptionHandler] can be invoked from an arbitrary thread.
97 */
98 public interface CoroutineExceptionHandler : CoroutineContext.Element {
99 /**
100 * Key for [CoroutineExceptionHandler] instance in the coroutine context.
101 */
102 public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>
103
104 /**
105 * Handles uncaught [exception] in the given [context]. It is invoked
106 * if coroutine has an uncaught exception.
107 */
handleExceptionnull108 public fun handleException(context: CoroutineContext, exception: Throwable)
109 }
110