diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a4f3d85761..3c74e713a2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.18.1 + +* `Coroutines`: + * Add `SmartMutex` + ## 0.18.0 **ALL PREVIOUSLY DEPRECATED FUNCTIONALITY HAVE BEEN REMOVED** diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartMutex.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartMutex.kt new file mode 100644 index 00000000000..c8a962d453c --- /dev/null +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartMutex.kt @@ -0,0 +1,136 @@ +package dev.inmo.micro_utils.coroutines + +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.isActive +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * It is interface which will work like classic [Mutex], but in difference have [lockStateFlow] for listening of the + * [SmartMutex] state. + * + * There is [Mutable] and [Immutable] realizations. In case you are owner and manager current state of lock, you need + * [Mutable] [SmartMutex]. Otherwise, [Immutable]. + * + * Any [Mutable] [SmartMutex] may produce its [Immutable] variant which will contains [lockStateFlow] equal to its + * [Mutable] creator + */ +sealed interface SmartMutex { + val lockStateFlow: StateFlow + + /** + * * True - locked + * * False - unlocked + */ + val isLocked: Boolean + get() = lockStateFlow.value + + /** + * Immutable variant of [SmartMutex]. In fact will depend on the owner of [lockStateFlow] + */ + class Immutable(override val lockStateFlow: StateFlow) : SmartMutex + + /** + * Mutable variant of [SmartMutex]. With that variant you may [lock] and [unlock]. Besides, you may create + * [Immutable] variant of [this] instance with [immutable] factory + * + * @param locked Preset state of [isLocked] and its internal [_lockStateFlow] + */ + class Mutable(locked: Boolean = false) : SmartMutex { + private val _lockStateFlow = MutableStateFlow(locked) + override val lockStateFlow: StateFlow = _lockStateFlow.asStateFlow() + + private val internalChangesMutex = Mutex() + + fun immutable() = Immutable(lockStateFlow) + + /** + * Holds call until this [SmartMutex] will be re-locked. That means that while [isLocked] == true, [holds] will + * wait for [isLocked] == false and then try to lock + */ + suspend fun lock() { + do { + waitUnlock() + val shouldContinue = internalChangesMutex.withLock { + if (_lockStateFlow.value) { + true + } else { + _lockStateFlow.value = true + false + } + } + } while (shouldContinue && currentCoroutineContext().isActive) + } + + /** + * Will try to lock this [SmartMutex] immediataly + * + * @return True if lock was successful. False otherwise + */ + suspend fun tryLock(): Boolean { + return if (!_lockStateFlow.value) { + internalChangesMutex.withLock { + if (!_lockStateFlow.value) { + _lockStateFlow.value = true + true + } else { + false + } + } + } else { + false + } + } + + /** + * If [isLocked] == true - will change it to false and return true. If current call will not unlock this + * [SmartMutex] - false + */ + suspend fun unlock(): Boolean { + return if (_lockStateFlow.value) { + internalChangesMutex.withLock { + if (_lockStateFlow.value) { + _lockStateFlow.value = false + true + } else { + false + } + } + } else { + false + } + } + } +} + +/** + * Will call [SmartMutex.Mutable.lock], then execute [action] and return the result after [SmartMutex.Mutable.unlock] + */ +@OptIn(ExperimentalContracts::class) +suspend inline fun SmartMutex.Mutable.withLock(action: () -> T): T { + contract { + callsInPlace(action, InvocationKind.EXACTLY_ONCE) + } + + lock() + try { + return action() + } finally { + unlock() + } +} + +/** + * Will wait until the [SmartMutex.lockStateFlow] of [this] instance will be false. + * + * Anyway, after the end of this block there are no any guaranties that [SmartMutex.isLocked] == false due to the fact + * that some other parties may lock it again + */ +suspend fun SmartMutex.waitUnlock() = lockStateFlow.first { !it } diff --git a/gradle.properties b/gradle.properties index 5db53e2a94f..9f2f9380401 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,5 +14,5 @@ crypto_js_version=4.1.1 # Project data group=dev.inmo -version=0.18.0 -android_code_version=191 +version=0.18.1 +android_code_version=192