Merge pull request #304 from InsanusMokrassar/0.20.3

0.20.3
This commit is contained in:
InsanusMokrassar 2023-09-02 03:03:57 +06:00 committed by GitHub
commit cd6c4bbe38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 294 additions and 91 deletions

View File

@ -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`

View File

@ -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)
}
}
} }
} }

View File

@ -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

View File

@ -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)
}
}
} }

View File

@ -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

View File

@ -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"

View File

@ -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)

View File

@ -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() {

View File

@ -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 } }

View File

@ -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 {

View File

@ -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) }
} }
} }

View File

@ -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>) {

View File

@ -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)
}
} }
} }
} }

View File

@ -0,0 +1,7 @@
package dev.inmo.micro_utils.repos
import kotlinx.coroutines.flow.MutableSharedFlow
fun <T> MapsReposDefaultMutableSharedFlow() = MutableSharedFlow<T>(
extraBufferCapacity = Int.MAX_VALUE
)