diff --git a/.gitignore b/.gitignore index 9d766410e18..1d2d1b0e7ff 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ publishing.sh local.* local/ + +.kotlin/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 3db6aa60047..c2f1f156844 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.21.0 + +**THIS UPDATE CONTAINS BREAKING CHANGES IN `safely*`-ORIENTED FUNCTIONS** + +* `Coroutines`: + * **All `safely` functions lost their `supervisorScope` in favor to wrapping `runCatching`** + * `runCatchingSafely` is the main handling function of all `safely` functions + * `launchSafely*` and `asyncSafely*` blocks lost `CoroutineScope` as their receiver + ## 0.20.52 * `Coroutines`: diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/ContextSafelyExceptionHandler.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/ContextSafelyExceptionHandler.kt new file mode 100644 index 00000000000..140e59ae9e1 --- /dev/null +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/ContextSafelyExceptionHandler.kt @@ -0,0 +1,44 @@ +package dev.inmo.micro_utils.coroutines + +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.coroutineContext + +typealias ExceptionHandler = suspend (Throwable) -> T + +/** + * This instance will be used in all calls of [safely] where exception handler has not been passed + */ +var defaultSafelyExceptionHandler: ExceptionHandler = { throw it } + +/** + * This instance will be used in all calls of [safelyWithoutExceptions] as an exception handler for [safely] call + */ +var defaultSafelyWithoutExceptionHandler: ExceptionHandler = { + try { + defaultSafelyExceptionHandler(it) + } catch (e: Throwable) { + // do nothing + } +} + +/** + * 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 +} \ No newline at end of file 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 3b1a93e8f0a..ce84e2c71fe 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 @@ -4,46 +4,58 @@ import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext -typealias ExceptionHandler = suspend (Throwable) -> T - /** - * This instance will be used in all calls of [safely] where exception handler has not been passed + * Launching [block] in [runCatching]. In case of failure, it will: + * + * * Try to get [ContextSafelyExceptionHandler] from current [coroutineContext] and call its + * [ContextSafelyExceptionHandler.handler] invoke. **Thrown exception from its handler + * will pass out of [runCatchingSafely]** + * * Execute [onException] inside of new [runCatching] and return its result. Throws exception + * will be caught by [runCatching] and wrapped in [Result] + * + * @return [Result] with result of [block] if no exceptions or [Result] from [onException] execution */ -var defaultSafelyExceptionHandler: ExceptionHandler = { throw it } - -/** - * This instance will be used in all calls of [safelyWithoutExceptions] as an exception handler for [safely] call - */ -var defaultSafelyWithoutExceptionHandler: ExceptionHandler = { - try { - defaultSafelyExceptionHandler(it) - } catch (e: Throwable) { - // do nothing +suspend inline fun runCatchingSafely( + onException: ExceptionHandler, + block: suspend () -> T +): Result { + return runCatching { + block() + }.onFailure { + coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(it) + return runCatching { + onException(it) + } } } -/** - * 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 +suspend inline fun R.runCatchingSafely( + onException: ExceptionHandler, + block: suspend R.() -> T +): Result = runCatchingSafely(onException) { + block() +} /** - * [ExceptionHandler] wrapper which was created to make possible to use [handler] across all coroutines calls - * - * @see safelyWithContextExceptionHandler - * @see ContextSafelyExceptionHandlerKey + * Launching [runCatchingSafely] with [defaultSafelyExceptionHandler] as `onException` parameter */ -class ContextSafelyExceptionHandler( - val handler: ExceptionHandler -) : CoroutineContext.Element { - override val key: CoroutineContext.Key<*> - get() = ContextSafelyExceptionHandlerKey +suspend inline fun runCatchingSafely( + block: suspend () -> T +): Result = runCatchingSafely(defaultSafelyExceptionHandler, block) + +suspend inline fun R.runCatchingSafely( + block: suspend R.() -> T +): Result = runCatchingSafely { + block() } +//suspend inline fun T.runCatchingSafely( +// onException: ExceptionHandler, +// block: suspend T.() -> R +//): Result = runCatchingSafely(onException) { +// block() +//} + /** * @return [ContextSafelyExceptionHandler] from [coroutineContext] by key [ContextSafelyExceptionHandlerKey] if * exists @@ -64,7 +76,7 @@ suspend fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExce suspend fun safelyWithContextExceptionHandler( contextExceptionHandler: ExceptionHandler, safelyExceptionHandler: ExceptionHandler = defaultSafelyExceptionHandler, - block: suspend CoroutineScope.() -> T + block: suspend () -> T ): T { val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler -> ContextSafelyExceptionHandler { @@ -78,57 +90,35 @@ suspend fun safelyWithContextExceptionHandler( } /** - * It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions + * Calls [runCatchingSafely] and getting the result via [Result.getOrThrow] * - * Priorities of [ExceptionHandler]s: - * - * * [onException] In case if custom (will be used anyway if not [defaultSafelyExceptionHandler]) - * * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key - * * [defaultSafelyExceptionHandler] - * - * Remember, that [ExceptionHandler] from [CoroutineContext.get] will be used anyway if it is available. After it will - * be called [onException] - * - * @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this - * exception will be available for catching - * - * @see defaultSafelyExceptionHandler - * @see safelyWithoutExceptions - * @see safelyWithContextExceptionHandler + * @see runCatchingSafely */ -suspend fun safely( - onException: ExceptionHandler = defaultSafelyExceptionHandler, - block: suspend CoroutineScope.() -> T -): T { - return try { - supervisorScope(block) - } catch (e: Throwable) { - coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(e) - onException(e) - } -} +suspend inline fun safely( + onException: ExceptionHandler, + block: suspend () -> T +): T = runCatchingSafely(onException, block).getOrThrow() -suspend fun runCatchingSafely( - onException: ExceptionHandler = defaultSafelyExceptionHandler, - block: suspend CoroutineScope.() -> T -): Result = runCatching { - safely(onException, block) -} - -suspend fun T.runCatchingSafely( - onException: ExceptionHandler = defaultSafelyExceptionHandler, - block: suspend T.() -> R -): Result = runCatching { - safely(onException) { block() } -} +/** + * Calls [safely] with passing of [defaultSafelyExceptionHandler] as `onException` + * + * @see runCatchingSafely + */ +suspend inline fun safely( + block: suspend () -> T +): T = safely(defaultSafelyExceptionHandler, block) +suspend inline fun R.safely( + block: suspend R.() -> T +): T = safely { block() } +@Deprecated("Renamed", ReplaceWith("runCatchingSafely(block)", "dev.inmo.micro_utils.coroutines.runCatchingSafely")) suspend fun safelyWithResult( - block: suspend CoroutineScope.() -> T + block: suspend () -> T ): Result = runCatchingSafely(defaultSafelyExceptionHandler, block) - -suspend fun T.safelyWithResult( - block: suspend T.() -> R -): Result = runCatchingSafely(defaultSafelyExceptionHandler, block) +@Deprecated("Renamed", ReplaceWith("this.runCatchingSafely(block)", "dev.inmo.micro_utils.coroutines.runCatchingSafely")) +suspend fun R.safelyWithResult( + block: suspend R.() -> T +): Result = safelyWithResult { block() } /** * Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and @@ -148,18 +138,20 @@ val defaultSafelyWithoutExceptionHandlerWithNull: ExceptionHandler = { * result from exception (instead of throwing it, by default always returns null) */ suspend fun safelyWithoutExceptions( - onException: ExceptionHandler = defaultSafelyWithoutExceptionHandlerWithNull, - block: suspend CoroutineScope.() -> T -): T? = safely(onException, block) + onException: ExceptionHandler = defaultSafelyExceptionHandler, + block: suspend () -> T +): T? = runCatchingSafely(onException, block).getOrNull() suspend fun runCatchingSafelyWithoutExceptions( - onException: ExceptionHandler = defaultSafelyWithoutExceptionHandlerWithNull, - block: suspend CoroutineScope.() -> T -): Result = runCatching { - safelyWithoutExceptions(onException, block) + onException: ExceptionHandler = defaultSafelyExceptionHandler, + block: suspend () -> T +): Result = runCatchingSafely(onException, block).let { + if (it.isFailure) return Result.success(null) + + it } -fun CoroutineScope( +fun CoroutineScopeWithDefaultFallback( context: CoroutineContext, defaultExceptionsHandler: ExceptionHandler ) = CoroutineScope( diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/LaunchSafely.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/LaunchSafely.kt index ee3cd890c44..13583d2c622 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/LaunchSafely.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/LaunchSafely.kt @@ -10,7 +10,9 @@ fun CoroutineScope.launchSafely( onException: ExceptionHandler = defaultSafelyExceptionHandler, block: suspend CoroutineScope.() -> Unit ) = launch(context, start) { - safely(onException, block) + runCatchingSafely(onException) { + block() + } } fun CoroutineScope.launchSafelyWithoutExceptions( @@ -19,7 +21,9 @@ fun CoroutineScope.launchSafelyWithoutExceptions( onException: ExceptionHandler = defaultSafelyWithoutExceptionHandlerWithNull, block: suspend CoroutineScope.() -> Unit ) = launch(context, start) { - safelyWithoutExceptions(onException, block) + runCatchingSafelyWithoutExceptions(onException) { + block() + } } fun CoroutineScope.asyncSafely( @@ -28,7 +32,9 @@ fun CoroutineScope.asyncSafely( onException: ExceptionHandler = defaultSafelyExceptionHandler, block: suspend CoroutineScope.() -> T ) = async(context, start) { - safely(onException, block) + runCatchingSafely(onException) { + block() + } } fun CoroutineScope.asyncSafelyWithoutExceptions( @@ -37,5 +43,7 @@ fun CoroutineScope.asyncSafelyWithoutExceptions( onException: ExceptionHandler = defaultSafelyWithoutExceptionHandlerWithNull, block: suspend CoroutineScope.() -> T ) = async(context, start) { - safelyWithoutExceptions(onException, block) + runCatchingSafelyWithoutExceptions(onException) { + block() + } } diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SupervisorJob.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SupervisorJob.kt index df4131ddecd..8c4c3d010dc 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SupervisorJob.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SupervisorJob.kt @@ -10,8 +10,12 @@ fun CoroutineScope.LinkedSupervisorJob( additionalContext: CoroutineContext? = null ) = coroutineContext.LinkedSupervisorJob(additionalContext) -fun CoroutineScope.LinkedSupervisorScope( + +fun CoroutineContext.LinkedSupervisorScope( additionalContext: CoroutineContext? = null ) = CoroutineScope( - coroutineContext + LinkedSupervisorJob(additionalContext) + this + LinkedSupervisorJob(additionalContext) ) +fun CoroutineScope.LinkedSupervisorScope( + additionalContext: CoroutineContext? = null +) = coroutineContext.LinkedSupervisorScope(additionalContext) diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesMachine.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesMachine.kt index 2c4ffe1c5d7..5a2751d6db0 100644 --- a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesMachine.kt +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesMachine.kt @@ -1,7 +1,6 @@ package dev.inmo.micro_utils.fsm.common import dev.inmo.micro_utils.common.Optional -import dev.inmo.micro_utils.common.onPresented import dev.inmo.micro_utils.coroutines.* import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler @@ -118,23 +117,28 @@ open class DefaultStatesMachine ( * [launchStateHandling] will returns some [State] then [statesManager] [StatesManager.update] will be used, otherwise * [StatesManager.endChain]. */ - override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { - (statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(this) { - launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) } - } - statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) { - launch { performStateUpdate(Optional.presented(it.first), it.second, scope.LinkedSupervisorScope()) } - } - statesManager.onEndChain.subscribeSafelyWithoutExceptions(this) { removedState -> - launch { - statesJobsMutex.withLock { - val stateInMap = statesJobs.keys.firstOrNull { stateInMap -> stateInMap == removedState } - if (stateInMap === removedState) { - statesJobs[stateInMap] ?.cancel() + override fun start(scope: CoroutineScope): Job { + val supervisorScope = scope.LinkedSupervisorScope() + supervisorScope.launchSafelyWithoutExceptions { + (statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(supervisorScope) { + supervisorScope.launch { performStateUpdate(Optional.absent(), it, supervisorScope) } + } + statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(supervisorScope) { + supervisorScope.launch { performStateUpdate(Optional.presented(it.first), it.second, supervisorScope) } + } + statesManager.onEndChain.subscribeSafelyWithoutExceptions(supervisorScope) { removedState -> + supervisorScope.launch { + statesJobsMutex.withLock { + val stateInMap = statesJobs.keys.firstOrNull { stateInMap -> stateInMap == removedState } + if (stateInMap === removedState) { + statesJobs[stateInMap] ?.cancel() + } } } } } + + return supervisorScope.coroutineContext.job } /** diff --git a/gradle.properties b/gradle.properties index c2138181643..ef1b439b102 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,5 +15,5 @@ crypto_js_version=4.1.1 # Project data group=dev.inmo -version=0.20.52 -android_code_version=258 +version=0.21.0 +android_code_version=259 diff --git a/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/server/TemporalFilesRoutingConfigurator.kt b/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/server/TemporalFilesRoutingConfigurator.kt index a017e87a7a2..dd45d06741a 100644 --- a/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/server/TemporalFilesRoutingConfigurator.kt +++ b/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/server/TemporalFilesRoutingConfigurator.kt @@ -41,7 +41,7 @@ class TemporalFilesRoutingConfigurator( filesMutex: Mutex, onNewFileFlow: Flow ): Job = scope.launchSafelyWithoutExceptions { - while (isActive) { + while (currentCoroutineContext().isActive) { val filesWithCreationInfo = filesMap.mapNotNull { (fileId, file) -> fileId to ((Files.getAttribute(file.toPath(), "creationTime") as? FileTime) ?.toMillis() ?: return@mapNotNull null) }