mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2024-11-26 20:18:48 +00:00
commit
cd6c4bbe38
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,5 +1,15 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.20.3
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Compose`: `1.4.3` -> `1.5.0`
|
||||||
|
* `Exposed`: `0.42.1` -> `0.43.0`
|
||||||
|
* `Ktor`: `2.3.3` -> `2.3.4`
|
||||||
|
* `Repos`:
|
||||||
|
* `Cache`:
|
||||||
|
* Fixes in locks of caches
|
||||||
|
|
||||||
## 0.20.2
|
## 0.20.2
|
||||||
|
|
||||||
* All main repos uses `SmartRWLocker`
|
* All main repos uses `SmartRWLocker`
|
||||||
|
@ -23,10 +23,8 @@ class SmartRWLocker(private val readPermits: Int = Int.MAX_VALUE, writeIsLocked:
|
|||||||
* Do lock in [readSemaphore] inside of [writeMutex] locking
|
* Do lock in [readSemaphore] inside of [writeMutex] locking
|
||||||
*/
|
*/
|
||||||
suspend fun acquireRead() {
|
suspend fun acquireRead() {
|
||||||
_writeMutex.withLock {
|
|
||||||
_readSemaphore.acquire()
|
_readSemaphore.acquire()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Release one read permit in [readSemaphore]
|
* Release one read permit in [readSemaphore]
|
||||||
@ -39,15 +37,19 @@ class SmartRWLocker(private val readPermits: Int = Int.MAX_VALUE, writeIsLocked:
|
|||||||
* Locking [writeMutex] and wait while all [readSemaphore] permits will be freed
|
* Locking [writeMutex] and wait while all [readSemaphore] permits will be freed
|
||||||
*/
|
*/
|
||||||
suspend fun lockWrite() {
|
suspend fun lockWrite() {
|
||||||
|
_readSemaphore.acquire(readPermits)
|
||||||
_writeMutex.lock()
|
_writeMutex.lock()
|
||||||
readSemaphore.waitRelease(readPermits)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unlock [writeMutex]
|
* Unlock [writeMutex]
|
||||||
*/
|
*/
|
||||||
suspend fun unlockWrite(): Boolean {
|
suspend fun unlockWrite(): Boolean {
|
||||||
return _writeMutex.unlock()
|
return _writeMutex.unlock().also {
|
||||||
|
if (it) {
|
||||||
|
_readSemaphore.release(readPermits)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,11 +42,11 @@ sealed interface SmartSemaphore {
|
|||||||
* Mutable variant of [SmartSemaphore]. With that variant you may [lock] and [unlock]. Besides, you may create
|
* Mutable variant of [SmartSemaphore]. With that variant you may [lock] and [unlock]. Besides, you may create
|
||||||
* [Immutable] variant of [this] instance with [immutable] factory
|
* [Immutable] variant of [this] instance with [immutable] factory
|
||||||
*
|
*
|
||||||
* @param locked Preset state of [freePermits] and its internal [_permitsStateFlow]
|
* @param locked Preset state of [freePermits] and its internal [_freePermitsStateFlow]
|
||||||
*/
|
*/
|
||||||
class Mutable(private val permits: Int, acquiredPermits: Int = 0) : SmartSemaphore {
|
class Mutable(private val permits: Int, acquiredPermits: Int = 0) : SmartSemaphore {
|
||||||
private val _permitsStateFlow = MutableStateFlow<Int>(permits - acquiredPermits)
|
private val _freePermitsStateFlow = MutableStateFlow<Int>(permits - acquiredPermits)
|
||||||
override val permitsStateFlow: StateFlow<Int> = _permitsStateFlow.asStateFlow()
|
override val permitsStateFlow: StateFlow<Int> = _freePermitsStateFlow.asStateFlow()
|
||||||
|
|
||||||
private val internalChangesMutex = Mutex(false)
|
private val internalChangesMutex = Mutex(false)
|
||||||
|
|
||||||
@ -54,19 +54,45 @@ sealed interface SmartSemaphore {
|
|||||||
|
|
||||||
private fun checkedPermits(permits: Int) = permits.coerceIn(1 .. this.permits)
|
private fun checkedPermits(permits: Int) = permits.coerceIn(1 .. this.permits)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds call until this [SmartSemaphore] will be re-locked. That means that current method will
|
||||||
|
*/
|
||||||
|
suspend fun acquire(permits: Int = 1) {
|
||||||
|
var acquiredPermits = 0
|
||||||
|
val checkedPermits = checkedPermits(permits)
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
val shouldContinue = internalChangesMutex.withLock {
|
||||||
|
val requiredPermits = checkedPermits - acquiredPermits
|
||||||
|
val acquiring = minOf(freePermits, requiredPermits).takeIf { it > 0 } ?: return@withLock true
|
||||||
|
acquiredPermits += acquiring
|
||||||
|
_freePermitsStateFlow.value -= acquiring
|
||||||
|
|
||||||
|
acquiredPermits != checkedPermits
|
||||||
|
}
|
||||||
|
if (shouldContinue) {
|
||||||
|
waitRelease()
|
||||||
|
}
|
||||||
|
} while (shouldContinue && currentCoroutineContext().isActive)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
release(acquiredPermits)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds call until this [SmartSemaphore] will be re-locked. That means that while [freePermits] == true, [holds] will
|
* 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
|
* wait for [freePermits] == false and then try to lock
|
||||||
*/
|
*/
|
||||||
suspend fun acquire(permits: Int = 1) {
|
suspend fun acquireByOne(permits: Int = 1) {
|
||||||
do {
|
|
||||||
val checkedPermits = checkedPermits(permits)
|
val checkedPermits = checkedPermits(permits)
|
||||||
|
do {
|
||||||
waitRelease(checkedPermits)
|
waitRelease(checkedPermits)
|
||||||
val shouldContinue = internalChangesMutex.withLock {
|
val shouldContinue = internalChangesMutex.withLock {
|
||||||
if (_permitsStateFlow.value < checkedPermits) {
|
if (_freePermitsStateFlow.value < checkedPermits) {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
_permitsStateFlow.value -= checkedPermits
|
_freePermitsStateFlow.value -= checkedPermits
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,10 +106,10 @@ sealed interface SmartSemaphore {
|
|||||||
*/
|
*/
|
||||||
suspend fun tryAcquire(permits: Int = 1): Boolean {
|
suspend fun tryAcquire(permits: Int = 1): Boolean {
|
||||||
val checkedPermits = checkedPermits(permits)
|
val checkedPermits = checkedPermits(permits)
|
||||||
return if (_permitsStateFlow.value < checkedPermits) {
|
return if (_freePermitsStateFlow.value < checkedPermits) {
|
||||||
internalChangesMutex.withLock {
|
internalChangesMutex.withLock {
|
||||||
if (_permitsStateFlow.value < checkedPermits) {
|
if (_freePermitsStateFlow.value < checkedPermits) {
|
||||||
_permitsStateFlow.value -= checkedPermits
|
_freePermitsStateFlow.value -= checkedPermits
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@ -100,10 +126,10 @@ sealed interface SmartSemaphore {
|
|||||||
*/
|
*/
|
||||||
suspend fun release(permits: Int = 1): Boolean {
|
suspend fun release(permits: Int = 1): Boolean {
|
||||||
val checkedPermits = checkedPermits(permits)
|
val checkedPermits = checkedPermits(permits)
|
||||||
return if (_permitsStateFlow.value < this.permits) {
|
return if (_freePermitsStateFlow.value < this.permits) {
|
||||||
internalChangesMutex.withLock {
|
internalChangesMutex.withLock {
|
||||||
if (_permitsStateFlow.value < this.permits) {
|
if (_freePermitsStateFlow.value < this.permits) {
|
||||||
_permitsStateFlow.value = minOf(_permitsStateFlow.value + checkedPermits, this.permits)
|
_freePermitsStateFlow.value = minOf(_freePermitsStateFlow.value + checkedPermits, this.permits)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import dev.inmo.micro_utils.coroutines.SmartRWLocker
|
import dev.inmo.micro_utils.coroutines.*
|
||||||
import dev.inmo.micro_utils.coroutines.withReadAcquire
|
import kotlinx.coroutines.*
|
||||||
import dev.inmo.micro_utils.coroutines.withWriteLock
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.CoroutineStart
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.joinAll
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
@ -57,4 +53,99 @@ class SmartRWLockerTests {
|
|||||||
assertEquals(expected = readAndWriteWorkers, actual = doneWrites)
|
assertEquals(expected = readAndWriteWorkers, actual = doneWrites)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simpleWithWriteLockTest() {
|
||||||
|
val locker = SmartRWLocker()
|
||||||
|
|
||||||
|
runTest {
|
||||||
|
locker.withWriteLock {
|
||||||
|
assertEquals(0, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(true, locker.writeMutex.isLocked)
|
||||||
|
}
|
||||||
|
assertEquals(Int.MAX_VALUE, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(false, locker.writeMutex.isLocked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun failureWithWriteLockTest() {
|
||||||
|
val locker = SmartRWLocker()
|
||||||
|
|
||||||
|
val exception = IllegalArgumentException()
|
||||||
|
try {
|
||||||
|
runTest {
|
||||||
|
val subscope = kotlinx.coroutines.CoroutineScope(this.coroutineContext)
|
||||||
|
var happenException: Throwable? = null
|
||||||
|
try {
|
||||||
|
locker.withWriteLock {
|
||||||
|
val checkFunction = fun (): Deferred<Unit> {
|
||||||
|
return subscope.async {
|
||||||
|
assertEquals(0, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(true, locker.writeMutex.isLocked)
|
||||||
|
throw exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doInDefault {
|
||||||
|
assertEquals(0, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(true, locker.writeMutex.isLocked)
|
||||||
|
checkFunction().await()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
happenException = e
|
||||||
|
}
|
||||||
|
if (exception != happenException) {
|
||||||
|
assertEquals(exception, happenException ?.cause)
|
||||||
|
}
|
||||||
|
assertEquals(Int.MAX_VALUE, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(false, locker.writeMutex.isLocked)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
assertEquals(exception, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simpleWithReadAcquireTest() {
|
||||||
|
val locker = SmartRWLocker()
|
||||||
|
|
||||||
|
runTest {
|
||||||
|
locker.withReadAcquire {
|
||||||
|
assertEquals(Int.MAX_VALUE - 1, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(false, locker.writeMutex.isLocked)
|
||||||
|
locker.withReadAcquire {
|
||||||
|
assertEquals(Int.MAX_VALUE - 2, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(false, locker.writeMutex.isLocked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(Int.MAX_VALUE, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(false, locker.writeMutex.isLocked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simple2WithWriteLockTest() {
|
||||||
|
val locker = SmartRWLocker()
|
||||||
|
|
||||||
|
val unlockDelay = 1000L // 1 sec
|
||||||
|
var unlocked: Boolean = false
|
||||||
|
runTest {
|
||||||
|
launch {
|
||||||
|
locker.withReadAcquire {
|
||||||
|
delay(unlockDelay)
|
||||||
|
}
|
||||||
|
unlocked = true
|
||||||
|
}
|
||||||
|
locker.readSemaphore.permitsStateFlow.first { it == Int.MAX_VALUE - 1 }
|
||||||
|
assertEquals(false, unlocked)
|
||||||
|
locker.withWriteLock {
|
||||||
|
assertEquals(true, unlocked)
|
||||||
|
assertEquals(0, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(true, locker.writeMutex.isLocked)
|
||||||
|
}
|
||||||
|
assertEquals(Int.MAX_VALUE, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(false, locker.writeMutex.isLocked)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,5 +15,5 @@ crypto_js_version=4.1.1
|
|||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.20.2
|
version=0.20.3
|
||||||
android_code_version=208
|
android_code_version=209
|
||||||
|
@ -6,14 +6,14 @@ kt-coroutines = "1.7.3"
|
|||||||
|
|
||||||
kslog = "1.2.0"
|
kslog = "1.2.0"
|
||||||
|
|
||||||
jb-compose = "1.4.3"
|
jb-compose = "1.5.0"
|
||||||
jb-exposed = "0.42.1"
|
jb-exposed = "0.43.0"
|
||||||
jb-dokka = "1.8.20"
|
jb-dokka = "1.8.20"
|
||||||
|
|
||||||
korlibs = "4.0.10"
|
korlibs = "4.0.10"
|
||||||
uuid = "0.8.0"
|
uuid = "0.8.0"
|
||||||
|
|
||||||
ktor = "2.3.3"
|
ktor = "2.3.4"
|
||||||
|
|
||||||
gh-release = "2.4.1"
|
gh-release = "2.4.1"
|
||||||
|
|
||||||
|
@ -61,18 +61,28 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
|||||||
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
|
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
|
||||||
|
|
||||||
val createdObjectsFlowJob = parentRepo.newObjectsFlow.onEach {
|
val createdObjectsFlowJob = parentRepo.newObjectsFlow.onEach {
|
||||||
locker.withWriteLock { kvCache.set(idGetter(it), it) }
|
locker.withWriteLock {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
|
|
||||||
val updatedObjectsFlowJob = parentRepo.updatedObjectsFlow.onEach {
|
val updatedObjectsFlowJob = parentRepo.updatedObjectsFlow.onEach {
|
||||||
locker.withWriteLock { kvCache.set(idGetter(it), it) }
|
locker.withWriteLock {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
|
|
||||||
val deletedObjectsFlowJob = parentRepo.deletedObjectsIdsFlow.onEach {
|
val deletedObjectsFlowJob = parentRepo.deletedObjectsIdsFlow.onEach {
|
||||||
locker.withWriteLock { kvCache.unset(it) }
|
locker.withWriteLock {
|
||||||
|
kvCache.unset(it)
|
||||||
|
}
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
|
|
||||||
override suspend fun deleteById(ids: List<IdType>) = parentRepo.deleteById(ids)
|
override suspend fun deleteById(ids: List<IdType>) = parentRepo.deleteById(ids).also {
|
||||||
|
locker.withWriteLock {
|
||||||
|
kvCache.unset(ids)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> {
|
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> {
|
||||||
val updated = parentRepo.update(values)
|
val updated = parentRepo.update(values)
|
||||||
|
@ -28,9 +28,18 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
|
|||||||
kvCache.action().onPresented { return it }
|
kvCache.action().onPresented { return it }
|
||||||
}
|
}
|
||||||
return parentRepo.actionElse().also {
|
return parentRepo.actionElse().also {
|
||||||
locker.withWriteLock { kvCache.actualize(it) }
|
kvCache.actualize(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
protected suspend inline fun <T> doOrTakeAndActualizeWithWriteLock(
|
||||||
|
action: KeyValueRepo<IdType, ObjectType>.() -> Optional<T>,
|
||||||
|
actionElse: ReadCRUDRepo<ObjectType, IdType>.() -> T,
|
||||||
|
actualize: KeyValueRepo<IdType, ObjectType>.(T) -> Unit
|
||||||
|
): T = doOrTakeAndActualize(
|
||||||
|
action = action,
|
||||||
|
actionElse = actionElse,
|
||||||
|
actualize = { locker.withWriteLock { actualize(it) } }
|
||||||
|
)
|
||||||
|
|
||||||
protected open suspend fun actualizeAll() {
|
protected open suspend fun actualizeAll() {
|
||||||
locker.withWriteLock { kvCache.actualizeAll(parentRepo) }
|
locker.withWriteLock { kvCache.actualizeAll(parentRepo) }
|
||||||
@ -54,22 +63,22 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
|
|||||||
{ if (it != 0L) actualizeAll() }
|
{ if (it != 0L) actualizeAll() }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun contains(id: IdType): Boolean = doOrTakeAndActualize(
|
override suspend fun contains(id: IdType): Boolean = doOrTakeAndActualizeWithWriteLock(
|
||||||
{ contains(id).takeIf { it }.optionalOrAbsentIfNull },
|
{ contains(id).takeIf { it }.optionalOrAbsentIfNull },
|
||||||
{ contains(id) },
|
{ contains(id) },
|
||||||
{ if (it) parentRepo.getById(id) ?.let { set(id, it) } }
|
{ if (it) parentRepo.getById(id) ?.let { kvCache.set(id, it) } }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun getAll(): Map<IdType, ObjectType> = doOrTakeAndActualize(
|
override suspend fun getAll(): Map<IdType, ObjectType> = doOrTakeAndActualizeWithWriteLock(
|
||||||
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
|
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||||
{ getAll() },
|
{ getAll() },
|
||||||
{ kvCache.actualizeAll(clear = true) { it } }
|
{ kvCache.actualizeAll(clear = true) { it } }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualize(
|
override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualizeWithWriteLock(
|
||||||
{ get(id) ?.optional ?: Optional.absent() },
|
{ get(id) ?.optional ?: Optional.absent() },
|
||||||
{ getById(id) },
|
{ getById(id) },
|
||||||
{ it ?.let { set(idGetter(it), it) } }
|
{ it ?.let { kvCache.set(idGetter(it), it) } }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun invalidate() {
|
override suspend fun invalidate() {
|
||||||
|
@ -28,20 +28,29 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
|
|||||||
kvCache.action().onPresented { return it }
|
kvCache.action().onPresented { return it }
|
||||||
}
|
}
|
||||||
return parentRepo.actionElse().also {
|
return parentRepo.actionElse().also {
|
||||||
locker.withWriteLock { kvCache.actualize(it) }
|
kvCache.actualize(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
protected suspend inline fun <T> doOrTakeAndActualizeWithWriteLock(
|
||||||
|
action: KeyValueRepo<Key, Value>.() -> Optional<T>,
|
||||||
|
actionElse: ReadKeyValueRepo<Key, Value>.() -> T,
|
||||||
|
actualize: KeyValueRepo<Key, Value>.(T) -> Unit
|
||||||
|
): T = doOrTakeAndActualize(
|
||||||
|
action = action,
|
||||||
|
actionElse = actionElse,
|
||||||
|
actualize = { locker.withWriteLock { actualize(it) } }
|
||||||
|
)
|
||||||
protected open suspend fun actualizeAll() {
|
protected open suspend fun actualizeAll() {
|
||||||
locker.withWriteLock {
|
locker.withWriteLock {
|
||||||
kvCache.clear()
|
kvCache.clear()
|
||||||
kvCache.set(parentRepo.getAll { keys(it) }.toMap())
|
kvCache.set(parentRepo.getAll())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun get(k: Key): Value? = doOrTakeAndActualize(
|
override suspend fun get(k: Key): Value? = doOrTakeAndActualizeWithWriteLock(
|
||||||
{ get(k) ?.optional ?: Optional.absent() },
|
{ get(k) ?.optional ?: Optional.absent() },
|
||||||
{ get(k) },
|
{ get(k) },
|
||||||
{ set(k, it ?: return@doOrTakeAndActualize) }
|
{ kvCache.set(k, it ?: return@doOrTakeAndActualizeWithWriteLock) }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = doOrTakeAndActualize(
|
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = doOrTakeAndActualize(
|
||||||
@ -56,13 +65,13 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
|
|||||||
{ if (it != 0L) actualizeAll() }
|
{ if (it != 0L) actualizeAll() }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun contains(key: Key): Boolean = doOrTakeAndActualize(
|
override suspend fun contains(key: Key): Boolean = doOrTakeAndActualizeWithWriteLock(
|
||||||
{ contains(key).takeIf { it }.optionalOrAbsentIfNull },
|
{ contains(key).takeIf { it }.optionalOrAbsentIfNull },
|
||||||
{ contains(key) },
|
{ contains(key) },
|
||||||
{ if (it) parentRepo.get(key) ?.also { kvCache.set(key, it) } }
|
{ if (it) parentRepo.get(key) ?.also { kvCache.set(key, it) } }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun getAll(): Map<Key, Value> = doOrTakeAndActualize(
|
override suspend fun getAll(): Map<Key, Value> = doOrTakeAndActualizeWithWriteLock(
|
||||||
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
|
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||||
{ getAll() },
|
{ getAll() },
|
||||||
{ kvCache.actualizeAll(clear = true) { it } }
|
{ kvCache.actualizeAll(clear = true) { it } }
|
||||||
|
@ -27,9 +27,18 @@ open class FullReadKeyValuesCacheRepo<Key,Value>(
|
|||||||
kvCache.action().onPresented { return it }
|
kvCache.action().onPresented { return it }
|
||||||
}
|
}
|
||||||
return parentRepo.actionElse().also {
|
return parentRepo.actionElse().also {
|
||||||
locker.withWriteLock { kvCache.actualize(it) }
|
kvCache.actualize(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
protected suspend inline fun <T> doOrTakeAndActualizeWithWriteLock(
|
||||||
|
action: KeyValueRepo<Key, List<Value>>.() -> Optional<T>,
|
||||||
|
actionElse: ReadKeyValuesRepo<Key, Value>.() -> T,
|
||||||
|
actualize: KeyValueRepo<Key, List<Value>>.(T) -> Unit
|
||||||
|
): T = doOrTakeAndActualize(
|
||||||
|
action = action,
|
||||||
|
actionElse = actionElse,
|
||||||
|
actualize = { locker.withWriteLock { actualize(it) } }
|
||||||
|
)
|
||||||
|
|
||||||
protected open suspend fun actualizeKey(k: Key) {
|
protected open suspend fun actualizeKey(k: Key) {
|
||||||
locker.withWriteLock {
|
locker.withWriteLock {
|
||||||
|
@ -65,12 +65,12 @@ abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
protected val map: MutableMap<IdType, ObjectType> = mutableMapOf(),
|
protected val map: MutableMap<IdType, ObjectType> = mutableMapOf(),
|
||||||
protected val locker: SmartRWLocker = SmartRWLocker()
|
protected val locker: SmartRWLocker = SmartRWLocker()
|
||||||
) : WriteCRUDRepo<ObjectType, IdType, InputValueType> {
|
) : WriteCRUDRepo<ObjectType, IdType, InputValueType> {
|
||||||
protected val _newObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
protected open val _newObjectsFlow: MutableSharedFlow<ObjectType> = MapsReposDefaultMutableSharedFlow()
|
||||||
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow()
|
override val newObjectsFlow: Flow<ObjectType> by lazy { _newObjectsFlow.asSharedFlow() }
|
||||||
protected val _updatedObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
protected open val _updatedObjectsFlow: MutableSharedFlow<ObjectType> = MapsReposDefaultMutableSharedFlow()
|
||||||
override val updatedObjectsFlow: Flow<ObjectType> = _updatedObjectsFlow.asSharedFlow()
|
override val updatedObjectsFlow: Flow<ObjectType> by lazy { _updatedObjectsFlow.asSharedFlow() }
|
||||||
protected val _deletedObjectsIdsFlow: MutableSharedFlow<IdType> = MutableSharedFlow()
|
protected open val _deletedObjectsIdsFlow: MutableSharedFlow<IdType> = MapsReposDefaultMutableSharedFlow()
|
||||||
override val deletedObjectsIdsFlow: Flow<IdType> = _deletedObjectsIdsFlow.asSharedFlow()
|
override val deletedObjectsIdsFlow: Flow<IdType> by lazy { _deletedObjectsIdsFlow.asSharedFlow() }
|
||||||
|
|
||||||
protected abstract suspend fun updateObject(newValue: InputValueType, id: IdType, old: ObjectType): ObjectType
|
protected abstract suspend fun updateObject(newValue: InputValueType, id: IdType, old: ObjectType): ObjectType
|
||||||
protected abstract suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType>
|
protected abstract suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType>
|
||||||
@ -80,10 +80,10 @@ abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
values.map {
|
values.map {
|
||||||
val (id, newObject) = createObject(it)
|
val (id, newObject) = createObject(it)
|
||||||
map[id] = newObject
|
map[id] = newObject
|
||||||
newObject.also { _ ->
|
newObject
|
||||||
_newObjectsFlow.emit(newObject)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}.onEach {
|
||||||
|
_newObjectsFlow.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,8 +93,9 @@ abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
|
|
||||||
newValue.also {
|
newValue.also {
|
||||||
map[id] = it
|
map[id] = it
|
||||||
_updatedObjectsFlow.emit(it)
|
|
||||||
}
|
}
|
||||||
|
} ?.also {
|
||||||
|
_updatedObjectsFlow.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,10 +105,10 @@ abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
|
|
||||||
override suspend fun deleteById(ids: List<IdType>) {
|
override suspend fun deleteById(ids: List<IdType>) {
|
||||||
locker.withWriteLock {
|
locker.withWriteLock {
|
||||||
ids.forEach {
|
ids.mapNotNull {
|
||||||
map.remove(it) ?.also { _ -> _deletedObjectsIdsFlow.emit(it) }
|
it.takeIf { map.remove(it) != null }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}.onEach { _deletedObjectsIdsFlow.emit(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import dev.inmo.micro_utils.pagination.utils.paginate
|
|||||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Map]-based [ReadKeyValueRepo]. All internal operations will be locked with [locker] (mostly with
|
* [Map]-based [ReadKeyValueRepo]. All internal operations will be locked with [locker] (mostly with
|
||||||
@ -88,12 +89,14 @@ class WriteMapKeyValueRepo<Key, Value>(
|
|||||||
private val map: MutableMap<Key, Value>,
|
private val map: MutableMap<Key, Value>,
|
||||||
private val locker: SmartRWLocker
|
private val locker: SmartRWLocker
|
||||||
) : WriteKeyValueRepo<Key, Value> {
|
) : WriteKeyValueRepo<Key, Value> {
|
||||||
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MapsReposDefaultMutableSharedFlow()
|
||||||
override val onNewValue: Flow<Pair<Key, Value>>
|
override val onNewValue: Flow<Pair<Key, Value>> by lazy {
|
||||||
get() = _onNewValue
|
_onNewValue.asSharedFlow()
|
||||||
private val _onValueRemoved: MutableSharedFlow<Key> = MutableSharedFlow()
|
}
|
||||||
override val onValueRemoved: Flow<Key>
|
private val _onValueRemoved: MutableSharedFlow<Key> = MapsReposDefaultMutableSharedFlow()
|
||||||
get() = _onValueRemoved
|
override val onValueRemoved: Flow<Key> by lazy {
|
||||||
|
_onValueRemoved.asSharedFlow()
|
||||||
|
}
|
||||||
constructor(map: MutableMap<Key, Value> = mutableMapOf()) : this(map, SmartRWLocker())
|
constructor(map: MutableMap<Key, Value> = mutableMapOf()) : this(map, SmartRWLocker())
|
||||||
|
|
||||||
override suspend fun set(toSet: Map<Key, Value>) {
|
override suspend fun set(toSet: Map<Key, Value>) {
|
||||||
@ -103,10 +106,10 @@ class WriteMapKeyValueRepo<Key, Value>(
|
|||||||
|
|
||||||
override suspend fun unset(toUnset: List<Key>) {
|
override suspend fun unset(toUnset: List<Key>) {
|
||||||
locker.withWriteLock {
|
locker.withWriteLock {
|
||||||
toUnset.forEach { k ->
|
toUnset.mapNotNull { k ->
|
||||||
map.remove(k) ?.also { _ -> _onValueRemoved.emit(k) }
|
map.remove(k) ?.let { _ -> k }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}.forEach { _onValueRemoved.emit(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun unsetWithValues(toUnset: List<Value>) {
|
override suspend fun unsetWithValues(toUnset: List<Value>) {
|
||||||
|
@ -81,68 +81,94 @@ class MapWriteKeyValuesRepo<Key, Value>(
|
|||||||
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf(),
|
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf(),
|
||||||
private val locker: SmartRWLocker = SmartRWLocker()
|
private val locker: SmartRWLocker = SmartRWLocker()
|
||||||
) : WriteKeyValuesRepo<Key, Value> {
|
) : WriteKeyValuesRepo<Key, Value> {
|
||||||
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MapsReposDefaultMutableSharedFlow()
|
||||||
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
override val onNewValue: Flow<Pair<Key, Value>> by lazy {
|
||||||
private val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
_onNewValue.asSharedFlow()
|
||||||
override val onValueRemoved: Flow<Pair<Key, Value>> = _onValueRemoved.asSharedFlow()
|
}
|
||||||
private val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow()
|
private val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MapsReposDefaultMutableSharedFlow()
|
||||||
override val onDataCleared: Flow<Key> = _onDataCleared.asSharedFlow()
|
override val onValueRemoved: Flow<Pair<Key, Value>> by lazy {
|
||||||
|
_onValueRemoved.asSharedFlow()
|
||||||
|
}
|
||||||
|
private val _onDataCleared: MutableSharedFlow<Key> = MapsReposDefaultMutableSharedFlow()
|
||||||
|
override val onDataCleared: Flow<Key> by lazy {
|
||||||
|
_onDataCleared.asSharedFlow()
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun add(toAdd: Map<Key, List<Value>>) {
|
override suspend fun add(toAdd: Map<Key, List<Value>>) {
|
||||||
locker.withWriteLock {
|
locker.withWriteLock {
|
||||||
toAdd.keys.forEach { k ->
|
toAdd.keys.mapNotNull { k ->
|
||||||
if (map.getOrPut(k) { mutableListOf() }.addAll(toAdd[k] ?: return@forEach)) {
|
(k to toAdd[k]).takeIf {
|
||||||
toAdd[k] ?.forEach { v ->
|
map.getOrPut(k) { mutableListOf() }.addAll(toAdd[k] ?: return@mapNotNull null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.forEach { (k, vs) ->
|
||||||
|
vs ?.forEach { v ->
|
||||||
_onNewValue.emit(k to v)
|
_onNewValue.emit(k to v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
|
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
|
||||||
|
val removed = mutableListOf<Pair<Key, Value>>()
|
||||||
|
val cleared = mutableListOf<Key>()
|
||||||
locker.withWriteLock {
|
locker.withWriteLock {
|
||||||
toRemove.keys.forEach { k ->
|
toRemove.keys.forEach { k ->
|
||||||
if (map[k]?.removeAll(toRemove[k] ?: return@forEach) == true) {
|
if (map[k]?.removeAll(toRemove[k] ?: return@forEach) == true) {
|
||||||
toRemove[k]?.forEach { v ->
|
toRemove[k]?.forEach { v ->
|
||||||
_onValueRemoved.emit(k to v)
|
removed.add(k to v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (map[k]?.isEmpty() == true) {
|
if (map[k]?.isEmpty() == true) {
|
||||||
map.remove(k)
|
map.remove(k)
|
||||||
_onDataCleared.emit(k)
|
cleared.add(k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
removed.forEach {
|
||||||
|
_onValueRemoved.emit(it)
|
||||||
|
}
|
||||||
|
cleared.forEach {
|
||||||
|
_onDataCleared.emit(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun removeWithValue(v: Value) {
|
override suspend fun removeWithValue(v: Value) {
|
||||||
locker.withWriteLock {
|
locker.withWriteLock {
|
||||||
map.forEach { (k, values) ->
|
map.mapNotNull { (k, values) ->
|
||||||
if (values.remove(v)) {
|
if (values.remove(v)) {
|
||||||
_onValueRemoved.emit(k to v)
|
k to v
|
||||||
|
} else {
|
||||||
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.forEach {
|
||||||
|
_onValueRemoved.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun clear(k: Key) {
|
override suspend fun clear(k: Key) {
|
||||||
locker.withWriteLock {
|
locker.withWriteLock {
|
||||||
map.remove(k) ?.also { _onDataCleared.emit(k) }
|
map.remove(k)
|
||||||
}
|
} ?.also { _onDataCleared.emit(k) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun clearWithValue(v: Value) {
|
override suspend fun clearWithValue(v: Value) {
|
||||||
locker.withWriteLock {
|
locker.withWriteLock {
|
||||||
map.filter { (_, values) ->
|
map.filter { (_, values) ->
|
||||||
values.contains(v)
|
values.contains(v)
|
||||||
}.forEach {
|
}.mapNotNull {
|
||||||
map.remove(it.key)?.onEach { v ->
|
if (map.remove(it.key) != null) {
|
||||||
_onValueRemoved.emit(it.key to v)
|
it.toPair()
|
||||||
}?.also { _ ->
|
} else {
|
||||||
_onDataCleared.emit(it.key)
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.forEach {
|
||||||
|
it.second.onEach { v ->
|
||||||
|
_onValueRemoved.emit(it.first to v)
|
||||||
|
}.also { _ ->
|
||||||
|
_onDataCleared.emit(it.first)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
|
||||||
|
fun <T> MapsReposDefaultMutableSharedFlow() = MutableSharedFlow<T>(
|
||||||
|
extraBufferCapacity = Int.MAX_VALUE
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user