Fix memory leak related to diagnostics computation within light classes

Before this change, when somebody has requested the exact light class
we also store LazyJvmDiagnostic for it in a map inside DiagnosticHolder.

The problem is that LazyJvmDiagnostic retains a lot of memory:
GenerationState, bindingContext, etc.

At the same time, it commonly happens that light classes are being computed from Java resolve and diagnostics are obviously
unnecessary there.

The solution has two parts:
1. We don't retain diagnostics until somebody explicitly requested them
2. We force computation of LazyJvmDiagnostic to avoid retaining
backend's parts

The solution has a kind of drawback: for an opened in editor Kotlin file,
we'll run back-end twice for contained classes (analysis parts should be reused, though)

 #KT-24048 Fixed
This commit is contained in:
Denis Zharkov
2018-04-26 13:04:24 +03:00
parent 26f1d6ae7c
commit b75024e3e0
@@ -30,30 +30,20 @@ typealias DummyLightClassContextProvider = (() -> LightClassConstructionContext?
typealias DiagnosticsHolderProvider = () -> LazyLightClassDataHolder.DiagnosticsHolder
sealed class LazyLightClassDataHolder(
builder: LightClassBuilder,
exactContextProvider: ExactLightClassContextProvider,
private val builder: LightClassBuilder,
private val exactContextProvider: ExactLightClassContextProvider,
dummyContextProvider: DummyLightClassContextProvider,
private val diagnosticsHolderProvider: DiagnosticsHolderProvider
) : LightClassDataHolder {
class DiagnosticsHolder(private val storageManager: StorageManager) {
private val computedLightClassDiagnostics = hashMapOf<LazyLightClassDataHolder, Diagnostics>()
class DiagnosticsHolder(storageManager: StorageManager) {
private val cache = storageManager.createCacheWithNotNullValues<LazyLightClassDataHolder, Diagnostics>()
fun putDiagnostics(lazyLightClassDataHolder: LazyLightClassDataHolder, diagnostics: Diagnostics) {
storageManager.compute {
computedLightClassDiagnostics[lazyLightClassDataHolder] = diagnostics
}
}
fun getComputedDiagnostics(lazyLightClassDataHolder: LazyLightClassDataHolder): Diagnostics? =
storageManager.compute { computedLightClassDiagnostics[lazyLightClassDataHolder] }
fun getOrCompute(lazyLightClassDataHolder: LazyLightClassDataHolder, diagnostics: () -> Diagnostics) =
cache.computeIfAbsent(lazyLightClassDataHolder, diagnostics)
}
private val exactResultLazyValue = lazyPub {
val (stub, _, diagnostics) = builder(exactContextProvider())
diagnosticsHolderProvider().putDiagnostics(this, diagnostics)
stub
}
private val exactResultLazyValue = lazyPub { builder(exactContextProvider()).stub }
private val lazyInexactStub by lazyPub {
dummyContextProvider?.let { provider -> provider()?.let { context -> builder.invoke(context).stub } }
@@ -65,11 +55,11 @@ sealed class LazyLightClassDataHolder(
override val javaFileStub by exactResultLazyValue
override val extraDiagnostics: Diagnostics
get() {
// run light class builder
exactResultLazyValue.value
return diagnosticsHolderProvider().getComputedDiagnostics(this) ?: Diagnostics.EMPTY
get() = diagnosticsHolderProvider().getOrCompute(this) {
builder(exactContextProvider()).diagnostics
// Force lazy diagnostics computation because otherwise a lot of memory is retained by computation.
// NB: Laziness here is not crucial anyway since somebody already has requested diagnostics and we hope one will use them
.takeUnless { it.isEmpty() } ?: Diagnostics.EMPTY
}
// for facade or defaultImpls