From fe07b69e045e1d9dcb59b76a78cf9e3b6c65b3ee Mon Sep 17 00:00:00 2001 From: MohamadJaara Date: Fri, 1 Dec 2023 23:40:27 +0100 Subject: [PATCH 1/4] feat: implement computeIfAbsent for ConcurrentMutableMap --- .../collections/ConcurrentMutableMap.kt | 20 +++++++++++++++++++ .../collections/ConcurrentMutableMapTest.kt | 13 ++++++++++++ 2 files changed, 33 insertions(+) diff --git a/stately-concurrent-collections/src/commonMain/kotlin/co/touchlab/stately/collections/ConcurrentMutableMap.kt b/stately-concurrent-collections/src/commonMain/kotlin/co/touchlab/stately/collections/ConcurrentMutableMap.kt index d12af6c..997549f 100644 --- a/stately-concurrent-collections/src/commonMain/kotlin/co/touchlab/stately/collections/ConcurrentMutableMap.kt +++ b/stately-concurrent-collections/src/commonMain/kotlin/co/touchlab/stately/collections/ConcurrentMutableMap.kt @@ -2,11 +2,13 @@ package co.touchlab.stately.collections import co.touchlab.stately.concurrency.Synchronizable import co.touchlab.stately.concurrency.synchronize +import kotlin.jvm.JvmName class ConcurrentMutableMap internal constructor( rootArg: Synchronizable? = null, private val del: MutableMap ) : Synchronizable(), MutableMap { + constructor() : this(null, mutableMapOf()) private val syncTarget: Synchronizable = rootArg ?: this @@ -28,6 +30,24 @@ class ConcurrentMutableMap internal constructor( syncTarget.synchronize { del.clear() } } + /** + * If the specified key is not already associated with a value + * attempts to compute its value using the given mapping function and enters it into this map + */ + @JvmName("safeComputeIfAbsent") + fun computeIfAbsent(key: K, defaultValue: (K) -> V): V { + return syncTarget.synchronize { + val value = del[key] + if (value == null) { + val newValue = defaultValue(key) + del[key] = newValue + newValue + } else { + value + } + } + } + override fun put(key: K, value: V): V? = syncTarget.synchronize { del.put(key, value) } override fun putAll(from: Map) { syncTarget.synchronize { del.putAll(from) } diff --git a/stately-concurrent-collections/src/commonTest/kotlin/co/touchlab/stately/collections/ConcurrentMutableMapTest.kt b/stately-concurrent-collections/src/commonTest/kotlin/co/touchlab/stately/collections/ConcurrentMutableMapTest.kt index b0bfc76..3cee886 100644 --- a/stately-concurrent-collections/src/commonTest/kotlin/co/touchlab/stately/collections/ConcurrentMutableMapTest.kt +++ b/stately-concurrent-collections/src/commonTest/kotlin/co/touchlab/stately/collections/ConcurrentMutableMapTest.kt @@ -68,4 +68,17 @@ class ConcurrentMutableMapTest { assertEquals(map.size, DEFAULT_RUNS * 2) } + + @Test + @NoJsTest + fun computeIfAbsent() { + val map = ConcurrentMutableMap() + + runAlot(100) { run -> + map.computeIfAbsent("key") { SomeData("value $run") } + } + + assertEquals(map.size, 1) + assertEquals(map["key"]?.s, "value 0") + } } \ No newline at end of file From 60bb9f2dde18add6600f99719eb6f7a62d9ebaf3 Mon Sep 17 00:00:00 2001 From: Kevin Galligan Date: Sun, 3 Dec 2023 08:17:44 -0500 Subject: [PATCH 2/4] Tweaked test --- .../collections/ConcurrentMutableMapTest.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/stately-concurrent-collections/src/commonTest/kotlin/co/touchlab/stately/collections/ConcurrentMutableMapTest.kt b/stately-concurrent-collections/src/commonTest/kotlin/co/touchlab/stately/collections/ConcurrentMutableMapTest.kt index 3cee886..2aaec70 100644 --- a/stately-concurrent-collections/src/commonTest/kotlin/co/touchlab/stately/collections/ConcurrentMutableMapTest.kt +++ b/stately-concurrent-collections/src/commonTest/kotlin/co/touchlab/stately/collections/ConcurrentMutableMapTest.kt @@ -1,5 +1,8 @@ package co.touchlab.stately.collections +import co.touchlab.stately.concurrency.AtomicInt +import co.touchlab.testhelp.concurrency.sleep +import kotlinx.coroutines.delay import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -73,12 +76,16 @@ class ConcurrentMutableMapTest { @NoJsTest fun computeIfAbsent() { val map = ConcurrentMutableMap() + val count = AtomicInt(0) - runAlot(100) { run -> - map.computeIfAbsent("key") { SomeData("value $run") } + runAlot(1) { run -> + map.computeIfAbsent("key") { + sleep(1000) + count.incrementAndGet() + SomeData("value $run") + } } - assertEquals(map.size, 1) - assertEquals(map["key"]?.s, "value 0") + assertEquals(count.get(), 1) } } \ No newline at end of file From 66e6dc67414cfd6d3a31425325fd073e4d90202c Mon Sep 17 00:00:00 2001 From: Kevin Galligan Date: Sun, 3 Dec 2023 08:28:38 -0500 Subject: [PATCH 3/4] Run build --- .github/workflows/build.yml | 49 +++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..b542fe9 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,49 @@ +name: deploy +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + +jobs: + deploy: + runs-on: macos-latest + steps: + - name: Checkout the repo + uses: actions/checkout@v3 + + - uses: actions/setup-java@v2 + with: + distribution: "adopt" + java-version: "17" + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v1 + - name: Cache gradle + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Cache konan + uses: actions/cache@v2 + with: + path: ~/.konan + key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Publish Mac/Windows Artifacts + run: ./gradlew build --no-daemon --stacktrace --no-build-cache + env: + ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }} + ORG_GRADLE_PROJECT_SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} + ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} + +env: + GRADLE_OPTS: -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx3g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=512m" From 6a3c7e8aec1d35f619c0f7e3809cdc7fc6e8c4cd Mon Sep 17 00:00:00 2001 From: Kevin Galligan Date: Sun, 3 Dec 2023 08:55:26 -0500 Subject: [PATCH 4/4] Dump new api --- .../api/stately-concurrent-collections.api | 1 + 1 file changed, 1 insertion(+) diff --git a/stately-concurrent-collections/api/stately-concurrent-collections.api b/stately-concurrent-collections/api/stately-concurrent-collections.api index cfe0326..22277eb 100644 --- a/stately-concurrent-collections/api/stately-concurrent-collections.api +++ b/stately-concurrent-collections/api/stately-concurrent-collections.api @@ -49,6 +49,7 @@ public final class co/touchlab/stately/collections/ConcurrentMutableMap : java/u public fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; public fun putAll (Ljava/util/Map;)V public fun remove (Ljava/lang/Object;)Ljava/lang/Object; + public final fun safeComputeIfAbsent (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public final fun size ()I public final fun values ()Ljava/util/Collection; }