blob: 44e60fd056e70bd6c9e0720ea624b4c84eac7a27 [file] [log] [blame]
Roman Elizarovf16fd272017-02-07 11:26:00 +03001/*
2 * Copyright 2016-2017 JetBrains s.r.o.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Roman Elizarov3754f952017-01-18 20:47:54 +030017package kotlinx.coroutines.experimental
18
Roman Elizarovaa461cf2018-04-11 13:20:29 +030019import kotlin.coroutines.experimental.*
20
21internal expect fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable)
Roman Elizarov3754f952017-01-18 20:47:54 +030022
Roman Elizarov3754f952017-01-18 20:47:54 +030023/**
24 * Helper function for coroutine builder implementations to handle uncaught exception in coroutines.
Roman Elizarov8b38fa22017-09-27 17:44:31 +030025 *
Roman Elizarov3754f952017-01-18 20:47:54 +030026 * It tries to handle uncaught exception in the following way:
27 * * If there is [CoroutineExceptionHandler] in the context, then it is used.
Roman Elizarov58a7add2017-01-20 12:19:52 +030028 * * Otherwise, if exception is [CancellationException] then it is ignored
29 * (because that is the supposed mechanism to cancel the running coroutine)
Roman Elizarov8b38fa22017-09-27 17:44:31 +030030 * * Otherwise:
31 * * if there is a [Job] in the context, then [Job.cancel] is invoked;
Roman Elizarovd166eb62017-11-06 00:27:49 +030032 * * all instances of [CoroutineExceptionHandler] found via [ServiceLoader] are invoked;
33 * * current thread's [Thread.uncaughtExceptionHandler] is invoked.
Roman Elizarov3754f952017-01-18 20:47:54 +030034 */
Roman Elizarovaa461cf2018-04-11 13:20:29 +030035public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
Roman Elizarovc0563cf2018-02-01 17:56:47 +010036 // if exception handling fails, make sure the original exception is not lost
37 try {
38 context[CoroutineExceptionHandler]?.let {
39 it.handleException(context, exception)
40 return
41 }
42 // ignore CancellationException (they are normal means to terminate a coroutine)
43 if (exception is CancellationException) return
44 // try cancel job in the context
45 context[Job]?.cancel(exception)
Roman Elizarovaa461cf2018-04-11 13:20:29 +030046 // platform-specific
47 handleCoroutineExceptionImpl(context, exception)
Roman Elizarovc0563cf2018-02-01 17:56:47 +010048 } catch (handlerException: Throwable) {
49 // simply rethrow if handler threw the original exception
50 if (handlerException === exception) throw exception
51 // handler itself crashed for some other reason -- that is bad -- keep both
52 throw RuntimeException("Exception while trying to handle coroutine exception", exception).apply {
Roman Elizarovaa461cf2018-04-11 13:20:29 +030053 addSuppressedThrowable(handlerException)
Roman Elizarovc0563cf2018-02-01 17:56:47 +010054 }
Roman Elizarov3754f952017-01-18 20:47:54 +030055 }
Roman Elizarov3754f952017-01-18 20:47:54 +030056}
57
58/**
Roman Elizarovaa461cf2018-04-11 13:20:29 +030059 * Creates new [CoroutineExceptionHandler] instance.
60 * @param handler a function which handles exception thrown by a coroutine
61 */
62@Suppress("FunctionName")
63public inline fun CoroutineExceptionHandler(crossinline handler: (CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler =
64 object: AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler {
65 override fun handleException(context: CoroutineContext, exception: Throwable) =
66 handler.invoke(context, exception)
67 }
68
69/**
Roman Elizarov20f184a2017-03-17 09:52:19 +030070 * An optional element on the coroutine context to handle uncaught exceptions.
Roman Elizarov8b38fa22017-09-27 17:44:31 +030071 *
72 * By default, when no handler is installed, uncaught exception are handled in the following way:
73 * * If exception is [CancellationException] then it is ignored
74 * (because that is the supposed mechanism to cancel the running coroutine)
75 * * Otherwise:
76 * * if there is a [Job] in the context, then [Job.cancel] is invoked;
Roman Elizarove1c0b652017-12-01 14:02:57 +030077 * * all instances of [CoroutineExceptionHandler] found via [ServiceLoader] are invoked;
Roman Elizarov8b38fa22017-09-27 17:44:31 +030078 * * and current thread's [Thread.uncaughtExceptionHandler] is invoked.
79 *
Roman Elizarov3754f952017-01-18 20:47:54 +030080 * See [handleCoroutineException].
81 */
Roman Elizarovaa461cf2018-04-11 13:20:29 +030082public interface CoroutineExceptionHandler : CoroutineContext.Element {
Roman Elizarovf138bbc2017-02-09 19:13:08 +030083 /**
84 * Key for [CoroutineExceptionHandler] instance in the coroutine context.
85 */
Roman Elizarovaa461cf2018-04-11 13:20:29 +030086 public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>
Roman Elizarovf138bbc2017-02-09 19:13:08 +030087
88 /**
89 * Handles uncaught [exception] in the given [context]. It is invoked
Roman Elizarov20f184a2017-03-17 09:52:19 +030090 * if coroutine has an uncaught exception. See [handleCoroutineException].
Roman Elizarovf138bbc2017-02-09 19:13:08 +030091 */
Roman Elizarovaa461cf2018-04-11 13:20:29 +030092 public fun handleException(context: CoroutineContext, exception: Throwable)
Roman Elizarov3754f952017-01-18 20:47:54 +030093}