diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d5148f046c..dda2413ccba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## 0.4.15 +* `Coroutines`: + * `safely`: + * `SafelyExceptionHandlerKey` has been deprecated + * `SafelyExceptionHandler` has been deprecated + * `ContextSafelyExceptionHandlerKey` has been added + * `ContextSafelyExceptionHandler` has been added + * `safelyWithContextExceptionHandler` has been added + ## 0.4.14 * `Versions`: diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/HandleSafely.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/HandleSafely.kt index 327a902cdcf..423d4ad76e5 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/HandleSafely.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/HandleSafely.kt @@ -1,7 +1,6 @@ package dev.inmo.micro_utils.coroutines -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.supervisorScope +import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext @@ -16,12 +15,14 @@ var defaultSafelyExceptionHandler: ExceptionHandler = { throw it } * Key for [SafelyExceptionHandler] which can be used in [CoroutineContext.get] to get current default * [SafelyExceptionHandler] */ +@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandlerKey", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler")) class SafelyExceptionHandlerKey : CoroutineContext.Key> /** * Shortcut for creating instance of [SafelyExceptionHandlerKey] */ @Suppress("NOTHING_TO_INLINE") +@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandlerKey", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler")) inline fun safelyExceptionHandlerKey() = SafelyExceptionHandlerKey() /** @@ -31,13 +32,68 @@ inline fun safelyExceptionHandlerKey() = SafelyExceptionHandlerKey() * @see SafelyExceptionHandlerKey * @see ExceptionHandler */ +@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandler", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler")) class SafelyExceptionHandler( val handler: ExceptionHandler ) : CoroutineContext.Element { - override val key: CoroutineContext.Key<*> = safelyExceptionHandlerKey() } +/** + * This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions + * and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key] + * + * @see safelyWithContextExceptionHandler + * @see ContextSafelyExceptionHandler + */ +object ContextSafelyExceptionHandlerKey : CoroutineContext.Key + +/** + * [ExceptionHandler] wrapper which was created to make possible to use [handler] across all coroutines calls + * + * @see safelyWithContextExceptionHandler + * @see ContextSafelyExceptionHandlerKey + */ +class ContextSafelyExceptionHandler( + val handler: ExceptionHandler +) : CoroutineContext.Element { + override val key: CoroutineContext.Key<*> + get() = ContextSafelyExceptionHandlerKey +} + +/** + * @return [ContextSafelyExceptionHandler] from [coroutineContext] by key [ContextSafelyExceptionHandlerKey] if + * exists + * + * @see ContextSafelyExceptionHandler + * @see ContextSafelyExceptionHandlerKey + */ +suspend inline fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey] + +/** + * This method will set new [coroutineContext] with [ContextSafelyExceptionHandler]. In case if [coroutineContext] + * already contains [ContextSafelyExceptionHandler], [ContextSafelyExceptionHandler.handler] will be used BEFORE + * [contextExceptionHandler] in case of exception. + * + * After all, will be called [withContext] method with created [ContextSafelyExceptionHandler] and block which will call + * [safely] method with [safelyExceptionHandler] as onException parameter and [block] as execution block + */ +suspend fun safelyWithContextExceptionHandler( + contextExceptionHandler: ExceptionHandler, + safelyExceptionHandler: ExceptionHandler = defaultSafelyExceptionHandler, + block: suspend CoroutineScope.() -> T +): T { + val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler -> + ContextSafelyExceptionHandler { + oldHandler(it) + contextExceptionHandler(it) + } + } ?: ContextSafelyExceptionHandler(contextExceptionHandler) + return withContext(contextSafelyExceptionHandler) { + safely(safelyExceptionHandler, block) + } +} + /** * It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions * @@ -52,8 +108,7 @@ class SafelyExceptionHandler( * * @see defaultSafelyExceptionHandler * @see safelyWithoutExceptions - * @see SafelyExceptionHandlerKey - * @see SafelyExceptionHandler + * @see safelyWithContextExceptionHandler */ suspend inline fun safely( noinline onException: ExceptionHandler = defaultSafelyExceptionHandler, @@ -62,12 +117,8 @@ suspend inline fun safely( return try { supervisorScope(block) } catch (e: Throwable) { - val handler = if (onException == defaultSafelyExceptionHandler) { - coroutineContext[safelyExceptionHandlerKey()] ?.handler ?: onException - } else { - onException - } - handler(e) + coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(e) + onException(e) } } diff --git a/coroutines/src/jvmTest/kotlin/dev/inmo/micro_utils/coroutines/HandleSafelyCoroutineContextTest.kt b/coroutines/src/jvmTest/kotlin/dev/inmo/micro_utils/coroutines/HandleSafelyCoroutineContextTest.kt new file mode 100644 index 00000000000..3c209b67357 --- /dev/null +++ b/coroutines/src/jvmTest/kotlin/dev/inmo/micro_utils/coroutines/HandleSafelyCoroutineContextTest.kt @@ -0,0 +1,38 @@ +package dev.inmo.micro_utils.coroutines + +import kotlinx.coroutines.* +import kotlin.test.Test + +class HandleSafelyCoroutineContextTest { + @Test + fun testHandleSafelyCoroutineContext() { + val scope = CoroutineScope(Dispatchers.Default) + var contextHandlerHappen = false + var localHandlerHappen = false + var defaultHandlerHappen = false + defaultSafelyExceptionHandler = { + defaultHandlerHappen = true + throw it + } + val contextHandler: ExceptionHandler = { + contextHandlerHappen = true + } + val checkJob = scope.launch { + safelyWithContextExceptionHandler(contextHandler) { + safely( + { + localHandlerHappen = true + } + ) { + error("That must happen :)") + } + println(coroutineContext) + error("That must happen too:)") + } + } + launchSynchronously { checkJob.join() } + assert(contextHandlerHappen) + assert(localHandlerHappen) + assert(defaultHandlerHappen) + } +} \ No newline at end of file