From 7d5396ac95879062e8d9cfde8eab155c86e483b0 Mon Sep 17 00:00:00 2001 From: Dmitry Savvinov Date: Thu, 19 Mar 2020 18:23:27 +0300 Subject: [PATCH] Introduce ModuleInfo -> SdkInfo cache This fixes KT-35050 properly by caching this mapping and preventing inspecting dependencies multiple times. --- .../resolve/JvmPlatformKindResolution.kt | 1 - .../idea/caches/project/IdeaModuleInfos.kt | 17 ++++-- .../idea/caches/project/ScriptModuleInfos.kt | 6 +-- .../idea/caches/project/SdkInfoCache.kt | 52 +++++++++++++++++++ .../idea/caches/project/getModuleInfo.kt | 13 ++--- idea/resources/META-INF/plugin-common.xml | 3 ++ idea/resources/META-INF/plugin-common.xml.192 | 3 ++ 7 files changed, 79 insertions(+), 16 deletions(-) create mode 100644 idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/SdkInfoCache.kt diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JvmPlatformKindResolution.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JvmPlatformKindResolution.kt index e61c9c42bd9..bd1a8f2aed2 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JvmPlatformKindResolution.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/caches/resolve/JvmPlatformKindResolution.kt @@ -16,7 +16,6 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.jvm.JvmBuiltIns import org.jetbrains.kotlin.context.ProjectContext import org.jetbrains.kotlin.idea.caches.project.SdkInfo -import org.jetbrains.kotlin.idea.caches.project.findSdkAcrossDependencies import org.jetbrains.kotlin.idea.caches.resolve.BuiltInsCacheKey import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.platform.impl.JvmIdePlatformKind diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt index 23e72b9deb2..e3801d125e9 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt @@ -65,6 +65,8 @@ interface IdeaModuleInfo : org.jetbrains.kotlin.idea.caches.resolve.IdeaModuleIn val moduleOrigin: ModuleOrigin + val project: Project? + override val capabilities: Map, Any?> get() = super.capabilities + mapOf(OriginCapability to moduleOrigin) @@ -156,6 +158,9 @@ interface ModuleSourceInfo : IdeaModuleInfo, TrackableModuleInfo { override val moduleOrigin: ModuleOrigin get() = ModuleOrigin.MODULE + override val project: Project + get() = module.project + override val platform: TargetPlatform get() = TargetPlatformDetector.getPlatform(module) @@ -284,7 +289,7 @@ private class ModuleTestSourceScope(module: Module) : ModuleSourceScope(module) override fun toString() = "ModuleTestSourceScope($module)" } -open class LibraryInfo(val project: Project, val library: Library) : IdeaModuleInfo, LibraryModuleInfo, BinaryModuleInfo { +open class LibraryInfo(override val project: Project, val library: Library) : IdeaModuleInfo, LibraryModuleInfo, BinaryModuleInfo { override val moduleOrigin: ModuleOrigin get() = ModuleOrigin.LIBRARY @@ -329,7 +334,7 @@ open class LibraryInfo(val project: Project, val library: Library) : IdeaModuleI } -data class LibrarySourceInfo(val project: Project, val library: Library, override val binariesModuleInfo: BinaryModuleInfo) : +data class LibrarySourceInfo(override val project: Project, val library: Library, override val binariesModuleInfo: BinaryModuleInfo) : IdeaModuleInfo, SourceForBinaryModuleInfo { override val name: Name = Name.special(KotlinIdeaAnalysisBundle.message("sources.for.library.0", library.name.toString())) @@ -355,7 +360,7 @@ data class LibrarySourceInfo(val project: Project, val library: Library, overrid } //TODO: (module refactoring) there should be separate SdkSourceInfo but there are no kotlin source in existing sdks for now :) -data class SdkInfo(val project: Project, val sdk: Sdk) : IdeaModuleInfo { +data class SdkInfo(override val project: Project, val sdk: Sdk) : IdeaModuleInfo { override val moduleOrigin: ModuleOrigin get() = ModuleOrigin.LIBRARY @@ -378,6 +383,9 @@ object NotUnderContentRootModuleInfo : IdeaModuleInfo { override val name: Name = Name.special(KotlinIdeaAnalysisBundle.message("special.module.for.files.not.under.source.root")) + override val project: Project? + get() = null + override fun contentScope() = GlobalSearchScope.EMPTY_SCOPE //TODO: (module refactoring) dependency on runtime can be of use here @@ -481,6 +489,9 @@ data class PlatformModuleInfo( override val containedModules: List = listOf(platformModule) + commonModules + override val project: Project + get() = platformModule.module.project + override val platform: TargetPlatform get() = platformModule.platform diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/ScriptModuleInfos.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/ScriptModuleInfos.kt index d0c3d570f0d..45ff7a9d1f1 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/ScriptModuleInfos.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/ScriptModuleInfos.kt @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatformAnalyzerServices import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition data class ScriptModuleInfo( - val project: Project, + override val project: Project, val scriptFile: VirtualFile, val scriptDefinition: ScriptDefinition ) : IdeaModuleInfo { @@ -59,7 +59,7 @@ data class ScriptModuleInfo( get() = JvmPlatformAnalyzerServices } -sealed class ScriptDependenciesInfo(val project: Project) : IdeaModuleInfo, BinaryModuleInfo { +sealed class ScriptDependenciesInfo(override val project: Project) : IdeaModuleInfo, BinaryModuleInfo { abstract val sdk: Sdk? override val name = Name.special(KotlinIdeaAnalysisBundle.message("script.dependencies")) @@ -118,7 +118,7 @@ sealed class ScriptDependenciesInfo(val project: Project) : IdeaModuleInfo, Bina } } -sealed class ScriptDependenciesSourceInfo(val project: Project) : IdeaModuleInfo, SourceForBinaryModuleInfo { +sealed class ScriptDependenciesSourceInfo(override val project: Project) : IdeaModuleInfo, SourceForBinaryModuleInfo { override val name = Name.special(KotlinIdeaAnalysisBundle.message("source.for.script.dependencies")) override val binariesModuleInfo: ScriptDependenciesInfo diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/SdkInfoCache.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/SdkInfoCache.kt new file mode 100644 index 00000000000..8bbd21b89ac --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/SdkInfoCache.kt @@ -0,0 +1,52 @@ +/* + * 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.idea.caches.project + +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.util.containers.ConcurrentWeakKeySoftValueHashMap +import com.intellij.util.containers.ContainerUtil +import org.jetbrains.kotlin.analyzer.ModuleInfo +import org.jetbrains.kotlin.caches.project.cacheInvalidatingOnRootModifications + +/** + * Maintains and caches mapping ModuleInfo -> SdkInfo *form its dependencies* + * (note that this SDK might be different from Project SDK) + * + * Cache is needed because one and the same library might (and usually does) + * participate as dependency in several modules, so if someone queries a lot + * of modules for their SDKs (ex.: determine built-ins for each module in a + * project), we end up inspecting one and the same dependency multiple times. + * + * With projects with abnormally high amount of dependencies this might lead + * to performance issues. + */ +interface SdkInfoCache { + fun findOrGetCachedSdk(moduleInfo: ModuleInfo): SdkInfo? + + companion object { + fun getInstance(project: Project): SdkInfoCache = + ServiceManager.getService(project, SdkInfoCache::class.java) + } +} + +class SdkInfoCacheImpl(private val project: Project) : SdkInfoCache { + private val cache: MutableMap + // Note: it's ok to use thread-unsafe map, because 'doFindSdk' is pure + get() = project.cacheInvalidatingOnRootModifications { mutableMapOf() } + + override fun findOrGetCachedSdk(moduleInfo: ModuleInfo): SdkInfo? { + if (!cache.containsKey(moduleInfo)) { + cache[moduleInfo] = doFindSdk(moduleInfo) + } + + return cache[moduleInfo] + } + + private fun doFindSdk(moduleInfo: ModuleInfo): SdkInfo? = + moduleInfo.dependencies().lazyClosure { it.dependencies() }.firstOrNull { it is SdkInfo } as SdkInfo? +} \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/getModuleInfo.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/getModuleInfo.kt index 0f2e23b0e34..7fae48d0f97 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/getModuleInfo.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/getModuleInfo.kt @@ -17,6 +17,7 @@ import com.intellij.psi.PsiFile import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade import org.jetbrains.kotlin.asJava.elements.KtLightElement +import org.jetbrains.kotlin.caches.project.cacheByClassInvalidatingOnRootModifications import org.jetbrains.kotlin.caches.project.cacheInvalidatingOnRootModifications import org.jetbrains.kotlin.idea.caches.lightClasses.KtLightClassForDecompiledDeclaration import org.jetbrains.kotlin.idea.core.isInTestSourceContentKotlinAware @@ -43,16 +44,10 @@ fun PsiElement.getNullableModuleInfo(): IdeaModuleInfo? = this.collectInfos(Modu fun PsiElement.getModuleInfos(): Sequence = this.collectInfos(ModuleInfoCollector.ToSequence) -private fun ModuleInfo.doFindSdk(): SdkInfo? = dependencies().lazyClosure { it.dependencies() }.firstIsInstanceOrNull() - fun ModuleInfo.findSdkAcrossDependencies(): SdkInfo? { - return when (this) { - // It is important to check for cases of test/production explicitly, because we're using lambda's class - // of 'cacheInvalidatingOnRootModifications' as key for cache, and it should be different for Production/Source - is ModuleProductionSourceInfo -> module.cacheInvalidatingOnRootModifications { doFindSdk() } - is ModuleTestSourceInfo -> module.cacheInvalidatingOnRootModifications { doFindSdk() } - else -> doFindSdk() - } + val project = (this as? IdeaModuleInfo)?.project ?: return null + + return SdkInfoCache.getInstance(project).findOrGetCachedSdk(this) } fun getModuleInfoByVirtualFile(project: Project, virtualFile: VirtualFile): IdeaModuleInfo? = diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml index 04d38556dd2..2e440b1e447 100644 --- a/idea/resources/META-INF/plugin-common.xml +++ b/idea/resources/META-INF/plugin-common.xml @@ -362,6 +362,9 @@ + + diff --git a/idea/resources/META-INF/plugin-common.xml.192 b/idea/resources/META-INF/plugin-common.xml.192 index fea47f0ae73..0cba12b85e4 100644 --- a/idea/resources/META-INF/plugin-common.xml.192 +++ b/idea/resources/META-INF/plugin-common.xml.192 @@ -371,6 +371,9 @@ + +