From fd41bf0ae7054d8c7023879f054a2bc747c92ab8 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Fri, 17 Jan 2025 14:41:09 +0600 Subject: [PATCH 1/3] start 0.24.4 --- CHANGELOG.md | 2 ++ gradle.properties | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c967da3c347..4cf67526731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 0.24.4 + ## 0.24.3 * `Ksp`: diff --git a/gradle.properties b/gradle.properties index c92d2e9170f..3f6772e9594 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,5 +15,5 @@ crypto_js_version=4.1.1 # Project data group=dev.inmo -version=0.24.3 -android_code_version=282 +version=0.24.4 +android_code_version=283 From ce717a4c9fce8f29c6b8f54a39c94fbe352cab26 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Fri, 17 Jan 2025 18:34:54 +0600 Subject: [PATCH 2/3] improvements in FSM --- fsm/common/build.gradle | 1 + .../micro_utils/fsm/common/StatesMachine.kt | 11 +++- .../micro_utils/fsm/common/StatesManager.kt | 7 ++- .../fsm/common/UpdatableStatesMachine.kt | 9 +++- .../common/managers/DefaultStatesManager.kt | 41 ++++++++++----- .../KeyValueBasedDefaultStatesManagerRepo.kt | 52 +++++++++++++++---- 6 files changed, 95 insertions(+), 26 deletions(-) diff --git a/fsm/common/build.gradle b/fsm/common/build.gradle index ee882e2b7b6..4aa30d4a72e 100644 --- a/fsm/common/build.gradle +++ b/fsm/common/build.gradle @@ -12,6 +12,7 @@ kotlin { dependencies { api project(":micro_utils.common") api project(":micro_utils.coroutines") + api libs.kslog } } } 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 5a2751d6db0..a395137f40e 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,5 +1,7 @@ package dev.inmo.micro_utils.fsm.common +import dev.inmo.kslog.common.TagLogger +import dev.inmo.kslog.common.e import dev.inmo.micro_utils.common.Optional import dev.inmo.micro_utils.coroutines.* import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler @@ -68,6 +70,7 @@ open class DefaultStatesMachine ( protected val handlers: List>, protected val onStateHandlingErrorHandler: StateHandlingErrorHandler = defaultStateHandlingErrorHandler() ) : StatesMachine { + protected val logger = TagLogger(this::class.simpleName!!) /** * Will call [launchStateHandling] for state handling */ @@ -96,7 +99,13 @@ open class DefaultStatesMachine ( statesJobsMutex.withLock { statesJobs[actualState] ?.cancel() statesJobs[actualState] = scope.launch { - performUpdate(actualState) + runCatching { + performUpdate(actualState) + }.onFailure { + logger.e(it) { + "Unable to perform update of state from $actualState" + } + }.getOrThrow() }.also { job -> job.invokeOnCompletion { _ -> scope.launch { diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesManager.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesManager.kt index 972106c6d83..fa4e4540702 100644 --- a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesManager.kt +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/StatesManager.kt @@ -9,7 +9,12 @@ interface StatesManager { /** - * Must set current set using [State.context] + * It is expected, that [new] state will be saved in manager. + * + * If [new] context will not be equal to [old] one, it must do some check of availability for replacement + * of potentially exists state on [new] context. If this state can't be replaced, it will throw [IllegalStateException] + * + * @throws IllegalStateException - in case when [new] [State] can't be set */ suspend fun update(old: T, new: T) diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/UpdatableStatesMachine.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/UpdatableStatesMachine.kt index 15892a3cdee..5dff8ed5be7 100644 --- a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/UpdatableStatesMachine.kt +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/UpdatableStatesMachine.kt @@ -1,5 +1,6 @@ package dev.inmo.micro_utils.fsm.common +import dev.inmo.kslog.common.e import dev.inmo.micro_utils.common.* import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler @@ -44,7 +45,13 @@ open class DefaultUpdatableStatesMachine( val job = previousState.mapOnPresented { statesJobs.remove(it) } ?.takeIf { it.isActive } ?: scope.launch { - performUpdate(actualState) + runCatching { + performUpdate(actualState) + }.onFailure { + logger.e(it) { + "Unable to perform update of state up to $actualState" + } + }.getOrThrow() }.also { job -> job.invokeOnCompletion { _ -> scope.launch { diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/DefaultStatesManager.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/DefaultStatesManager.kt index cb38a18948b..bb151d6ae2b 100644 --- a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/DefaultStatesManager.kt +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/DefaultStatesManager.kt @@ -1,10 +1,11 @@ package dev.inmo.micro_utils.fsm.common.managers +import dev.inmo.micro_utils.coroutines.SmartRWLocker +import dev.inmo.micro_utils.coroutines.withReadAcquire +import dev.inmo.micro_utils.coroutines.withWriteLock import dev.inmo.micro_utils.fsm.common.State import dev.inmo.micro_utils.fsm.common.StatesManager import kotlinx.coroutines.flow.* -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock /** * Implement this repo if you want to use some custom repo for [DefaultStatesManager] @@ -19,6 +20,14 @@ interface DefaultStatesManagerRepo { * NOT be removed */ suspend fun removeState(state: T) + + /** + * Semantically, calls [removeState] and then [set] + */ + suspend fun removeAndSet(toRemove: T, toSet: T) { + removeState(toRemove) + set(toSet) + } /** * @return Current list of available and saved states */ @@ -58,7 +67,7 @@ open class DefaultStatesManager( protected val _onEndChain = MutableSharedFlow(0) override val onEndChain: Flow = _onEndChain.asSharedFlow() - protected val mapMutex = Mutex() + protected val internalLocker = SmartRWLocker() constructor( repo: DefaultStatesManagerRepo, @@ -68,28 +77,30 @@ open class DefaultStatesManager( onUpdateContextsConflictResolver = onContextsConflictResolver ) - override suspend fun update(old: T, new: T) = mapMutex.withLock { + override suspend fun update(old: T, new: T) = internalLocker.withWriteLock { val stateByOldContext: T? = repo.getContextState(old.context) when { - stateByOldContext != old -> return@withLock - stateByOldContext == null || old.context == new.context -> { - repo.removeState(old) - repo.set(new) + stateByOldContext != old -> return@withWriteLock + old.context == new.context -> { + repo.removeAndSet(old, new) _onChainStateUpdated.emit(old to new) } - else -> { + old.context != new.context -> { val stateOnNewOneContext = repo.getContextState(new.context) if (stateOnNewOneContext == null || onUpdateContextsConflictResolver(old, new, stateOnNewOneContext)) { stateOnNewOneContext ?.let { endChainWithoutLock(it) } - repo.removeState(old) - repo.set(new) + repo.removeAndSet(old, new) _onChainStateUpdated.emit(old to new) + } else { + error( + "Unable to update state from $old to $new due to false answer from $onUpdateContextsConflictResolver and state on old context $stateOnNewOneContext" + ) } } } } - override suspend fun startChain(state: T) = mapMutex.withLock { + override suspend fun startChain(state: T) = internalLocker.withWriteLock { val stateOnContext = repo.getContextState(state.context) if (stateOnContext == null || onStartContextsConflictResolver(stateOnContext, state)) { stateOnContext ?.let { @@ -108,11 +119,13 @@ open class DefaultStatesManager( } override suspend fun endChain(state: T) { - mapMutex.withLock { + internalLocker.withWriteLock { endChainWithoutLock(state) } } - override suspend fun getActiveStates(): List = repo.getStates() + override suspend fun getActiveStates(): List = internalLocker.withReadAcquire { + repo.getStates() + } } diff --git a/fsm/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/repos/common/KeyValueBasedDefaultStatesManagerRepo.kt b/fsm/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/repos/common/KeyValueBasedDefaultStatesManagerRepo.kt index 21894f4bb61..3ed3bec66a0 100644 --- a/fsm/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/repos/common/KeyValueBasedDefaultStatesManagerRepo.kt +++ b/fsm/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/repos/common/KeyValueBasedDefaultStatesManagerRepo.kt @@ -1,25 +1,59 @@ package dev.inmo.micro_utils.fsm.repos.common +import dev.inmo.kslog.common.TagLogger +import dev.inmo.kslog.common.i +import dev.inmo.micro_utils.coroutines.SmartRWLocker +import dev.inmo.micro_utils.coroutines.withReadAcquire +import dev.inmo.micro_utils.coroutines.withWriteLock import dev.inmo.micro_utils.fsm.common.State import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.pagination.getAll +import dev.inmo.micro_utils.repos.unset class KeyValueBasedDefaultStatesManagerRepo( private val keyValueRepo: KeyValueRepo ) : DefaultStatesManagerRepo { + private val locker = SmartRWLocker() + private val logger = TagLogger("KeyValueBasedDefaultStatesManagerRepo") override suspend fun set(state: T) { - keyValueRepo.set(state.context, state) - } - - override suspend fun removeState(state: T) { - if (keyValueRepo.get(state.context) == state) { - keyValueRepo.unset(state.context) + locker.withWriteLock { + keyValueRepo.set(state.context, state) + logger.i { "Set ${state.context} value to $state" } } } - override suspend fun getStates(): List = keyValueRepo.getAll { keys(it) }.map { it.second } - override suspend fun getContextState(context: Any): T? = keyValueRepo.get(context) + override suspend fun removeState(state: T) { + locker.withWriteLock { + if (keyValueRepo.get(state.context) == state) { + keyValueRepo.unset(state.context) + logger.i { "Unset $state" } + } + } + } - override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context) + override suspend fun removeAndSet(toRemove: T, toSet: T) { + locker.withWriteLock { + when { + toRemove.context == toSet.context -> { + keyValueRepo.set(toSet.context, toSet) + } + else -> { + keyValueRepo.set(toSet.context, toSet) + keyValueRepo.unset(toRemove) + } + } + } + } + + override suspend fun getStates(): List = locker.withReadAcquire { + keyValueRepo.getAll { keys(it) }.map { it.second } + } + override suspend fun getContextState(context: Any): T? = locker.withReadAcquire { + keyValueRepo.get(context) + } + + override suspend fun contains(context: Any): Boolean = locker.withReadAcquire { + keyValueRepo.contains(context) + } } From a8e226786d177dffe96a992067f2dc1addf704b8 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Fri, 17 Jan 2025 19:46:29 +0600 Subject: [PATCH 3/3] improve selectByIds and fill changelog --- CHANGELOG.md | 6 ++++++ .../micro_utils/repos/exposed/CommonExposedRepo.kt | 14 +++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cf67526731..480d3ebe6e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## 0.24.4 +* `Repos`: + * `Exposed`: + * Improve `CommonExposedRepo.selectByIds` +* `FSM`: + * Fixes and improvements + ## 0.24.3 * `Ksp`: diff --git a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/CommonExposedRepo.kt b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/CommonExposedRepo.kt index febc8648ddf..e4cad290308 100644 --- a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/CommonExposedRepo.kt +++ b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/CommonExposedRepo.kt @@ -8,8 +8,16 @@ interface CommonExposedRepo : ExposedRepo { val selectById: ISqlExpressionBuilder.(IdType) -> Op val selectByIds: ISqlExpressionBuilder.(List) -> Op get() = { - it.foldRight?>(null) { id, acc -> - acc ?.or(selectById(id)) ?: selectById(id) - } ?: Op.FALSE + if (it.isEmpty()) { + Op.FALSE + } else { + var op = it.firstOrNull() ?.let { selectById(it) } ?: Op.FALSE + var i = 1 + while (i < it.size) { + op = op.or(selectById(it[i])) + i++ + } + op + } } }