mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2026-04-18 23:42:31 +00:00
Compare commits
6 Commits
5447bf9691
...
v0.25.3
| Author | SHA1 | Date | |
|---|---|---|---|
| d46cc3b09c | |||
| dfd23f8d60 | |||
| 28eb1a11e6 | |||
| 04f82a03bf | |||
| aac545074b | |||
| 87a3a925ee |
@@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
* `Coroutines`:
|
* `Coroutines`:
|
||||||
* Add extensions `SmartRWLocker.alsoWithUnlockingOnSuccessAsync` and `SmartRWLocker.alsoWithUnlockingOnSuccess`
|
* 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`:
|
* `Transactions`:
|
||||||
* Add `TransactionsDSL`
|
* Add `TransactionsDSL`
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,22 @@ import kotlin.contracts.ExperimentalContracts
|
|||||||
import kotlin.contracts.InvocationKind
|
import kotlin.contracts.InvocationKind
|
||||||
import kotlin.contracts.contract
|
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>(
|
class SmartKeyRWLocker<T>(
|
||||||
globalLockerReadPermits: Int = Int.MAX_VALUE,
|
globalLockerReadPermits: Int = Int.MAX_VALUE,
|
||||||
globalLockerWriteIsLocked: Boolean = false,
|
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)
|
@OptIn(ExperimentalContracts::class)
|
||||||
suspend inline fun <T, R> SmartKeyRWLocker<T>.withWriteLock(key: T, action: () -> R): R {
|
suspend inline fun <T, R> SmartKeyRWLocker<T>.withWriteLock(key: T, action: () -> R): R {
|
||||||
contract {
|
contract {
|
||||||
@@ -164,4 +201,24 @@ suspend inline fun <T, R> SmartKeyRWLocker<T>.withWriteLock(key: T, action: () -
|
|||||||
} finally {
|
} finally {
|
||||||
unlockWrite(key)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,9 +8,10 @@ import kotlin.contracts.contract
|
|||||||
/**
|
/**
|
||||||
* Composite mutex which works with next rules:
|
* 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]
|
* * [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]
|
* * [unlockWrite] will just unlock [writeMutex]
|
||||||
*/
|
*/
|
||||||
class SmartRWLocker(private val readPermits: Int = Int.MAX_VALUE, writeIsLocked: Boolean = false) {
|
class SmartRWLocker(private val readPermits: Int = Int.MAX_VALUE, writeIsLocked: Boolean = false) {
|
||||||
|
|||||||
@@ -64,6 +64,56 @@ class SmartKeyRWLockerTests {
|
|||||||
assertFalse { locker.isWriteLocked(testKey) }
|
assertFalse { locker.isWriteLocked(testKey) }
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
|
fun readLockFailedOnWriteLockKeyTest() = runTest {
|
||||||
|
val locker = SmartKeyRWLocker<String>()
|
||||||
|
val testKey = "test"
|
||||||
|
locker.lockWrite(testKey)
|
||||||
|
|
||||||
|
assertTrue { locker.isWriteLocked(testKey) }
|
||||||
|
|
||||||
|
assertFails {
|
||||||
|
realWithTimeout(1.seconds) {
|
||||||
|
locker.acquireRead()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(locker.readSemaphore().maxPermits - 1, locker.readSemaphore().freePermits)
|
||||||
|
|
||||||
|
locker.unlockWrite(testKey)
|
||||||
|
assertFalse { locker.isWriteLocked(testKey) }
|
||||||
|
|
||||||
|
realWithTimeout(1.seconds) {
|
||||||
|
locker.acquireRead()
|
||||||
|
}
|
||||||
|
assertEquals(locker.readSemaphore().maxPermits - 1, locker.readSemaphore().freePermits)
|
||||||
|
assertTrue { locker.releaseRead() }
|
||||||
|
assertEquals(locker.readSemaphore().maxPermits, locker.readSemaphore().freePermits)
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun writeLockFailedOnWriteLockKeyTest() = runTest {
|
||||||
|
val locker = SmartKeyRWLocker<String>()
|
||||||
|
val testKey = "test"
|
||||||
|
locker.lockWrite(testKey)
|
||||||
|
|
||||||
|
assertTrue { locker.isWriteLocked(testKey) }
|
||||||
|
|
||||||
|
assertFails {
|
||||||
|
realWithTimeout(1.seconds) {
|
||||||
|
locker.lockWrite()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertFalse(locker.isWriteLocked())
|
||||||
|
|
||||||
|
locker.unlockWrite(testKey)
|
||||||
|
assertFalse { locker.isWriteLocked(testKey) }
|
||||||
|
|
||||||
|
realWithTimeout(1.seconds) {
|
||||||
|
locker.lockWrite()
|
||||||
|
}
|
||||||
|
assertTrue(locker.isWriteLocked())
|
||||||
|
assertTrue { locker.unlockWrite() }
|
||||||
|
assertFalse(locker.isWriteLocked())
|
||||||
|
}
|
||||||
|
@Test
|
||||||
fun readsBlockingGlobalWrite() = runTest {
|
fun readsBlockingGlobalWrite() = runTest {
|
||||||
val locker = SmartKeyRWLocker<String>()
|
val locker = SmartKeyRWLocker<String>()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
|
||||||
kt = "2.1.10"
|
kt = "2.1.20"
|
||||||
kt-serialization = "1.8.0"
|
kt-serialization = "1.8.0"
|
||||||
kt-coroutines = "1.10.1"
|
kt-coroutines = "1.10.1"
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ koin = "4.0.2"
|
|||||||
|
|
||||||
okio = "3.10.2"
|
okio = "3.10.2"
|
||||||
|
|
||||||
ksp = "2.1.10-1.0.31"
|
ksp = "2.1.20-1.0.31"
|
||||||
kotlin-poet = "1.18.1"
|
kotlin-poet = "1.18.1"
|
||||||
|
|
||||||
versions = "0.51.0"
|
versions = "0.51.0"
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import dev.inmo.micro_utils.repos.*
|
|||||||
import dev.inmo.micro_utils.repos.annotations.OverrideRequireManualInvalidation
|
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.ActualizeAllClearMode
|
||||||
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
import dev.inmo.micro_utils.repos.pagination.maxPagePagination
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ import dev.inmo.micro_utils.pagination.Pagination
|
|||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.annotations.OverrideRequireManualInvalidation
|
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 dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache.full.direct
|
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.SmartRWLocker
|
||||||
import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions
|
import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions
|
||||||
import dev.inmo.micro_utils.coroutines.withReadAcquire
|
import dev.inmo.micro_utils.coroutines.withReadAcquire
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "application"
|
|
||||||
id "com.google.devtools.ksp"
|
id "com.google.devtools.ksp"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppJvmJsLinuxMingwProject"
|
apply from: "$mppJvmJsLinuxMingwProject"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
|
jvm {
|
||||||
|
binaries {
|
||||||
|
executable {
|
||||||
|
mainClass.set("dev.inmo.micro_utils.startup.launcher.MainKt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -23,10 +29,6 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
|
||||||
mainClassName = "dev.inmo.micro_utils.startup.launcher.MainKt"
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
|||||||
Reference in New Issue
Block a user