[FIR IDE] LC Remove difficult caching from FirLightClassBase

This commit is contained in:
Igor Yakovlev
2020-12-11 22:40:08 +03:00
parent f282b721bc
commit 2fa5ab6e31
6 changed files with 107 additions and 59 deletions
@@ -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<Any>) {
class KotlinClassInnerStuffCache(
private val myClass: PsiExtensibleClass,
externalDependencies: List<Any>,
private val lazyCreator: LazyCreator,
) {
private val myTracker = SimpleModificationTracker()
private val dependencies: List<Any> = externalDependencies + myTracker
fun <T : Any> get(initializer: () -> T) = object : Lazy<T> {
private val lock = ReentrantLock()
private val holder = lazyPub {
PsiCachedValueImpl(PsiManager.getInstance(myClass.project),
CachedValueProvider<T> {
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 <T : Any> get(initializer: () -> T, dependencies: List<Any>): Lazy<T>
}
private fun <T : Any> get(initializer: () -> T): Lazy<T> = lazyCreator.get(initializer, dependencies)
private val _getConstructors: Array<PsiMethod> by get { PsiImplUtil.getConstructors(myClass) }
val constructors: Array<PsiMethod>
@@ -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<Boolean> = ThreadLocal.withInitial { false }
// Copy of PsiClassImplUtil.processDeclarationsInEnum for own cache class
@JvmStatic
fun processDeclarationsInEnum(
@@ -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
@@ -72,8 +72,9 @@ abstract class KtLightClassForSourceDeclaration(
StubBasedPsiElement<KotlinClassOrObjectStub<out KtClassOrObject>> {
override val myInnersCache: KotlinClassInnerStuffCache = KotlinClassInnerStuffCache(
this,
classOrObject.getExternalDependencies()
myClass = this,
externalDependencies = classOrObject.getExternalDependencies(),
lazyCreator = LightClassesLazyCreator(project)
)
private val lightIdentifier = KtLightIdentifier(this, classOrObject)
@@ -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 <T : Any> get(initializer: () -> T, dependencies: List<Any>) = object : Lazy<T> {
private val lock = ReentrantLock()
private val holder = lazyPub {
PsiCachedValueImpl(PsiManager.getInstance(project),
CachedValueProvider<T> {
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<Boolean> = ThreadLocal.withInitial { false }
}
}