diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KotlinClassInnerStuffCache.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KotlinClassInnerStuffCache.kt index b737a77d9cc..442e9669d8c 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KotlinClassInnerStuffCache.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KotlinClassInnerStuffCache.kt @@ -24,60 +24,20 @@ import org.jetbrains.kotlin.utils.SmartList import java.util.concurrent.TimeUnit import java.util.concurrent.locks.ReentrantLock -class KotlinClassInnerStuffCache(val myClass: PsiExtensibleClass, externalDependencies: List) { +class KotlinClassInnerStuffCache( + private val myClass: PsiExtensibleClass, + externalDependencies: List, + private val lazyCreator: LazyCreator, +) { private val myTracker = SimpleModificationTracker() private val dependencies: List = externalDependencies + myTracker - fun get(initializer: () -> T) = object : Lazy { - private val lock = ReentrantLock() - private val holder = lazyPub { - PsiCachedValueImpl(PsiManager.getInstance(myClass.project), - CachedValueProvider { - val v = initializer() - CachedValueProvider.Result.create(v, dependencies) - }) - } - - private fun computeValue(): T = holder.value.value ?: error("holder has not null in initializer") - - override val value: T - get() { - return if (holder.value.hasUpToDateValue()) { - computeValue() - } else { - // the idea behind this locking approach: - // Thread T1 starts to calculate value for A it acquires lock for A - // - // Assumption 1: Lets say A calculation requires another value e.g. B to be calculated - // Assumption 2: Thread T2 wants to calculate value for B - - // to avoid dead-lock - // - we mark thread as doing calculation and acquire lock only once per thread - // as a trade-off to prevent dependent value could be calculated several time - // due to CAS (within putUserDataIfAbsent etc) the same instance of calculated value will be used - - // TODO: NOTE: acquire lock for a several seconds to avoid dead-lock via resolve is a WORKAROUND - - if (!initIsRunning.get() && lock.tryLock(5, TimeUnit.SECONDS)) { - try { - initIsRunning.set(true) - try { - computeValue() - } finally { - initIsRunning.set(false) - } - } finally { - lock.unlock() - } - } else { - computeValue() - } - } - } - - override fun isInitialized() = holder.isInitialized() + abstract class LazyCreator { + abstract fun get(initializer: () -> T, dependencies: List): Lazy } + private fun get(initializer: () -> T): Lazy = lazyCreator.get(initializer, dependencies) + private val _getConstructors: Array by get { PsiImplUtil.getConstructors(myClass) } val constructors: Array @@ -235,9 +195,6 @@ class KotlinClassInnerStuffCache(val myClass: PsiExtensibleClass, externalDepend private const val VALUES_METHOD = "values" private const val VALUE_OF_METHOD = "valueOf" - @JvmStatic - private val initIsRunning: ThreadLocal = ThreadLocal.withInitial { false } - // Copy of PsiClassImplUtil.processDeclarationsInEnum for own cache class @JvmStatic fun processDeclarationsInEnum( diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassBase.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassBase.kt index cef1846b358..2bc6b82178b 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassBase.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassBase.kt @@ -31,7 +31,10 @@ import org.jetbrains.kotlin.idea.KotlinLanguage abstract class KtLightClassBase protected constructor(manager: PsiManager) : AbstractLightClass(manager, KotlinLanguage.INSTANCE), KtLightClass, PsiExtensibleClass { protected open val myInnersCache = KotlinClassInnerStuffCache( - this, listOf(KotlinModificationTrackerService.getInstance(manager.project).outOfBlockModificationTracker)) + myClass = this, + externalDependencies = listOf(KotlinModificationTrackerService.getInstance(manager.project).outOfBlockModificationTracker), + lazyCreator = LightClassesLazyCreator(project) + ) override fun getDelegate() = clsDelegate diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassForSourceDeclaration.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassForSourceDeclaration.kt index e568c6c74b6..9bc019e3ae3 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassForSourceDeclaration.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassForSourceDeclaration.kt @@ -72,8 +72,9 @@ abstract class KtLightClassForSourceDeclaration( StubBasedPsiElement> { override val myInnersCache: KotlinClassInnerStuffCache = KotlinClassInnerStuffCache( - this, - classOrObject.getExternalDependencies() + myClass = this, + externalDependencies = classOrObject.getExternalDependencies(), + lazyCreator = LightClassesLazyCreator(project) ) private val lightIdentifier = KtLightIdentifier(this, classOrObject) diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/LightClassesLazyCreator.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/LightClassesLazyCreator.kt new file mode 100644 index 00000000000..297628e4fdc --- /dev/null +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/LightClassesLazyCreator.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.asJava.classes + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiManager +import com.intellij.psi.impl.PsiCachedValueImpl +import com.intellij.psi.util.CachedValueProvider +import java.util.concurrent.TimeUnit +import java.util.concurrent.locks.ReentrantLock + +class LightClassesLazyCreator(private val project: Project) : KotlinClassInnerStuffCache.LazyCreator() { + override fun get(initializer: () -> T, dependencies: List) = object : Lazy { + private val lock = ReentrantLock() + private val holder = lazyPub { + PsiCachedValueImpl(PsiManager.getInstance(project), + CachedValueProvider { + val v = initializer() + CachedValueProvider.Result.create(v, dependencies) + }) + } + + private fun computeValue(): T = holder.value.value ?: error("holder has not null in initializer") + + override val value: T + get() { + return if (holder.value.hasUpToDateValue()) { + computeValue() + } else { + // the idea behind this locking approach: + // Thread T1 starts to calculate value for A it acquires lock for A + // + // Assumption 1: Lets say A calculation requires another value e.g. B to be calculated + // Assumption 2: Thread T2 wants to calculate value for B + + // to avoid dead-lock + // - we mark thread as doing calculation and acquire lock only once per thread + // as a trade-off to prevent dependent value could be calculated several time + // due to CAS (within putUserDataIfAbsent etc) the same instance of calculated value will be used + + // TODO: NOTE: acquire lock for a several seconds to avoid dead-lock via resolve is a WORKAROUND + + if (!initIsRunning.get() && lock.tryLock(5, TimeUnit.SECONDS)) { + try { + initIsRunning.set(true) + try { + computeValue() + } finally { + initIsRunning.set(false) + } + } finally { + lock.unlock() + } + } else { + computeValue() + } + } + } + + override fun isInitialized() = holder.isInitialized() + } + + companion object { + @JvmStatic + private val initIsRunning: ThreadLocal = ThreadLocal.withInitial { false } + } +} \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/decompiledDeclarations/KtLightClassForDecompiledDeclaration.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/decompiledDeclarations/KtLightClassForDecompiledDeclaration.kt index 04803f5f3f0..9a6ad868b1a 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/decompiledDeclarations/KtLightClassForDecompiledDeclaration.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/decompiledDeclarations/KtLightClassForDecompiledDeclaration.kt @@ -18,6 +18,7 @@ import org.jetbrains.annotations.NonNls import org.jetbrains.kotlin.analyzer.KotlinModificationTrackerService import org.jetbrains.kotlin.asJava.classes.KotlinClassInnerStuffCache import org.jetbrains.kotlin.asJava.classes.KtLightClass +import org.jetbrains.kotlin.asJava.classes.LightClassesLazyCreator import org.jetbrains.kotlin.asJava.classes.lazyPub import org.jetbrains.kotlin.asJava.elements.KtLightElementBase import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile @@ -34,7 +35,8 @@ open class KtLightClassForDecompiledDeclaration( private val myInnersCache = KotlinClassInnerStuffCache( myClass = this, - externalDependencies = listOf(KotlinModificationTrackerService.getInstance(manager.project).outOfBlockModificationTracker) + externalDependencies = listOf(KotlinModificationTrackerService.getInstance(manager.project).outOfBlockModificationTracker), + lazyCreator = LightClassesLazyCreator(project) ) override fun getOwnMethods(): MutableList = _methods diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/asJava/classes/FirLightClassBase.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/asJava/classes/FirLightClassBase.kt index d33adf7bde9..b422d36b3aa 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/asJava/classes/FirLightClassBase.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/asJava/classes/FirLightClassBase.kt @@ -18,8 +18,10 @@ package org.jetbrains.kotlin.idea.asJava import com.intellij.navigation.ItemPresentation import com.intellij.navigation.ItemPresentationProviders +import com.intellij.openapi.project.Project import com.intellij.openapi.util.Pair import com.intellij.psi.* +import com.intellij.psi.impl.PsiCachedValueImpl import com.intellij.psi.impl.PsiClassImplUtil import com.intellij.psi.impl.PsiImplUtil import com.intellij.psi.impl.PsiSuperMethodImplUtil @@ -27,6 +29,7 @@ import com.intellij.psi.impl.light.LightElement import com.intellij.psi.impl.source.PsiExtensibleClass import com.intellij.psi.javadoc.PsiDocComment import com.intellij.psi.scope.PsiScopeProcessor +import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.PsiUtil import org.jetbrains.annotations.NonNls import org.jetbrains.kotlin.analyzer.KotlinModificationTrackerService @@ -34,7 +37,10 @@ import org.jetbrains.kotlin.asJava.classes.KotlinClassInnerStuffCache import org.jetbrains.kotlin.asJava.classes.KotlinClassInnerStuffCache.Companion.processDeclarationsInEnum import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.asJava.classes.cannotModify +import org.jetbrains.kotlin.asJava.classes.lazyPub import org.jetbrains.kotlin.idea.KotlinLanguage +import java.util.concurrent.TimeUnit +import java.util.concurrent.locks.ReentrantLock import javax.swing.Icon abstract class FirLightClassBase protected constructor(manager: PsiManager) : LightElement(manager, KotlinLanguage.INSTANCE), PsiClass, @@ -43,9 +49,18 @@ abstract class FirLightClassBase protected constructor(manager: PsiManager) : Li override val clsDelegate: PsiClass get() = invalidAccess() - protected open val myInnersCache = KotlinClassInnerStuffCache( - myClass = this, - externalDependencies = listOf(KotlinModificationTrackerService.getInstance(manager.project).outOfBlockModificationTracker) + private class FirLightClassesLazyCreator(private val project: Project) : KotlinClassInnerStuffCache.LazyCreator() { + override fun get(initializer: () -> T, dependencies: List): Lazy = lazyPub { + PsiCachedValueImpl(PsiManager.getInstance(project)) { + CachedValueProvider.Result.create(initializer(), dependencies) + }.value ?: error("initializer cannot return null") + } + } + + private val myInnersCache = KotlinClassInnerStuffCache( + myClass = this@FirLightClassBase, + externalDependencies = listOf(KotlinModificationTrackerService.getInstance(manager.project).outOfBlockModificationTracker), + lazyCreator = FirLightClassesLazyCreator(project) ) override fun getFields(): Array = myInnersCache.fields