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:
+12
-22
@@ -30,30 +30,20 @@ typealias DummyLightClassContextProvider = (() -> LightClassConstructionContext?
|
|||||||
typealias DiagnosticsHolderProvider = () -> LazyLightClassDataHolder.DiagnosticsHolder
|
typealias DiagnosticsHolderProvider = () -> LazyLightClassDataHolder.DiagnosticsHolder
|
||||||
|
|
||||||
sealed class LazyLightClassDataHolder(
|
sealed class LazyLightClassDataHolder(
|
||||||
builder: LightClassBuilder,
|
private val builder: LightClassBuilder,
|
||||||
exactContextProvider: ExactLightClassContextProvider,
|
private val exactContextProvider: ExactLightClassContextProvider,
|
||||||
dummyContextProvider: DummyLightClassContextProvider,
|
dummyContextProvider: DummyLightClassContextProvider,
|
||||||
private val diagnosticsHolderProvider: DiagnosticsHolderProvider
|
private val diagnosticsHolderProvider: DiagnosticsHolderProvider
|
||||||
) : LightClassDataHolder {
|
) : LightClassDataHolder {
|
||||||
|
|
||||||
class DiagnosticsHolder(private val storageManager: StorageManager) {
|
class DiagnosticsHolder(storageManager: StorageManager) {
|
||||||
private val computedLightClassDiagnostics = hashMapOf<LazyLightClassDataHolder, Diagnostics>()
|
private val cache = storageManager.createCacheWithNotNullValues<LazyLightClassDataHolder, Diagnostics>()
|
||||||
|
|
||||||
fun putDiagnostics(lazyLightClassDataHolder: LazyLightClassDataHolder, diagnostics: Diagnostics) {
|
fun getOrCompute(lazyLightClassDataHolder: LazyLightClassDataHolder, diagnostics: () -> Diagnostics) =
|
||||||
storageManager.compute {
|
cache.computeIfAbsent(lazyLightClassDataHolder, diagnostics)
|
||||||
computedLightClassDiagnostics[lazyLightClassDataHolder] = diagnostics
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getComputedDiagnostics(lazyLightClassDataHolder: LazyLightClassDataHolder): Diagnostics? =
|
|
||||||
storageManager.compute { computedLightClassDiagnostics[lazyLightClassDataHolder] }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val exactResultLazyValue = lazyPub {
|
private val exactResultLazyValue = lazyPub { builder(exactContextProvider()).stub }
|
||||||
val (stub, _, diagnostics) = builder(exactContextProvider())
|
|
||||||
diagnosticsHolderProvider().putDiagnostics(this, diagnostics)
|
|
||||||
stub
|
|
||||||
}
|
|
||||||
|
|
||||||
private val lazyInexactStub by lazyPub {
|
private val lazyInexactStub by lazyPub {
|
||||||
dummyContextProvider?.let { provider -> provider()?.let { context -> builder.invoke(context).stub } }
|
dummyContextProvider?.let { provider -> provider()?.let { context -> builder.invoke(context).stub } }
|
||||||
@@ -65,11 +55,11 @@ sealed class LazyLightClassDataHolder(
|
|||||||
override val javaFileStub by exactResultLazyValue
|
override val javaFileStub by exactResultLazyValue
|
||||||
|
|
||||||
override val extraDiagnostics: Diagnostics
|
override val extraDiagnostics: Diagnostics
|
||||||
get() {
|
get() = diagnosticsHolderProvider().getOrCompute(this) {
|
||||||
// run light class builder
|
builder(exactContextProvider()).diagnostics
|
||||||
exactResultLazyValue.value
|
// 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
|
||||||
return diagnosticsHolderProvider().getComputedDiagnostics(this) ?: Diagnostics.EMPTY
|
.takeUnless { it.isEmpty() } ?: Diagnostics.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
// for facade or defaultImpls
|
// for facade or defaultImpls
|
||||||
|
|||||||
Reference in New Issue
Block a user