From 364d97e9501b7ff35c235ebaafa618006d5c29ae Mon Sep 17 00:00:00 2001 From: Vladimir Dolzhenko Date: Mon, 16 Mar 2020 11:14:50 +0000 Subject: [PATCH] Invalidate partialBodyResolveCache on OCB instead of PSI modification #KT-37466 Fixed --- .../KotlinCodeBlockModificationListener.kt | 5 ++ .../idea/project/ResolveElementCache.kt | 77 ++++++++++++++----- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt index 59b2a8a2a9c..314dd9f8839 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/trackers/KotlinCodeBlockModificationListener.kt @@ -359,6 +359,9 @@ private fun KtFile.incOutOfBlockModificationCount() { * inBlockModifications is a collection of block elements those have in-block modifications */ private val IN_BLOCK_MODIFICATIONS = Key>("IN_BLOCK_MODIFICATIONS") +private val FILE_IN_BLOCK_MODIFICATION_COUNT = Key("FILE_IN_BLOCK_MODIFICATION_COUNT") + +val KtFile.inBlockModificationCount: Long by NotNullableUserDataProperty(FILE_IN_BLOCK_MODIFICATION_COUNT, 0) val KtFile.inBlockModifications: Collection get() { @@ -371,6 +374,8 @@ private fun KtFile.addInBlockModifiedItem(element: KtElement) { synchronized(collection) { collection.add(element) } + val count = getUserData(FILE_IN_BLOCK_MODIFICATION_COUNT) ?: 0 + putUserData(FILE_IN_BLOCK_MODIFICATION_COUNT, count + 1) } fun KtFile.clearInBlockModifications() { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/ResolveElementCache.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/ResolveElementCache.kt index 2150f25f6d1..98fa5f5629c 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/ResolveElementCache.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/ResolveElementCache.kt @@ -9,8 +9,8 @@ import com.intellij.openapi.project.Project import com.intellij.psi.util.CachedValue import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValuesManager -import com.intellij.psi.util.PsiModificationTracker import com.intellij.util.containers.ContainerUtil +import com.intellij.util.containers.SLRUCache import org.jetbrains.kotlin.cfg.ControlFlowInformationProvider import org.jetbrains.kotlin.container.get import org.jetbrains.kotlin.context.SimpleGlobalContext @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.frontend.di.createContainerForBodyResolve import org.jetbrains.kotlin.idea.caches.resolve.CodeFragmentAnalyzer import org.jetbrains.kotlin.idea.caches.resolve.util.analyzeControlFlow import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener +import org.jetbrains.kotlin.idea.caches.trackers.inBlockModificationCount import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.psi.* @@ -35,6 +36,7 @@ import org.jetbrains.kotlin.resolve.lazy.* import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor import org.jetbrains.kotlin.resolve.scopes.LexicalScope import java.util.* +import java.util.concurrent.ConcurrentMap class ResolveElementCache( private val resolveSession: ResolveSession, @@ -42,6 +44,7 @@ class ResolveElementCache( private val targetPlatform: TargetPlatform, private val codeFragmentAnalyzer: CodeFragmentAnalyzer ) : BodyResolveCache { + private class CachedFullResolve(val bindingContext: BindingContext, resolveElement: KtElement) { private val modificationStamp: Long? = modificationStamp(resolveElement) @@ -75,25 +78,38 @@ class ResolveElementCache( ) private class CachedPartialResolve(val bindingContext: BindingContext, file: KtFile, val mode: BodyResolveMode) { - private val modificationStamp: Long? = modificationStamp(file) + private val modificationStamp: Long = modificationStamp(file) - fun isUpToDate(file: KtFile, newMode: BodyResolveMode) = - modificationStamp == modificationStamp(file) && mode.doesNotLessThan(newMode) + fun isUpToDate(file: KtFile, newMode: BodyResolveMode): Boolean { + return modificationStamp == modificationStamp(file) && mode.doesNotLessThan(newMode) + } - private fun modificationStamp(file: KtFile): Long? { - return if (!file.isPhysical) // for non-physical file we don't get MODIFICATION_COUNT increased and must reset data on any modification of the file + private fun modificationStamp(file: KtFile): Long { + // for non-physical file we don't get MODIFICATION_COUNT increased and must reset data on any modification of the file + return if (!file.isPhysical) file.modificationStamp else - null + file.inBlockModificationCount + } + + override fun toString(): String { + return "{CachedPartialResolve: $mode $modificationStamp}" } } - private val partialBodyResolveCache: CachedValue> = + private val partialBodyResolveCache: CachedValue>> = CachedValuesManager.getManager(project).createCachedValue( - CachedValueProvider> { + CachedValueProvider { + val slruCache: SLRUCache> = + object : SLRUCache>(20, 20) { + override fun createValue(file: KtFile): ConcurrentMap { + return ContainerUtil.createConcurrentWeakKeySoftValueMap() + } + } + CachedValueProvider.Result.create( - ContainerUtil.createConcurrentWeakKeySoftValueMap(), - PsiModificationTracker.MODIFICATION_COUNT, + slruCache, + KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker, resolveSession.exceptionTracker ) }, @@ -155,14 +171,21 @@ class ResolveElementCache( val file = resolveElement.getContainingKtFile() val statementsToResolve = contextElements!!.map { PartialBodyResolveFilter.findStatementToResolve(it, resolveElement) }.distinct() - val partialResolveMap = partialBodyResolveCache.value - val cachedResults = statementsToResolve.map { partialResolveMap[it ?: resolveElement] } - if (cachedResults.all { - it != null && it.isUpToDate( - file, - bodyResolveMode - ) - }) { // partial resolve is already cached for these statements + val statementsToResolveByKtFile = + statementsToResolve.groupBy { (it ?: resolveElement).containingKtFile } + val cachedResults = + statementsToResolveByKtFile.flatMap { (file, expressions) -> + val partialBodyResolveCacheValue = partialBodyResolveCache.value + val expressionsMap = synchronized(partialBodyResolveCacheValue) { + partialBodyResolveCacheValue[file] + } + expressions.map { expressionsMap[it ?: resolveElement] } + } + + // a bit of problem here that several threads come to analyze same resolveElement + + if (cachedResults.all { it != null && it.isUpToDate(file, bodyResolveMode) }) { + // partial resolve is already cached for these statements return CompositeBindingContext.create(cachedResults.map { it!!.bindingContext }.distinct()) } @@ -178,18 +201,30 @@ class ResolveElementCache( return bindingContext } + val partialBodyResolveCacheValue = partialBodyResolveCache.value + val expressionsMap = synchronized(partialBodyResolveCacheValue) { + partialBodyResolveCacheValue[file] + } + val resolveToCache = CachedPartialResolve(bindingContext, file, bodyResolveMode) if (statementFilter is PartialBodyResolveFilter) { for (statement in statementFilter.allStatementsToResolve) { if (bindingContext[BindingContext.PROCESSED, statement] == true) { - partialResolveMap.putIfAbsent(statement, resolveToCache) + expressionsMap.putIfAbsent(statement, resolveToCache)?.let { oldResolveToCache -> + if (!oldResolveToCache.isUpToDate(file, bodyResolveMode)) { + expressionsMap[statement] = resolveToCache + } else if (!oldResolveToCache.mode.doesNotLessThan(bodyResolveMode)) { + expressionsMap.replace(statement, oldResolveToCache, resolveToCache) + } + return@let + } } } } // we use the whole declaration key in the map to obtain resolve not inside any block (e.g. default parameter values) - partialResolveMap[resolveElement] = resolveToCache + expressionsMap[resolveElement] = resolveToCache return bindingContext }