From ce7d4fe9a20dbf8334dde2ea60cb5676433ee27a Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sat, 12 Aug 2023 22:08:12 +0600 Subject: [PATCH] fix of #293 --- CHANGELOG.md | 3 + .../micro_utils/coroutines/SmartRWLocker.kt | 102 +++++++++++++ .../micro_utils/coroutines/SmartSemaphore.kt | 142 ++++++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartRWLocker.kt create mode 100644 coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartSemaphore.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c5c560375a..4f0288f92a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ * Module is initialized * `Pickers`: * Module is initialized +* `Coroutines`: + * Add `SmartSemaphore` + * Add `SmartRWLocker` ## 0.20.0 diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartRWLocker.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartRWLocker.kt new file mode 100644 index 00000000000..0e79aca6f21 --- /dev/null +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartRWLocker.kt @@ -0,0 +1,102 @@ +package dev.inmo.micro_utils.coroutines + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Composite mutex which works with next rules: + * + * * [acquireRead] require to [writeMutex] be free. Then it will take one lock from [readSemaphore] + * * [releaseRead] will just free up one permit in [readSemaphore] + * * [lockWrite] will lock [writeMutex] and then await while all [readSemaphore] will be freed + * * [unlockWrite] will just unlock [writeMutex] + */ +class SmartRWLocker(private val readPermits: Int = Int.MAX_VALUE) { + private val _readSemaphore = SmartSemaphore.Mutable(permits = readPermits, acquiredPermits = 0) + private val _writeMutex = SmartMutex.Mutable(locked = false) + + val readSemaphore: SmartSemaphore.Immutable = _readSemaphore.immutable() + val writeMutex: SmartMutex.Immutable = _writeMutex.immutable() + + /** + * Do lock in [readSemaphore] inside of [writeMutex] locking + */ + suspend fun acquireRead() { + _writeMutex.withLock { + _readSemaphore.acquire() + } + } + + /** + * Release one read permit in [readSemaphore] + */ + suspend fun releaseRead(): Boolean { + return _readSemaphore.release() + } + + /** + * Locking [writeMutex] and wait while all [readSemaphore] permits will be freed + */ + suspend fun lockWrite() { + _writeMutex.lock() + readSemaphore.waitRelease(readPermits) + } + + /** + * Unlock [writeMutex] + */ + suspend fun unlockWrite(): Boolean { + return _writeMutex.unlock() + } +} + +/** + * Will call [SmartSemaphore.Mutable.lock], then execute [action] and return the result after [SmartSemaphore.Mutable.unlock] + */ +@OptIn(ExperimentalContracts::class) +suspend inline fun SmartRWLocker.withReadAcquire(action: () -> T): T { + contract { + callsInPlace(action, InvocationKind.EXACTLY_ONCE) + } + + acquireRead() + try { + return action() + } finally { + releaseRead() + } +} + +/** + * Will wait until the [SmartSemaphore.permitsStateFlow] of [this] instance will have [permits] count free permits. + * + * Anyway, after the end of this block there are no any guaranties that [SmartSemaphore.freePermits] >= [permits] due to + * the fact that some other parties may lock it again + */ +suspend fun SmartRWLocker.waitReadRelease(permits: Int = 1) = readSemaphore.waitRelease(permits) + +/** + * Will call [SmartMutex.Mutable.lock], then execute [action] and return the result after [SmartMutex.Mutable.unlock] + */ +@OptIn(ExperimentalContracts::class) +suspend inline fun SmartRWLocker.withWriteLock(action: () -> T): T { + contract { + callsInPlace(action, InvocationKind.EXACTLY_ONCE) + } + + lockWrite() + try { + return action() + } finally { + unlockWrite() + } +} + +/** + * 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 SmartRWLocker.waitWriteUnlock() = writeMutex.waitUnlock() diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartSemaphore.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartSemaphore.kt new file mode 100644 index 00000000000..bfb29feb599 --- /dev/null +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartSemaphore.kt @@ -0,0 +1,142 @@ +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.Semaphore +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 [Semaphore], but in difference have [permitsStateFlow] for listening of the + * [SmartSemaphore] state. + * + * There is [Mutable] and [Immutable] realizations. In case you are owner and manager current state of lock, you need + * [Mutable] [SmartSemaphore]. Otherwise, [Immutable]. + * + * Any [Mutable] [SmartSemaphore] may produce its [Immutable] variant which will contains [permitsStateFlow] equal to its + * [Mutable] creator + */ +sealed interface SmartSemaphore { + val permitsStateFlow: StateFlow + + /** + * * True - locked + * * False - unlocked + */ + val freePermits: Int + get() = permitsStateFlow.value + + /** + * Immutable variant of [SmartSemaphore]. In fact will depend on the owner of [permitsStateFlow] + */ + class Immutable(override val permitsStateFlow: StateFlow) : SmartSemaphore + + /** + * Mutable variant of [SmartSemaphore]. 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 [freePermits] and its internal [_permitsStateFlow] + */ + class Mutable(private val permits: Int, acquiredPermits: Int = 0) : SmartSemaphore { + private val _permitsStateFlow = MutableStateFlow(permits - acquiredPermits) + override val permitsStateFlow: StateFlow = _permitsStateFlow.asStateFlow() + + private val internalChangesMutex = Mutex(false) + + fun immutable() = Immutable(permitsStateFlow) + + private fun checkedPermits(permits: Int) = permits.coerceIn(1 .. this.permits) + + /** + * Holds call until this [SmartSemaphore] will be re-locked. That means that while [freePermits] == true, [holds] will + * wait for [freePermits] == false and then try to lock + */ + suspend fun acquire(permits: Int = 1) { + do { + val checkedPermits = checkedPermits(permits) + waitRelease(checkedPermits) + val shouldContinue = internalChangesMutex.withLock { + if (_permitsStateFlow.value < checkedPermits) { + true + } else { + _permitsStateFlow.value -= checkedPermits + false + } + } + } while (shouldContinue && currentCoroutineContext().isActive) + } + + /** + * Will try to lock this [SmartSemaphore] immediataly + * + * @return True if lock was successful. False otherwise + */ + suspend fun tryAcquire(permits: Int = 1): Boolean { + val checkedPermits = checkedPermits(permits) + return if (_permitsStateFlow.value >= checkedPermits) { + internalChangesMutex.withLock { + if (_permitsStateFlow.value >= checkedPermits) { + _permitsStateFlow.value -= checkedPermits + true + } else { + false + } + } + } else { + false + } + } + + /** + * If [freePermits] == true - will change it to false and return true. If current call will not unlock this + * [SmartSemaphore] - false + */ + suspend fun release(permits: Int = 1): Boolean { + val checkedPermits = checkedPermits(permits) + return if (this.permits - _permitsStateFlow.value > checkedPermits) { + internalChangesMutex.withLock { + if (this.permits - _permitsStateFlow.value > checkedPermits) { + _permitsStateFlow.value += checkedPermits + true + } else { + false + } + } + } else { + false + } + } + } +} + +/** + * Will call [SmartSemaphore.Mutable.lock], then execute [action] and return the result after [SmartSemaphore.Mutable.unlock] + */ +@OptIn(ExperimentalContracts::class) +suspend inline fun SmartSemaphore.Mutable.withAcquire(permits: Int = 1, action: () -> T): T { + contract { + callsInPlace(action, InvocationKind.EXACTLY_ONCE) + } + + acquire(permits) + try { + return action() + } finally { + release(permits) + } +} + +/** + * Will wait until the [SmartSemaphore.permitsStateFlow] of [this] instance will have [permits] count free permits. + * + * Anyway, after the end of this block there are no any guaranties that [SmartSemaphore.freePermits] >= [permits] due to + * the fact that some other parties may lock it again + */ +suspend fun SmartSemaphore.waitRelease(permits: Int = 1) = permitsStateFlow.first { it >= permits }