From 24ed3cfd332cc07332357e120e0c2924b50547af Mon Sep 17 00:00:00 2001 From: Amr Yousef Date: Tue, 1 Oct 2024 17:23:36 +0100 Subject: [PATCH 1/4] Add test case Signed-off-by: Amr Yousef --- .../store5/StoreWithInMemoryCacheTests.kt | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) 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 b50ec9703..ba3a03289 100644 --- a/store/src/commonTest/kotlin/org/mobilenativefoundation/store/store5/StoreWithInMemoryCacheTests.kt +++ b/store/src/commonTest/kotlin/org/mobilenativefoundation/store/store5/StoreWithInMemoryCacheTests.kt @@ -1,12 +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.test.TestScope import kotlinx.coroutines.test.runTest 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 @FlowPreview @@ -39,4 +48,87 @@ class StoreWithInMemoryCacheTests { assertEquals("result", c) assertEquals("result", d) } + + @Test + fun storeDeadlock() = + testScope.runTest { + repeat(1000) { + val store = + 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 -> + + } + ) + ) + .disableCache() + .toMutableStoreBuilder( + converter = object : Converter { + override fun fromNetworkToLocal(network: String): String { + return network + } + + override fun fromOutputToLocal(output: String): String { + return 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("") + } + } + + override val onCompletion: OnUpdaterCompletion? + get() = null + + } + ) + + val jobs = mutableListOf() + jobs.add( + store.stream(StoreReadRequest.cached(1, refresh = true)) + .mapNotNull { it.dataOrNull() } + .launchIn(CoroutineScope(Dispatchers.Default)) + ) + val job1 = store.stream(StoreReadRequest.cached(0, refresh = true)) + .mapNotNull { it.dataOrNull() } + .launchIn(CoroutineScope(Dispatchers.Default)) + jobs.add( + store.stream(StoreReadRequest.cached(2, refresh = true)) + .mapNotNull { it.dataOrNull() } + .launchIn(CoroutineScope(Dispatchers.Default))) + jobs.add( + store.stream(StoreReadRequest.cached(3, refresh = true)) + .mapNotNull { it.dataOrNull() } + .launchIn(CoroutineScope(Dispatchers.Default))) + job1.cancel() + assertEquals( + expected = "source_of_truth_0", + 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() + ) + } + } + } } From 8510cc1ce875ca7aaad4c56886774a5347bb9fa7 Mon Sep 17 00:00:00 2001 From: Amr Yousef Date: Tue, 1 Oct 2024 17:41:40 +0100 Subject: [PATCH 2/4] Always Release storeLock Signed-off-by: Amr Yousef --- .../store/store5/impl/RealMutableStore.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 f5dbb54f9..84fd02948 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 @@ -203,10 +203,13 @@ internal class RealMutableStore Output, ): Output { storeLock.lock() - val threadSafety = requireNotNull(keyToThreadSafety[key]) - val output = threadSafety.block() - storeLock.unlock() - return output + try { + val threadSafety = requireNotNull(keyToThreadSafety[key]) + val output = threadSafety.block() + return output + } finally { + storeLock.unlock() + } } private suspend fun conflictsMightExist(key: Key): Boolean { From 76f34d48973f8b810bf247219a933d9ff0d686a7 Mon Sep 17 00:00:00 2001 From: Scott Olcott Date: Thu, 19 Sep 2024 06:44:43 -0600 Subject: [PATCH 3/4] Update kermit to 2.0.4 (#655) Fixes #653 and #654 Signed-off-by: Scott Olcott Signed-off-by: Amr Yousef --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ed3d58e54..9e3cb52b7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,7 @@ spotlessPluginGradle = "6.4.1" junit = "4.13.2" kotlinxCoroutines = "1.8.0" kotlinxSerialization = "1.5.1" -kermit = "1.2.2" +kermit = "2.0.4" testCore = "1.5.0" kmmBridge = "0.3.2" ktlint = "0.39.0" From 5562960276c810ef946dae50eb89dfc07ad134ea Mon Sep 17 00:00:00 2001 From: Amr Yousef Date: Tue, 1 Oct 2024 17:48:38 +0100 Subject: [PATCH 4/4] Revert "Update kermit to 2.0.4 (#655)" This reverts commit 76f34d48973f8b810bf247219a933d9ff0d686a7. Signed-off-by: Amr Yousef --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9e3cb52b7..ed3d58e54 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,7 @@ spotlessPluginGradle = "6.4.1" junit = "4.13.2" kotlinxCoroutines = "1.8.0" kotlinxSerialization = "1.5.1" -kermit = "2.0.4" +kermit = "1.2.2" testCore = "1.5.0" kmmBridge = "0.3.2" ktlint = "0.39.0"