diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9e3cb52b..2a973b84 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ androidCompileSdk = "33" androidGradlePlugin = "7.4.2" androidTargetSdk = "33" atomicFu = "0.24.0" -baseKotlin = "2.0.0" +baseKotlin = "2.0.20" dokkaGradlePlugin = "1.9.10" ktlintGradle = "12.1.0" jacocoGradlePlugin = "0.8.7" @@ -14,10 +14,10 @@ pagingCompose = "3.3.0-alpha02" pagingRuntime = "3.2.1" spotlessPluginGradle = "6.4.1" junit = "4.13.2" -kotlinxCoroutines = "1.8.0" -kotlinxSerialization = "1.5.1" +kotlinxCoroutines = "1.8.1" +kotlinxSerialization = "1.6.3" kermit = "2.0.4" -testCore = "1.5.0" +testCore = "1.6.1" kmmBridge = "0.3.2" ktlint = "0.39.0" kover = "0.6.0" diff --git a/paging/kover/coverage.xml b/paging/kover/coverage.xml index 13254ba4..a2384593 100644 --- a/paging/kover/coverage.xml +++ b/paging/kover/coverage.xml @@ -2,20 +2,15 @@ - - - - - - + - - + + @@ -278,10 +273,9 @@ - - + - + @@ -449,10 +443,10 @@ - + - - + + @@ -1066,10 +1060,10 @@ - + - - + + diff --git a/store/src/commonMain/kotlin/org/mobilenativefoundation/store/store5/impl/RealMutableStore.kt b/store/src/commonMain/kotlin/org/mobilenativefoundation/store/store5/impl/RealMutableStore.kt index 84fd0294..d798d57f 100644 --- a/store/src/commonMain/kotlin/org/mobilenativefoundation/store/store5/impl/RealMutableStore.kt +++ b/store/src/commonMain/kotlin/org/mobilenativefoundation/store/store5/impl/RealMutableStore.kt @@ -201,16 +201,11 @@ internal class RealMutableStore withThreadSafety( key: Key, block: suspend ThreadSafety.() -> Output, - ): Output { - storeLock.lock() - try { + ): Output = + storeLock.withLock { val threadSafety = requireNotNull(keyToThreadSafety[key]) - val output = threadSafety.block() - return output - } finally { - storeLock.unlock() + threadSafety.block() } - } private suspend fun conflictsMightExist(key: Key): Boolean { val lastFailedSync = bookkeeper?.getLastFailedSync(key) diff --git a/store/src/commonTest/kotlin/org/mobilenativefoundation/store/store5/StoreWithInMemoryCacheTests.kt b/store/src/commonTest/kotlin/org/mobilenativefoundation/store/store5/StoreWithInMemoryCacheTests.kt index ba3a0328..119ccd3a 100644 --- a/store/src/commonTest/kotlin/org/mobilenativefoundation/store/store5/StoreWithInMemoryCacheTests.kt +++ b/store/src/commonTest/kotlin/org/mobilenativefoundation/store/store5/StoreWithInMemoryCacheTests.kt @@ -1,23 +1,21 @@ package org.mobilenativefoundation.store.store5 -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.Job -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest +import org.mobilenativefoundation.store.core5.ExperimentalStoreApi import org.mobilenativefoundation.store.store5.impl.extensions.get import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertIs -import kotlin.test.assertNotNull import kotlin.time.Duration.Companion.hours +@OptIn(ExperimentalStoreApi::class) @FlowPreview @ExperimentalCoroutinesApi class StoreWithInMemoryCacheTests { @@ -51,82 +49,86 @@ class StoreWithInMemoryCacheTests { @Test fun storeDeadlock() = - testScope.runTest { - repeat(1000) { - val store = + runTest { + repeat(100) { + val store: MutableStore = StoreBuilder .from( - fetcher = Fetcher.of { key: Int -> "fetcher_${key}" }, - sourceOfTruth = SourceOfTruth.Companion.of( - reader = { key -> - flow { - emit("source_of_truth_${key}") - } - }, - writer = { key: Int, local: String -> - - } - ) + fetcher = Fetcher.of { key: Int -> "fetcher_$key" }, + sourceOfTruth = + SourceOfTruth.of( + reader = { key: Int -> + flowOf("source_of_truth_$key") + }, + writer = { key: Int, local: String -> }, + ), ) .disableCache() .toMutableStoreBuilder( - converter = object : Converter { - override fun fromNetworkToLocal(network: String): String { - return network - } + converter = + object : Converter { + override fun fromNetworkToLocal(network: String): String = network - override fun fromOutputToLocal(output: String): String { - return output - } - }, + override fun fromOutputToLocal(output: String): String = output + }, ) .build( - updater = object : Updater { - var callCount = -1 - override suspend fun post(key: Int, value: String): UpdaterResult { - callCount += 1 - if (callCount % 2 == 0) { - throw IllegalArgumentException(key.toString() + "value:$value") - } else { - return UpdaterResult.Success.Untyped("") - } - } + updater = + object : Updater { + var callCount = -1 - override val onCompletion: OnUpdaterCompletion? - get() = null + override suspend fun post( + key: Int, + value: String, + ): UpdaterResult { + callCount += 1 + return if (callCount % 2 == 0) { + throw IllegalArgumentException("$key value: $value") + } else { + UpdaterResult.Success.Untyped("") + } + } - } + override val onCompletion: OnUpdaterCompletion? = null + }, ) val jobs = mutableListOf() jobs.add( store.stream(StoreReadRequest.cached(1, refresh = true)) .mapNotNull { it.dataOrNull() } - .launchIn(CoroutineScope(Dispatchers.Default)) + .launchIn(this), ) - val job1 = store.stream(StoreReadRequest.cached(0, refresh = true)) - .mapNotNull { it.dataOrNull() } - .launchIn(CoroutineScope(Dispatchers.Default)) + val job1 = + store.stream(StoreReadRequest.cached(0, refresh = true)) + .mapNotNull { it.dataOrNull() } + .launchIn(this) jobs.add( store.stream(StoreReadRequest.cached(2, refresh = true)) .mapNotNull { it.dataOrNull() } - .launchIn(CoroutineScope(Dispatchers.Default))) + .launchIn(this), + ) jobs.add( store.stream(StoreReadRequest.cached(3, refresh = true)) .mapNotNull { it.dataOrNull() } - .launchIn(CoroutineScope(Dispatchers.Default))) + .launchIn(this), + ) job1.cancel() assertEquals( expected = "source_of_truth_0", - actual = store.stream(StoreReadRequest.cached(0, refresh = true)) - .mapNotNull { it.dataOrNull() }.first() + actual = + store.stream(StoreReadRequest.cached(0, refresh = true)) + .mapNotNull { it.dataOrNull() } + .first(), ) jobs.forEach { it.cancel() assertEquals( expected = "source_of_truth_0", - actual = store.stream(StoreReadRequest.cached(0, refresh = true)) - .mapNotNull { it.dataOrNull() }.first() + actual = + store.stream(StoreReadRequest.cached(0, refresh = true)) + .mapNotNull { it.dataOrNull() } + .first(), ) } }