IJPL-161739: Add safe extensions to UserDataHolderEx.kt and deprecate/mark unsafe APIs

GitOrigin-RevId: 0bf8dc95df07d5a9bdc21ac5715c3ee8cd15feed
This commit is contained in:
Sebastian Sellmair
2024-09-06 09:40:56 +02:00
committed by intellij-monorepo-bot
parent df4b25fc79
commit 0629629994
6 changed files with 286 additions and 2 deletions

View File

@@ -0,0 +1,86 @@
<component name="libraryTable">
<library name="jetbrains.kotlinx.lincheck.jvm" type="repository">
<properties maven-id="org.jetbrains.kotlinx:lincheck-jvm:2.33">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/lincheck-jvm/2.33/lincheck-jvm-2.33.jar">
<sha256sum>2dd9c390e2de0acbdd2fce1aabadd378fa75a9cf73ee723dd8cecbe5c8480457</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.21/kotlin-reflect-1.9.21.jar">
<sha256sum>a133e049f0a4e249651582428e166de4dfac9546adf436b6172119255ede510f</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.3/kotlinx-coroutines-core-jvm-1.7.3.jar">
<sha256sum>1ab3acc38f3e7355c4f9d1ec62107a46fa73c899f3070d055e5d4373dfe67e12</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.6/asm-commons-9.6.jar">
<sha256sum>7aefd0d5c0901701c69f7513feda765fb6be33af2ce7aa17c5781fc87657c511</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.6/asm-9.6.jar">
<sha256sum>3c6fac2424db3d4a853b669f4e3d1d9c3c552235e19a319673f887083c2303a1</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.6/asm-tree-9.6.jar">
<sha256sum>c43ecf17b539c777e15da7b5b86553b377e2d39a683de6285567d5283888e7ef</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.6/asm-util-9.6.jar">
<sha256sum>c635a7402f4aa9bf66b2f4230cea62025a0fe1cd63e8729adefc9b1994fac4c3</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.6/asm-analysis-9.6.jar">
<sha256sum>d92832d7c37edc07c60e2559ac6118b31d642e337a6671edcb7ba9fae68edbbb</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.14.12/byte-buddy-1.14.12.jar">
<sha256sum>970636134d61c183b19f8f58fa631e30d2f2abca344b37848a393cac7863dd70</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.14.12/byte-buddy-agent-1.14.12.jar">
<sha256sum>2b309a9300092e0b696f7c471fd51d9969001df784c8ab9f07997437d757ad6d</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/atomicfu-jvm/0.20.2/atomicfu-jvm-0.20.2.jar">
<sha256sum>b3d9ec0298e84ed09e0448c52a643bfdad7f3d8096f1303592a3046e711551af</sha256sum>
</artifact>
</verification>
<exclude>
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib" />
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-common" />
<dependency maven-id="org.jetbrains:annotations" />
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8" />
</exclude>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/lincheck-jvm/2.33/lincheck-jvm-2.33.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.21/kotlin-reflect-1.9.21.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.3/kotlinx-coroutines-core-jvm-1.7.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.6/asm-commons-9.6.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.6/asm-9.6.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.6/asm-tree-9.6.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.6/asm-util-9.6.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.6/asm-analysis-9.6.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.14.12/byte-buddy-1.14.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.14.12/byte-buddy-agent-1.14.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/atomicfu-jvm/0.20.2/atomicfu-jvm-0.20.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/lincheck-jvm/2.33/lincheck-jvm-2.33-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.21/kotlin-reflect-1.9.21-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.3/kotlinx-coroutines-core-jvm-1.7.3-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.6/asm-commons-9.6-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.6/asm-9.6-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.6/asm-tree-9.6-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.6/asm-util-9.6-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.6/asm-analysis-9.6-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.14.12/byte-buddy-1.14.12-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.14.12/byte-buddy-agent-1.14.12-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/atomicfu-jvm/0.20.2/atomicfu-jvm-0.20.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/lincheck-jvm/2.33/lincheck-jvm-2.33-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.21/kotlin-reflect-1.9.21-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.3/kotlinx-coroutines-core-jvm-1.7.3-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.6/asm-commons-9.6-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.6/asm-9.6-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.6/asm-tree-9.6-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.6/asm-util-9.6-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.6/asm-analysis-9.6-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.14.12/byte-buddy-1.14.12-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.14.12/byte-buddy-agent-1.14.12-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/atomicfu-jvm/0.20.2/atomicfu-jvm-0.20.2-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@@ -1101,8 +1101,14 @@ com.intellij.openapi.util.UserDataHolderEx
- a:putUserDataIfAbsent(com.intellij.openapi.util.Key,java.lang.Object):java.lang.Object - 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 - a:replace(com.intellij.openapi.util.Key,java.lang.Object,java.lang.Object):Z
f:com.intellij.openapi.util.UserDataHolderExKt 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.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: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 com.intellij.openapi.util.ValueKey
- sf:Companion:com.intellij.openapi.util.ValueKey$Companion - sf:Companion:com.intellij.openapi.util.ValueKey$Companion
- a:getName():java.lang.String - a:getName():java.lang.String

View File

@@ -3,7 +3,68 @@ package com.intellij.openapi.util
import com.intellij.util.ObjectUtils import com.intellij.util.ObjectUtils
inline fun <T> UserDataHolder.getOrCreateUserData(key: Key<T>, 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 <T : Any> UserDataHolderEx.getOrCreateUserData(key: Key<T>, 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 <T> UserDataHolderEx.getOrMaybeCreateUserData(key: Key<T>, 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 <T : Any> UserDataHolderEx.updateUserData(key: Key<T>, 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 <T : Any> UserDataHolderEx.getAndUpdateUserData(key: Key<T>, 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 <T> UserDataHolder.getOrCreateUserDataUnsafe(key: Key<T>, producer: () -> T): T {
val existing = getUserData(key) val existing = getUserData(key)
if (existing != null) return existing if (existing != null) return existing
@@ -12,7 +73,19 @@ inline fun <T> UserDataHolder.getOrCreateUserData(key: Key<T>, producer: () -> T
return value return value
} }
inline fun <T> UserDataHolder.nullableLazyValue(key: Key<T>, 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 <T> UserDataHolder.getOrCreateUserData(key: Key<T>, producer: () -> T): T {
return getOrCreateUserDataUnsafe(key, producer)
}
/**
* Note: This method is not thread safe
*/
inline fun <T> UserDataHolder.nullableLazyValueUnsafe(key: Key<T>, producer: () -> T?): T? {
val existing = getUserData(key) val existing = getUserData(key)
if (existing == ObjectUtils.NULL) return null if (existing == ObjectUtils.NULL) return null
if (existing != null) return existing if (existing != null) return existing
@@ -22,3 +95,12 @@ inline fun <T> UserDataHolder.nullableLazyValue(key: Key<T>, producer: () -> T?)
putUserData(key, value ?: ObjectUtils.NULL as T) putUserData(key, value ?: ObjectUtils.NULL as T)
return value 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 <T> UserDataHolder.nullableLazyValue(key: Key<T>, producer: () -> T?): T? {
return nullableLazyValueUnsafe(key, producer)
}

View File

@@ -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<Int>("a")
private val keyB = Key<Int>("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)
}

View File

@@ -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<Int>("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<Int>("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<Int>("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<Int>("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))
}
}

View File

@@ -35,5 +35,6 @@
<orderEntry type="library" name="kotlinx-coroutines-debug" level="project" /> <orderEntry type="library" name="kotlinx-coroutines-debug" level="project" />
<orderEntry type="module" module-name="intellij.tools.ide.metrics.benchmark" scope="TEST" /> <orderEntry type="module" module-name="intellij.tools.ide.metrics.benchmark" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.util.coroutines" scope="TEST" /> <orderEntry type="module" module-name="intellij.platform.util.coroutines" scope="TEST" />
<orderEntry type="library" scope="TEST" name="jetbrains.kotlinx.lincheck.jvm" level="project" />
</component> </component>
</module> </module>