From 673d1aa91db28e0fb39a535cf5cfaa8d50b74077 Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Thu, 22 Mar 2018 13:00:11 +0300 Subject: [PATCH] Fix a memory leak of old resolve session from light classes The problem is that diagnostics might indirectly retain a reference to an obsolete ResolverForProject (it becomes outdated after out of block modification). At the same time light class instances may survive afer OOM when they are leaked somewhere (e.g. to Java resolve) The idea is to store the Diagnostics instance inside a ResolverForProject component which will be collected once ResolverForProject is collected itself. #KT-23380 In Progress --- .../lightClasses/LazyLightClassDataHolder.kt | 80 +++++++++++-------- .../resolve/IDELightClassGenerationSupport.kt | 17 ++-- .../kotlin/idea/project/IdeaEnvironment.kt | 4 +- 3 files changed, 59 insertions(+), 42 deletions(-) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/LazyLightClassDataHolder.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/LazyLightClassDataHolder.kt index f6f625cab21..2a894ee9970 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/LazyLightClassDataHolder.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/LazyLightClassDataHolder.kt @@ -5,15 +5,11 @@ package org.jetbrains.kotlin.idea.caches.lightClasses -import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.psi.PsiClass import com.intellij.psi.PsiMember import com.intellij.psi.PsiMethod import com.intellij.psi.impl.java.stubs.PsiJavaFileStub -import com.intellij.psi.util.CachedValueProvider -import com.intellij.psi.util.CachedValuesManager -import com.intellij.psi.util.PsiModificationTracker import org.jetbrains.kotlin.asJava.LightClassBuilder import org.jetbrains.kotlin.asJava.builder.* import org.jetbrains.kotlin.asJava.classes.KtLightClass @@ -26,46 +22,55 @@ import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.NotNullableUserDataProperty import org.jetbrains.kotlin.psi.debugText.getDebugText import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics +import org.jetbrains.kotlin.storage.StorageManager import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments typealias ExactLightClassContextProvider = () -> LightClassConstructionContext typealias DummyLightClassContextProvider = (() -> LightClassConstructionContext?)? +typealias DiagnosticsHolderProvider = () -> LazyLightClassDataHolder.DiagnosticsHolder sealed class LazyLightClassDataHolder( builder: LightClassBuilder, - project: Project, exactContextProvider: ExactLightClassContextProvider, dummyContextProvider: DummyLightClassContextProvider, - isLocal: Boolean = false + private val diagnosticsHolderProvider: DiagnosticsHolderProvider ) : LightClassDataHolder { - private data class CachedLightClassBuilderResult(val stub: PsiJavaFileStub, val diagnostics: Diagnostics) + class DiagnosticsHolder(private val storageManager: StorageManager) { + private val computedLightClassDiagnostics = hashMapOf() - private val exactResultCachedValue = - CachedValuesManager.getManager(project).createCachedValue( - { - val (stub, _, diagnostics) = builder(exactContextProvider()) - val cachedResult = CachedLightClassBuilderResult(stub, diagnostics) + fun putDiagnostics(lazyLightClassDataHolder: LazyLightClassDataHolder, diagnostics: Diagnostics) { + storageManager.compute { + computedLightClassDiagnostics[lazyLightClassDataHolder] = diagnostics + } + } - CachedValueProvider.Result.create( - cachedResult, - if (isLocal) - PsiModificationTracker.MODIFICATION_COUNT - else - PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT - ) - }, false - ) + fun getComputedDiagnostics(lazyLightClassDataHolder: LazyLightClassDataHolder): Diagnostics? = + storageManager.compute { computedLightClassDiagnostics[lazyLightClassDataHolder] } + } + + private val exactResultLazyValue = lazyPub { + val (stub, _, diagnostics) = builder(exactContextProvider()) + diagnosticsHolderProvider().putDiagnostics(this, diagnostics) + stub + } private val lazyInexactStub by lazyPub { dummyContextProvider?.let { provider -> provider()?.let { context -> builder.invoke(context).stub } } } private val inexactStub: PsiJavaFileStub? - get() = if (exactResultCachedValue.hasUpToDateValue()) null else lazyInexactStub + get() = if (exactResultLazyValue.isInitialized()) null else lazyInexactStub - override val javaFileStub get() = exactResultCachedValue.value.stub - override val extraDiagnostics get() = exactResultCachedValue.value.diagnostics + override val javaFileStub by exactResultLazyValue + + override val extraDiagnostics: Diagnostics + get() { + // run light class builder + exactResultLazyValue.value + + return diagnosticsHolderProvider().getComputedDiagnostics(this) ?: Diagnostics.EMPTY + } // for facade or defaultImpls override fun findData(findDelegate: (PsiJavaFileStub) -> PsiClass): LightClassData = @@ -74,9 +79,12 @@ sealed class LazyLightClassDataHolder( } class ForClass( - builder: LightClassBuilder, project: Project, isLocal: Boolean, - exactContextProvider: ExactLightClassContextProvider, dummyContextProvider: DummyLightClassContextProvider - ) : LazyLightClassDataHolder(builder, project, exactContextProvider, dummyContextProvider, isLocal), LightClassDataHolder.ForClass { + builder: LightClassBuilder, + exactContextProvider: ExactLightClassContextProvider, + dummyContextProvider: DummyLightClassContextProvider, + diagnosticsHolderProvider: DiagnosticsHolderProvider + ) : LazyLightClassDataHolder(builder, exactContextProvider, dummyContextProvider, diagnosticsHolderProvider), + LightClassDataHolder.ForClass { override fun findDataForClassOrObject(classOrObject: KtClassOrObject): LightClassData = LazyLightClassData { stub -> stub.findDelegate(classOrObject) @@ -84,14 +92,20 @@ sealed class LazyLightClassDataHolder( } class ForFacade( - builder: LightClassBuilder, project: Project, - exactContextProvider: ExactLightClassContextProvider, dummyContextProvider: DummyLightClassContextProvider - ) : LazyLightClassDataHolder(builder, project, exactContextProvider, dummyContextProvider), LightClassDataHolder.ForFacade + builder: LightClassBuilder, + exactContextProvider: ExactLightClassContextProvider, + dummyContextProvider: DummyLightClassContextProvider, + diagnosticsHolderProvider: DiagnosticsHolderProvider + ) : LazyLightClassDataHolder(builder, exactContextProvider, dummyContextProvider, diagnosticsHolderProvider), + LightClassDataHolder.ForFacade class ForScript( - builder: LightClassBuilder, project: Project, - exactContextProvider: ExactLightClassContextProvider, dummyContextProvider: DummyLightClassContextProvider - ) : LazyLightClassDataHolder(builder, project, exactContextProvider, dummyContextProvider), LightClassDataHolder.ForScript + builder: LightClassBuilder, + exactContextProvider: ExactLightClassContextProvider, + dummyContextProvider: DummyLightClassContextProvider, + diagnosticsHolderProvider: DiagnosticsHolderProvider + ) : LazyLightClassDataHolder(builder, exactContextProvider, dummyContextProvider, diagnosticsHolderProvider), + LightClassDataHolder.ForScript private inner class LazyLightClassData( findDelegate: (PsiJavaFileStub) -> PsiClass diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.kt index 8a66fa6d187..53b598dc6b8 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.kt @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.asJava.finder.JavaElementFinder import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.idea.caches.lightClasses.IDELightClassContexts import org.jetbrains.kotlin.idea.caches.lightClasses.LazyLightClassDataHolder +import org.jetbrains.kotlin.idea.resolve.frontendService import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.NoDescriptorForDeclarationException @@ -39,17 +40,15 @@ class IDELightClassGenerationSupport(project: Project) : LightClassGenerationSup classOrObject.shouldNotBeVisibleAsLightClass() -> InvalidLightClassDataHolder classOrObject.isLocal -> LazyLightClassDataHolder.ForClass( builder, - classOrObject.project, exactContextProvider = { IDELightClassContexts.contextForLocalClassOrObject(classOrObject) }, dummyContextProvider = null, - isLocal = true + diagnosticsHolderProvider = { classOrObject.getDiagnosticsHolder() } ) else -> LazyLightClassDataHolder.ForClass( builder, - classOrObject.project, exactContextProvider = { IDELightClassContexts.contextForNonLocalClassOrObject(classOrObject) }, dummyContextProvider = { IDELightClassContexts.lightContextForClassOrObject(classOrObject) }, - isLocal = false + diagnosticsHolderProvider = { classOrObject.getDiagnosticsHolder() } ) } } @@ -62,21 +61,23 @@ class IDELightClassGenerationSupport(project: Project) : LightClassGenerationSup return LazyLightClassDataHolder.ForFacade( builder, - files.first().project, exactContextProvider = { IDELightClassContexts.contextForFacade(sortedFiles) }, - dummyContextProvider = { IDELightClassContexts.lightContextForFacade(sortedFiles) } + dummyContextProvider = { IDELightClassContexts.lightContextForFacade(sortedFiles) }, + diagnosticsHolderProvider = { files.first().getDiagnosticsHolder() } ) } override fun createDataHolderForScript(script: KtScript, builder: LightClassBuilder): LightClassDataHolder.ForScript { return LazyLightClassDataHolder.ForScript( builder, - script.project, exactContextProvider = { IDELightClassContexts.contextForScript(script) }, - dummyContextProvider = { IDELightClassContexts.lightContextForScript(script) } + dummyContextProvider = { IDELightClassContexts.lightContextForScript(script) }, + diagnosticsHolderProvider = { script.getDiagnosticsHolder() } ) } + private fun KtElement.getDiagnosticsHolder() = + getResolutionFacade().frontendService() override fun resolveToDescriptor(declaration: KtDeclaration): DeclarationDescriptor? { try { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/IdeaEnvironment.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/IdeaEnvironment.kt index 792dbda4115..bcd0d42360d 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/IdeaEnvironment.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/IdeaEnvironment.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.project import org.jetbrains.kotlin.container.StorageComponentContainer import org.jetbrains.kotlin.container.useImpl +import org.jetbrains.kotlin.idea.caches.lightClasses.LazyLightClassDataHolder import org.jetbrains.kotlin.resolve.TargetEnvironment object IdeaEnvironment : TargetEnvironment("Idea") { @@ -25,5 +26,6 @@ object IdeaEnvironment : TargetEnvironment("Idea") { container.useImpl() container.useImpl() container.useImpl() + container.useImpl() } -} \ No newline at end of file +}