diff --git a/.idea/libraries/jetbrains_kotlinx_lincheck_jvm.xml b/.idea/libraries/jetbrains_kotlinx_lincheck_jvm.xml new file mode 100644 index 000000000000..b7ad1a2358da --- /dev/null +++ b/.idea/libraries/jetbrains_kotlinx_lincheck_jvm.xml @@ -0,0 +1,86 @@ + + + + + + 2dd9c390e2de0acbdd2fce1aabadd378fa75a9cf73ee723dd8cecbe5c8480457 + + + a133e049f0a4e249651582428e166de4dfac9546adf436b6172119255ede510f + + + 1ab3acc38f3e7355c4f9d1ec62107a46fa73c899f3070d055e5d4373dfe67e12 + + + 7aefd0d5c0901701c69f7513feda765fb6be33af2ce7aa17c5781fc87657c511 + + + 3c6fac2424db3d4a853b669f4e3d1d9c3c552235e19a319673f887083c2303a1 + + + c43ecf17b539c777e15da7b5b86553b377e2d39a683de6285567d5283888e7ef + + + c635a7402f4aa9bf66b2f4230cea62025a0fe1cd63e8729adefc9b1994fac4c3 + + + d92832d7c37edc07c60e2559ac6118b31d642e337a6671edcb7ba9fae68edbbb + + + 970636134d61c183b19f8f58fa631e30d2f2abca344b37848a393cac7863dd70 + + + 2b309a9300092e0b696f7c471fd51d9969001df784c8ab9f07997437d757ad6d + + + b3d9ec0298e84ed09e0448c52a643bfdad7f3d8096f1303592a3046e711551af + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/util/api-dump-unreviewed.txt b/platform/util/api-dump-unreviewed.txt index dd7928eccf1b..ee933f3cb652 100644 --- a/platform/util/api-dump-unreviewed.txt +++ b/platform/util/api-dump-unreviewed.txt @@ -1101,8 +1101,14 @@ com.intellij.openapi.util.UserDataHolderEx - a:putUserDataIfAbsent(com.intellij.openapi.util.Key,java.lang.Object):java.lang.Object - a:replace(com.intellij.openapi.util.Key,java.lang.Object,java.lang.Object):Z f:com.intellij.openapi.util.UserDataHolderExKt +- sf:getAndUpdateUserData(com.intellij.openapi.util.UserDataHolderEx,com.intellij.openapi.util.Key,kotlin.jvm.functions.Function1):java.lang.Object - sf:getOrCreateUserData(com.intellij.openapi.util.UserDataHolder,com.intellij.openapi.util.Key,kotlin.jvm.functions.Function0):java.lang.Object +- sf:getOrCreateUserData(com.intellij.openapi.util.UserDataHolderEx,com.intellij.openapi.util.Key,kotlin.jvm.functions.Function0):java.lang.Object +- sf:getOrCreateUserDataUnsafe(com.intellij.openapi.util.UserDataHolder,com.intellij.openapi.util.Key,kotlin.jvm.functions.Function0):java.lang.Object +- sf:getOrMaybeCreateUserData(com.intellij.openapi.util.UserDataHolderEx,com.intellij.openapi.util.Key,kotlin.jvm.functions.Function0):java.lang.Object - sf:nullableLazyValue(com.intellij.openapi.util.UserDataHolder,com.intellij.openapi.util.Key,kotlin.jvm.functions.Function0):java.lang.Object +- sf:nullableLazyValueUnsafe(com.intellij.openapi.util.UserDataHolder,com.intellij.openapi.util.Key,kotlin.jvm.functions.Function0):java.lang.Object +- sf:updateUserData(com.intellij.openapi.util.UserDataHolderEx,com.intellij.openapi.util.Key,kotlin.jvm.functions.Function1):java.lang.Object com.intellij.openapi.util.ValueKey - sf:Companion:com.intellij.openapi.util.ValueKey$Companion - a:getName():java.lang.String diff --git a/platform/util/src/com/intellij/openapi/util/UserDataHolderEx.kt b/platform/util/src/com/intellij/openapi/util/UserDataHolderEx.kt index a636162f57ec..5e34612767bd 100644 --- a/platform/util/src/com/intellij/openapi/util/UserDataHolderEx.kt +++ b/platform/util/src/com/intellij/openapi/util/UserDataHolderEx.kt @@ -3,7 +3,68 @@ package com.intellij.openapi.util import com.intellij.util.ObjectUtils -inline fun UserDataHolder.getOrCreateUserData(key: Key, producer: () -> T): T { +/** + * Get or create the user-data under the current key. + * This operation is atomic as it relies on the atomic [UserDataHolderEx.putUserDataIfAbsent] + */ +inline fun UserDataHolderEx.getOrCreateUserData(key: Key, producer: () -> T): T { + getUserData(key)?.let { return it } + return putUserDataIfAbsent(key, producer()) +} + +/** + * Get or create the user-data under the current key. + * @param producer Tries to create the value of returns `null` if the value cannot be created. + * This operation is atomic as it relies on the atomic [UserDataHolderEx.putUserDataIfAbsent] + */ +inline fun UserDataHolderEx.getOrMaybeCreateUserData(key: Key, producer: () -> T?): T? { + getUserData(key)?.let { return it } + val newValueOrNull = producer() ?: return null + return putUserDataIfAbsent(key, newValueOrNull) +} + +/** + * Update the current user data based upon the current value. + * Note: This method is considered to be thread safe as it relies on the atomic [UserDataHolderEx.replace]. + * Note: The [update] function could be called multiple times when many threads are trying to update the value at the same time. + * @return The updated value (null if the update function returned null) + */ +inline fun UserDataHolderEx.updateUserData(key: Key, update: (T?) -> T?): T? { + while (true) { + val existing = getUserData(key) + val newValue = update(existing) + if (replace(key, existing, newValue)) { + return newValue + } + } +} + +/** + * Update the current user data based upon the current value. + * Note: This method is considered to be thread safe as it relies on the atomic [UserDataHolderEx.replace]. + * Note: The [update] function could be called multiple times when many threads are trying to update the value at the same time. + * @return The previous value, which got replaced by the update + */ +inline fun UserDataHolderEx.getAndUpdateUserData(key: Key, update: (T?) -> T?): T? { + while (true) { + val existing = getUserData(key) + val newValue = update(existing) + if (replace(key, existing, newValue)) { + return existing + } + } +} + + +/* +UNSAFE / DEPRECATED APIs + */ + +/** + * Warning: This method is not thread-safe: Use [UserDataHolderEx] based APIs instead + * See: [UserDataHolderEx.getOrCreateUserData] + */ +inline fun UserDataHolder.getOrCreateUserDataUnsafe(key: Key, producer: () -> T): T { val existing = getUserData(key) if (existing != null) return existing @@ -12,7 +73,19 @@ inline fun UserDataHolder.getOrCreateUserData(key: Key, producer: () -> T return value } -inline fun UserDataHolder.nullableLazyValue(key: Key, producer: () -> T?): T? { +/** + * Warning: This method is not thread-safe: Use [UserDataHolderEx] based APIs instead + * See: [UserDataHolderEx.getOrCreateUserData] + */ +@Deprecated("Use 'UserDataHolderEx' APIs instead", replaceWith = ReplaceWith("getOrCreateUserDataUnsafe(key, producer)")) +inline fun UserDataHolder.getOrCreateUserData(key: Key, producer: () -> T): T { + return getOrCreateUserDataUnsafe(key, producer) +} + +/** + * Note: This method is not thread safe + */ +inline fun UserDataHolder.nullableLazyValueUnsafe(key: Key, producer: () -> T?): T? { val existing = getUserData(key) if (existing == ObjectUtils.NULL) return null if (existing != null) return existing @@ -22,3 +95,12 @@ inline fun UserDataHolder.nullableLazyValue(key: Key, producer: () -> T?) putUserData(key, value ?: ObjectUtils.NULL as T) return value } + + +/** + * Warning: This method is not thread-safe: Use [UserDataHolderEx] based APIs instead + */ +@Deprecated("Use 'UserDataHolderEx' APIs instead", replaceWith = ReplaceWith("nullableLazyValueUnsafe(key, producer)")) +inline fun UserDataHolder.nullableLazyValue(key: Key, producer: () -> T?): T? { + return nullableLazyValueUnsafe(key, producer) +} diff --git a/platform/util/testSrc/com/intellij/openapi/util/UserDataHolderExLincheck.kt b/platform/util/testSrc/com/intellij/openapi/util/UserDataHolderExLincheck.kt new file mode 100644 index 000000000000..f663e6d76a18 --- /dev/null +++ b/platform/util/testSrc/com/intellij/openapi/util/UserDataHolderExLincheck.kt @@ -0,0 +1,51 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.openapi.util + +import org.jetbrains.kotlinx.lincheck.annotations.Operation +import org.jetbrains.kotlinx.lincheck.check +import org.jetbrains.kotlinx.lincheck.strategy.stress.StressOptions +import org.junit.jupiter.api.Test + +@Suppress("unused") +class UserDataHolderExLincheck { + + companion object { + private val keyA = Key("a") + private val keyB = Key("b") + } + + val holder = UserDataHolderBase() + + @Operation + fun getOrCreateA(value: Int) = holder.getOrCreateUserData(keyA) { value } + + @Operation + fun setKeyA(value: Int?) = holder.putUserData(keyA, value) + + @Operation + fun updateKeyA(value: Int) = holder.updateUserData(keyA) { value } + + @Operation + fun getAndUpdateKeyA(value: Int) = holder.getAndUpdateUserData(keyA) { value } + + @Operation + fun incrementKeyA(value: Int) = holder.updateUserData(keyA) { (it ?: 0) + value } + + @Operation + fun getOrCreateB(value: Int) = holder.getOrCreateUserData(keyB) { value } + + @Operation + fun setKeyB(value: Int?) = holder.putUserData(keyB, value) + + @Operation + fun updateKeyB(value: Int) = holder.updateUserData(keyB) { value } + + @Operation + fun getAndUpdateKeyB(value: Int) = holder.getAndUpdateUserData(keyB) { value } + + @Operation + fun incrementKeyB(value: Int) = holder.updateUserData(keyB) { (it ?: 0) + value } + + @Test + fun stressTest() = StressOptions().check(this::class) +} diff --git a/platform/util/testSrc/com/intellij/openapi/util/UserDataHolderExTest.kt b/platform/util/testSrc/com/intellij/openapi/util/UserDataHolderExTest.kt new file mode 100644 index 000000000000..8e9c6b4b9205 --- /dev/null +++ b/platform/util/testSrc/com/intellij/openapi/util/UserDataHolderExTest.kt @@ -0,0 +1,58 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.openapi.util + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class UserDataHolderExTest { + + @Test + fun getOrCreateUserData() { + val key = Key("a") + val userData = UserDataHolderBase() + assertEquals(42, userData.getOrCreateUserData(key) { 42 }) + assertEquals(42, userData.getOrCreateUserData(key) { 239 }) + + userData.putUserData(key, null) + assertEquals(2411, userData.getOrCreateUserData(key, { 2411 })) + assertEquals(2411, userData.getUserData(key)) + } + + @Test + fun getOrCreateUserDataNullable() { + val key = Key("a") + val userData = UserDataHolderBase() + assertEquals(null, userData.getOrMaybeCreateUserData(key) { null }) + assertEquals(42, userData.getOrCreateUserData(key) { 42 }) + assertEquals(42, userData.getOrMaybeCreateUserData(key) { null }) + } + + @Test + fun updateUserData() { + val key = Key("a") + val userData = UserDataHolderBase() + userData.updateUserData(key) { 1 } + assertEquals(1, userData.getUserData(key)) + + assertEquals(2, userData.updateUserData(key) { value -> + (value ?: 0).plus(1) + }) + + assertEquals(null, userData.updateUserData(key) { null }) + assertEquals(null, userData.getUserData(key)) + } + + @Test + fun getAndUpdateUserData() { + val key = Key("a") + val userData = UserDataHolderBase() + assertEquals(null, userData.getAndUpdateUserData(key) { 1 }) + assertEquals(1, userData.getAndUpdateUserData(key) { 2 }) + assertEquals(2, userData.getUserData(key)) + assertEquals(2, userData.getAndUpdateUserData(key) { it!! + 1 }) + assertEquals(3, userData.getUserData(key)) + assertEquals(3, userData.getAndUpdateUserData(key) { null }) + assertEquals(null, userData.getUserData(key)) + } +} + diff --git a/platform/util/testSrc/intellij.platform.util.tests.iml b/platform/util/testSrc/intellij.platform.util.tests.iml index 76745e2b729e..3adb202a55bd 100644 --- a/platform/util/testSrc/intellij.platform.util.tests.iml +++ b/platform/util/testSrc/intellij.platform.util.tests.iml @@ -35,5 +35,6 @@ + \ No newline at end of file