From ddac2b0b39c66fba56a7ca23e5d33e588a05ef48 Mon Sep 17 00:00:00 2001 From: Victor Matchenko Date: Thu, 24 Oct 2024 16:27:06 +0200 Subject: [PATCH] IJPL-164852 Memory leak related to StatisticsEventLoggerProvider prevents dynamic unloading of AIA Disposable StatisticsFileEventLogger lifecycle was bound to Application and disposed only on application close. This does not allow plugins with own implementation of StatisticsEventLoggerProvider to reload without memory leak. Created Application and Project level coroutine scopes for Statistics. Hold coroutine scope in StatisticsEventLoggerProvider and provide ability to override it. Handle lifecycle of disposable StatisticsFileEventLogger with coroutine instead of Disposer#register. Subscribe to EventLogConfigOptionsService.TOPIC using coroutine scope. Override coroutine scope in some implementations of implementation of StatisticsEventLoggerProvider. GitOrigin-RevId: 8e585b73cfa2e0face38ff2268b9c6021a345ba2 --- platform/statistics/api-dump-unreviewed.txt | 1 + .../statistic/StatisticsServiceScope.kt | 24 +++++++++++++++++++ .../eventLog/StatisticsEventLogger.kt | 10 +++++--- .../StatisticsEventLogThrottleWriter.kt | 11 +++++---- 4 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 platform/statistics/src/com/intellij/internal/statistic/StatisticsServiceScope.kt diff --git a/platform/statistics/api-dump-unreviewed.txt b/platform/statistics/api-dump-unreviewed.txt index ce45885f4a01..a43b33918626 100644 --- a/platform/statistics/api-dump-unreviewed.txt +++ b/platform/statistics/api-dump-unreviewed.txt @@ -166,6 +166,7 @@ a:com.intellij.internal.statistic.eventLog.StatisticsEventLoggerProvider - b:(java.lang.String,I,J,I,Z,Z,Z,I,kotlin.jvm.internal.DefaultConstructorMarker):V - createEventsMergeStrategy():com.intellij.internal.statistic.eventLog.StatisticsEventMergeStrategy - f:getActiveLogFile():com.intellij.internal.statistic.eventLog.EventLogFile +- getCoroutineScope():kotlinx.coroutines.CoroutineScope - sf:getEP_NAME():com.intellij.openapi.extensions.ExtensionPointName - f:getLogFilesProvider():com.intellij.internal.statistic.eventLog.EventLogFilesProvider - getLogger():com.intellij.internal.statistic.eventLog.StatisticsEventLogger diff --git a/platform/statistics/src/com/intellij/internal/statistic/StatisticsServiceScope.kt b/platform/statistics/src/com/intellij/internal/statistic/StatisticsServiceScope.kt new file mode 100644 index 000000000000..a5e3b41232a7 --- /dev/null +++ b/platform/statistics/src/com/intellij/internal/statistic/StatisticsServiceScope.kt @@ -0,0 +1,24 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.internal.statistic + +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import kotlinx.coroutines.* +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Internal +interface StatisticsServiceScope { + companion object { + fun getScope(project: Project): CoroutineScope = project.service().scope + fun getScope(): CoroutineScope = service().scope + } +} + +@ApiStatus.Internal +@Service(Service.Level.APP) +class StatisticsServiceApplicationScope(val scope: CoroutineScope) + +@ApiStatus.Internal +@Service(Service.Level.PROJECT) +class StatisticsServiceProjectScope(val scope: CoroutineScope) \ No newline at end of file diff --git a/platform/statistics/src/com/intellij/internal/statistic/eventLog/StatisticsEventLogger.kt b/platform/statistics/src/com/intellij/internal/statistic/eventLog/StatisticsEventLogger.kt index f497e522d525..176382333b9c 100644 --- a/platform/statistics/src/com/intellij/internal/statistic/eventLog/StatisticsEventLogger.kt +++ b/platform/statistics/src/com/intellij/internal/statistic/eventLog/StatisticsEventLogger.kt @@ -1,8 +1,9 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.internal.statistic.eventLog import com.intellij.ide.plugins.ProductLoadingStrategy import com.intellij.idea.AppMode +import com.intellij.internal.statistic.StatisticsServiceScope import com.intellij.internal.statistic.eventLog.logger.StatisticsEventLogThrottleWriter import com.intellij.internal.statistic.persistence.UsageStatisticsPersistenceComponent import com.intellij.openapi.application.ApplicationManager @@ -10,6 +11,7 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.util.Disposer import com.intellij.platform.runtime.product.ProductMode +import kotlinx.coroutines.* import java.io.File import java.util.* import java.util.concurrent.CompletableFuture @@ -81,6 +83,7 @@ abstract class StatisticsEventLoggerProvider(val recorderId: String, } } + open val coroutineScope: CoroutineScope = StatisticsServiceScope.getScope() private val localLogger: StatisticsEventLogger by lazy { createLocalLogger() } private val actualLogger: StatisticsEventLogger by lazy { createLogger() } internal val eventLogSystemLogger: EventLogSystemCollector by lazy { EventLogSystemCollector(this) } @@ -137,14 +140,15 @@ abstract class StatisticsEventLoggerProvider(val recorderId: String, val configService = EventLogConfigOptionsService.getInstance() val throttledWriter = StatisticsEventLogThrottleWriter( - configService, recorderId, version.toString(), writer + configService, recorderId, version.toString(), writer, coroutineScope ) val logger = StatisticsFileEventLogger( recorderId, config.sessionId, isHeadless, eventLogConfiguration.build, config.bucket.toString(), version.toString(), throttledWriter, UsageStatisticsPersistenceComponent.getInstance(), createEventsMergeStrategy(), ideMode, productMode ) - Disposer.register(ApplicationManager.getApplication(), logger) + + coroutineScope.coroutineContext.job.invokeOnCompletion { Disposer.dispose(logger) } return logger } diff --git a/platform/statistics/src/com/intellij/internal/statistic/eventLog/logger/StatisticsEventLogThrottleWriter.kt b/platform/statistics/src/com/intellij/internal/statistic/eventLog/logger/StatisticsEventLogThrottleWriter.kt index b399fb7eb60c..12c3345a2913 100644 --- a/platform/statistics/src/com/intellij/internal/statistic/eventLog/logger/StatisticsEventLogThrottleWriter.kt +++ b/platform/statistics/src/com/intellij/internal/statistic/eventLog/logger/StatisticsEventLogThrottleWriter.kt @@ -1,21 +1,24 @@ -// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.internal.statistic.eventLog.logger -import com.intellij.application.subscribe import com.intellij.internal.statistic.eventLog.* import com.intellij.internal.statistic.eventLog.EventLogConfigOptionsService.EventLogThresholdConfigOptionsListener import com.intellij.internal.statistic.utils.EventRateThrottleResult import com.intellij.internal.statistic.utils.EventsIdentityWindowThrottle import com.intellij.internal.statistic.utils.EventsRateWindowThrottle +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.service import com.intellij.openapi.util.Disposer import com.jetbrains.fus.reporting.model.lion3.LogEvent import com.jetbrains.fus.reporting.model.lion3.LogEventAction import com.jetbrains.fus.reporting.model.lion3.LogEventGroup +import kotlinx.coroutines.CoroutineScope internal class StatisticsEventLogThrottleWriter(configOptionsService: EventLogConfigOptionsService, private val recorderId: String, private val recorderVersion: String, - private val delegate: StatisticsEventLogWriter) : StatisticsEventLogWriter { + private val delegate: StatisticsEventLogWriter, + coroutineScope: CoroutineScope) : StatisticsEventLogWriter { private val ourLock: Any = Object() /** @@ -37,7 +40,7 @@ internal class StatisticsEventLogThrottleWriter(configOptionsService: EventLogCo val groupAlertThreshold = getOrDefault(configOptions.groupAlertThreshold, 6000) ourGroupThrottle = EventsIdentityWindowThrottle(groupThreshold, groupAlertThreshold, 60L * 60 * 1000) - EventLogConfigOptionsService.TOPIC.subscribe(this, object : EventLogThresholdConfigOptionsListener(recorderId) { + ApplicationManager.getApplication().messageBus.connect(coroutineScope).subscribe(EventLogConfigOptionsService.TOPIC, object : EventLogThresholdConfigOptionsListener(recorderId) { override fun onThresholdChanged(newValue: Int) { if (newValue > 0) { synchronized(ourLock) {