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