Roman Elizarov | f16fd27 | 2017-02-07 11:26:00 +0300 | [diff] [blame] | 1 | /* |
| 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 Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 17 | package kotlinx.coroutines.experimental |
| 18 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 19 | import kotlin.coroutines.experimental.* |
| 20 | |
| 21 | internal expect fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 22 | |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 23 | /** |
| 24 | * Helper function for coroutine builder implementations to handle uncaught exception in coroutines. |
Roman Elizarov | 8b38fa2 | 2017-09-27 17:44:31 +0300 | [diff] [blame] | 25 | * |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 26 | * It tries to handle uncaught exception in the following way: |
| 27 | * * If there is [CoroutineExceptionHandler] in the context, then it is used. |
Roman Elizarov | 58a7add | 2017-01-20 12:19:52 +0300 | [diff] [blame] | 28 | * * Otherwise, if exception is [CancellationException] then it is ignored |
| 29 | * (because that is the supposed mechanism to cancel the running coroutine) |
Roman Elizarov | 8b38fa2 | 2017-09-27 17:44:31 +0300 | [diff] [blame] | 30 | * * Otherwise: |
| 31 | * * if there is a [Job] in the context, then [Job.cancel] is invoked; |
Roman Elizarov | d166eb6 | 2017-11-06 00:27:49 +0300 | [diff] [blame] | 32 | * * all instances of [CoroutineExceptionHandler] found via [ServiceLoader] are invoked; |
| 33 | * * current thread's [Thread.uncaughtExceptionHandler] is invoked. |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 34 | */ |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 35 | public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) { |
Roman Elizarov | c0563cf | 2018-02-01 17:56:47 +0100 | [diff] [blame] | 36 | // 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 Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 46 | // platform-specific |
| 47 | handleCoroutineExceptionImpl(context, exception) |
Roman Elizarov | c0563cf | 2018-02-01 17:56:47 +0100 | [diff] [blame] | 48 | } 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 Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 53 | addSuppressedThrowable(handlerException) |
Roman Elizarov | c0563cf | 2018-02-01 17:56:47 +0100 | [diff] [blame] | 54 | } |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 55 | } |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 56 | } |
| 57 | |
| 58 | /** |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 59 | * Creates new [CoroutineExceptionHandler] instance. |
| 60 | * @param handler a function which handles exception thrown by a coroutine |
| 61 | */ |
| 62 | @Suppress("FunctionName") |
| 63 | public 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 Elizarov | 20f184a | 2017-03-17 09:52:19 +0300 | [diff] [blame] | 70 | * An optional element on the coroutine context to handle uncaught exceptions. |
Roman Elizarov | 8b38fa2 | 2017-09-27 17:44:31 +0300 | [diff] [blame] | 71 | * |
| 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 Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 77 | * * all instances of [CoroutineExceptionHandler] found via [ServiceLoader] are invoked; |
Roman Elizarov | 8b38fa2 | 2017-09-27 17:44:31 +0300 | [diff] [blame] | 78 | * * and current thread's [Thread.uncaughtExceptionHandler] is invoked. |
| 79 | * |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 80 | * See [handleCoroutineException]. |
| 81 | */ |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 82 | public interface CoroutineExceptionHandler : CoroutineContext.Element { |
Roman Elizarov | f138bbc | 2017-02-09 19:13:08 +0300 | [diff] [blame] | 83 | /** |
| 84 | * Key for [CoroutineExceptionHandler] instance in the coroutine context. |
| 85 | */ |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 86 | public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler> |
Roman Elizarov | f138bbc | 2017-02-09 19:13:08 +0300 | [diff] [blame] | 87 | |
| 88 | /** |
| 89 | * Handles uncaught [exception] in the given [context]. It is invoked |
Roman Elizarov | 20f184a | 2017-03-17 09:52:19 +0300 | [diff] [blame] | 90 | * if coroutine has an uncaught exception. See [handleCoroutineException]. |
Roman Elizarov | f138bbc | 2017-02-09 19:13:08 +0300 | [diff] [blame] | 91 | */ |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 92 | public fun handleException(context: CoroutineContext, exception: Throwable) |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 93 | } |