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: f4efe2f589602482ab8746d06ad67bc810f973ee
This commit is contained in:
Victor Matchenko
2024-10-24 16:27:06 +02:00
committed by intellij-monorepo-bot
parent 32413fb37c
commit ec2f17b852
4 changed files with 39 additions and 7 deletions

View File

@@ -166,6 +166,7 @@ a:com.intellij.internal.statistic.eventLog.StatisticsEventLoggerProvider
- b:<init>(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

View File

@@ -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<StatisticsServiceProjectScope>().scope
fun getScope(): CoroutineScope = service<StatisticsServiceApplicationScope>().scope
}
}
@ApiStatus.Internal
@Service(Service.Level.APP)
class StatisticsServiceApplicationScope(val scope: CoroutineScope)
@ApiStatus.Internal
@Service(Service.Level.PROJECT)
class StatisticsServiceProjectScope(val scope: CoroutineScope)

View File

@@ -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
}

View File

@@ -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) {