From 9ccf1e9f762e0ea93d8d2edbc2ea4130ae1f4a3f Mon Sep 17 00:00:00 2001 From: nphausg Date: Sun, 9 Feb 2025 09:42:41 +0800 Subject: [PATCH 1/2] eng: update a comment for better clarity about why the start = CoroutineStart.LAZY is used for better clarity about why the start = CoroutineStart.LAZY is used. --- .../kotlin/org/jetbrains/compose/resources/AsyncCache.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/AsyncCache.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/AsyncCache.kt index 60ac2c4f0a2..60885f52dc5 100644 --- a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/AsyncCache.kt +++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/AsyncCache.kt @@ -15,7 +15,7 @@ internal class AsyncCache { val deferred = mutex.withLock { var cached = cache[key] if (cached == null || cached.isCancelled) { - //LAZY - to free the mutex lock as fast as possible + // LAZY - to release the mutex as quickly as possible and defer the work cached = async(start = CoroutineStart.LAZY) { load() } cache[key] = cached } From 2831cd039a91d88f41c8382fcfc700f2efa5a859 Mon Sep 17 00:00:00 2001 From: nphausg Date: Sun, 9 Feb 2025 11:16:57 +0800 Subject: [PATCH 2/2] eng: update a comment for better clarity about why the start = CoroutineStart.LAZY is used --- .../compose/resources/AsyncCacheTest.kt | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/AsyncCacheTest.kt diff --git a/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/AsyncCacheTest.kt b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/AsyncCacheTest.kt new file mode 100644 index 00000000000..71f28cd02c8 --- /dev/null +++ b/components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/AsyncCacheTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2020-2024 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.compose.resources + +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.runTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class AsyncCacheTest { + + private lateinit var cache: AsyncCache + + @BeforeTest + fun setup() { + cache = AsyncCache() + } + + @Test + fun `test cache stores and retrieves value`() = runTest { + val key = "testKey" + val expectedValue = "Hello, World!" + + val value = cache.getOrLoad(key) { expectedValue } + + assertEquals(expectedValue, value) + } + + @Test + fun `test cache returns same instance for same key`() = runTest { + val key = "testKey" + var loadCount = 0 + + val firstLoad = cache.getOrLoad(key) { + loadCount++ + "Hello" + } + val secondLoad = cache.getOrLoad(key) { "NewValue" } + + assertEquals("Hello", firstLoad) + assertEquals("Hello", secondLoad) + assertEquals(1, loadCount) // Ensures the load function runs only once + } + + @Test + fun `test concurrent access to cache`() = runTest { + val key = "testKey" + var loadCount = 0 + + coroutineScope { + repeat(10) { + launch { + cache.getOrLoad(key) { + delay(100) // Simulate work + loadCount++ + "Concurrent Value" + } + } + } + } + + assertEquals(1, loadCount) // Ensures only one load operation happened + } + + @Test + fun `test cache invalidation on clear`() = runTest { + val key = "testKey" + + cache.getOrLoad(key) { "InitialValue" } + cache.clear() + + val newValue = cache.getOrLoad(key) { "NewValue" } + + assertEquals("NewValue", newValue) + } +} \ No newline at end of file