Compare commits

...

3 Commits

6 changed files with 64 additions and 7 deletions

View File

@@ -4,6 +4,10 @@
* `Coroutines`:
* Add extensions `SmartRWLocker.alsoWithUnlockingOnSuccessAsync` and `SmartRWLocker.alsoWithUnlockingOnSuccess`
* Fix of `SmartRWLocker.lockWrite` issue when it could lock write mutex without unlocking
* Add tool `SmartKeyRWLocker`
* `SmartSemaphore` got new property `maxPermits`
* New extension `SmartSemaphore.waitReleaseAll()`
* `Transactions`:
* Add `TransactionsDSL`

View File

@@ -7,6 +7,22 @@ import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* Combining [globalRWLocker] and internal map of [SmartRWLocker] associated by [T] to provide next logic:
*
* * Locker by key, for reading: waiting for [globalRWLocker] unlock write by acquiring read permit in it and then
* taking or creating locker for key [T] and lock its reading too
* * Locker by key, for writing: waiting for [globalRWLocker] unlock write by acquiring read permit in it and then
* taking or creating locker for key [T] and lock its writing
* * [globalRWLocker], for reading: using [SmartRWLocker.acquireRead], will be suspended until its
* [SmartRWLocker.lockWrite] will not be unlocked
* * [globalRWLocker], for writing: using [SmartRWLocker.lockWrite], will be paused by other reading and writing
* operations and will pause other operations until the end of operation (calling of [unlockWrite])
*
* You may see, that lockers by key still will use global locker permits - it is required to prevent [globalRWLocker]
* write locking during all other operations are not done. In fact, all the keys works like a simple [SmartRWLocker] as
* well, as [globalRWLocker], but they are linked with [globalRWLocker] [SmartRWLocker.acquireRead] permissions
*/
class SmartKeyRWLocker<T>(
globalLockerReadPermits: Int = Int.MAX_VALUE,
globalLockerWriteIsLocked: Boolean = false,
@@ -152,6 +168,27 @@ suspend inline fun <T, R> SmartKeyRWLocker<T>.withReadAcquire(key: T, action: ()
}
}
@OptIn(ExperimentalContracts::class)
suspend inline fun <T, R> SmartKeyRWLocker<T>.withReadAcquires(keys: Iterable<T>, action: () -> R): R {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
val acquired = mutableSetOf<T>()
try {
keys.forEach {
acquireRead(it)
acquired.add(it)
}
return action()
} finally {
acquired.forEach {
releaseRead(it)
}
}
}
suspend inline fun <T, R> SmartKeyRWLocker<T>.withReadAcquires(vararg keys: T, action: () -> R): R = withReadAcquires(keys.asIterable(), action)
@OptIn(ExperimentalContracts::class)
suspend inline fun <T, R> SmartKeyRWLocker<T>.withWriteLock(key: T, action: () -> R): R {
contract {
@@ -164,4 +201,24 @@ suspend inline fun <T, R> SmartKeyRWLocker<T>.withWriteLock(key: T, action: () -
} finally {
unlockWrite(key)
}
}
@OptIn(ExperimentalContracts::class)
suspend inline fun <T, R> SmartKeyRWLocker<T>.withWriteLocks(keys: Iterable<T>, action: () -> R): R {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
val locked = mutableSetOf<T>()
try {
keys.forEach {
lockWrite(it)
locked.add(it)
}
return action()
} finally {
locked.forEach {
unlockWrite(it)
}
}
}

View File

@@ -8,9 +8,10 @@ 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]
* * [acquireRead] require to [writeMutex] to 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
* * [lockWrite] will lock [writeMutex] and then await while all [readSemaphore] will be freed. If coroutine will be
* cancelled during read semaphore freeing, locking will be cancelled too with [SmartMutex.Mutable.unlock]ing of [writeMutex]
* * [unlockWrite] will just unlock [writeMutex]
*/
class SmartRWLocker(private val readPermits: Int = Int.MAX_VALUE, writeIsLocked: Boolean = false) {

View File

@@ -11,7 +11,6 @@ import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.annotations.OverrideRequireManualInvalidation
import dev.inmo.micro_utils.repos.cache.util.ActualizeAllClearMode
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import dev.inmo.micro_utils.repos.pagination.maxPagePagination
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*

View File

@@ -8,9 +8,6 @@ import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.annotations.OverrideRequireManualInvalidation
import dev.inmo.micro_utils.repos.cache.full.FullKeyValueCacheRepo
import dev.inmo.micro_utils.repos.cache.full.FullReadKeyValueCacheRepo
import dev.inmo.micro_utils.repos.cache.full.FullWriteKeyValueCacheRepo
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers

View File

@@ -1,6 +1,5 @@
package dev.inmo.micro_utils.repos.cache.full.direct
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.coroutines.SmartRWLocker
import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions
import dev.inmo.micro_utils.coroutines.withReadAcquire