From dacd8338ef038b100de50eb2af4e98db1162d0c2 Mon Sep 17 00:00:00 2001 From: Daniel M Date: Tue, 23 Apr 2024 12:06:05 -0400 Subject: [PATCH] fix:mem leak --- CHANGELOG.md | 6 ++++ gradle.properties | 2 +- .../data/JobsLoadingModelListener.kt | 4 +-- .../ghmanager/data/LogLoadingModelListener.kt | 8 ++--- .../data/WorkflowRunSelectionContext.kt | 2 ++ ...ionCache.kt => GitHubActionDataService.kt} | 29 ++++++++++++++----- .../ghmanager/psi/HighlightAnnotator.kt | 17 ++++------- .../dsoftware/ghmanager/psi/ProjectStartup.kt | 4 +-- .../ghmanager/ui/RepoTabController.kt | 4 +-- 9 files changed, 46 insertions(+), 30 deletions(-) rename src/main/kotlin/com/dsoftware/ghmanager/psi/{GitHubActionCache.kt => GitHubActionDataService.kt} (92%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20db787..5b1d898 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ ## Unreleased +## 1.21.4 + +### 🐛 Bug Fixes + +- Fix memory leak with log panel + ## 1.21.3 ### 🐛 Bug Fixes diff --git a/gradle.properties b/gradle.properties index 2d51c76..e8a9d3d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ pluginGroup=com.dsoftware.ghmanager pluginName=github-actions-manager # SemVer format -> https://semver.org -pluginVersion=1.21.3 +pluginVersion=1.21.4 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild=241.13688.18 diff --git a/src/main/kotlin/com/dsoftware/ghmanager/data/JobsLoadingModelListener.kt b/src/main/kotlin/com/dsoftware/ghmanager/data/JobsLoadingModelListener.kt index 4570b67..43d669f 100644 --- a/src/main/kotlin/com/dsoftware/ghmanager/data/JobsLoadingModelListener.kt +++ b/src/main/kotlin/com/dsoftware/ghmanager/data/JobsLoadingModelListener.kt @@ -10,10 +10,10 @@ import org.jetbrains.plugins.github.pullrequest.ui.GHLoadingModel class JobsLoadingModelListener( - workflowRunDisposable: Disposable, + parentDisposable: Disposable, dataProviderModel: SingleValueModel ) : GHLoadingModel.StateChangeListener { - val jobsLoadingModel = GHCompletableFutureLoadingModel(workflowRunDisposable) + val jobsLoadingModel = GHCompletableFutureLoadingModel(parentDisposable) init { var listenerDisposable: Disposable? = null diff --git a/src/main/kotlin/com/dsoftware/ghmanager/data/LogLoadingModelListener.kt b/src/main/kotlin/com/dsoftware/ghmanager/data/LogLoadingModelListener.kt index 771bd92..5ddd35b 100644 --- a/src/main/kotlin/com/dsoftware/ghmanager/data/LogLoadingModelListener.kt +++ b/src/main/kotlin/com/dsoftware/ghmanager/data/LogLoadingModelListener.kt @@ -15,7 +15,7 @@ enum class LogValueStatus { data class LogValue(val log: String?, val status: LogValueStatus, val jobName: String? = null) class LogLoadingModelListener( - workflowRunDisposable: Disposable, + parentDisposable: Disposable, logDataProvider: SingleValueModel, private val jobsSelectionHolder: JobListSelectionHolder, ) : GHLoadingModel.StateChangeListener { @@ -26,10 +26,10 @@ class LogLoadingModelListener( * When the provider is null, it means no workflow-run/job is selected. */ val logValueModel = SingleValueModel(null) - val logsLoadingModel = GHCompletableFutureLoadingModel(workflowRunDisposable) + val logsLoadingModel = GHCompletableFutureLoadingModel(parentDisposable) init { - jobsSelectionHolder.addSelectionChangeListener(workflowRunDisposable, this::setLogValue) + jobsSelectionHolder.addSelectionChangeListener(parentDisposable, this::setLogValue) logsLoadingModel.addStateChangeListener(this) var listenerDisposable: Disposable? = null logDataProvider.addAndInvokeListener { provider -> @@ -41,7 +41,7 @@ class LogLoadingModelListener( } provider?.let { val newDisposable = Disposer.newDisposable("Log listener disposable").apply { - Disposer.register(workflowRunDisposable, this) + Disposer.register(parentDisposable, this) } it.addRunChangesListener(newDisposable) { logsLoadingModel.future = it.processValue diff --git a/src/main/kotlin/com/dsoftware/ghmanager/data/WorkflowRunSelectionContext.kt b/src/main/kotlin/com/dsoftware/ghmanager/data/WorkflowRunSelectionContext.kt index 8b716e5..6d9945f 100644 --- a/src/main/kotlin/com/dsoftware/ghmanager/data/WorkflowRunSelectionContext.kt +++ b/src/main/kotlin/com/dsoftware/ghmanager/data/WorkflowRunSelectionContext.kt @@ -90,12 +90,14 @@ class WorkflowRunSelectionContext internal constructor( setNewLogProvider() selectedRunDisposable.dispose() selectedRunDisposable = Disposer.newDisposable("Selected run disposable") + Disposer.register(this, selectedRunDisposable) } jobSelectionHolder.addSelectionChangeListener(this) { LOG.debug("jobSelectionHolder selection change listener") setNewLogProvider() selectedJobDisposable.dispose() selectedJobDisposable = Disposer.newDisposable("Selected job disposable") + Disposer.register(this, selectedJobDisposable) } } diff --git a/src/main/kotlin/com/dsoftware/ghmanager/psi/GitHubActionCache.kt b/src/main/kotlin/com/dsoftware/ghmanager/psi/GitHubActionDataService.kt similarity index 92% rename from src/main/kotlin/com/dsoftware/ghmanager/psi/GitHubActionCache.kt rename to src/main/kotlin/com/dsoftware/ghmanager/psi/GitHubActionDataService.kt index 3bb3d9e..c3b8ab8 100644 --- a/src/main/kotlin/com/dsoftware/ghmanager/psi/GitHubActionCache.kt +++ b/src/main/kotlin/com/dsoftware/ghmanager/psi/GitHubActionDataService.kt @@ -10,6 +10,8 @@ import com.google.common.cache.Cache import com.google.common.cache.CacheBuilder import com.intellij.collaboration.api.dto.GraphQLRequestDTO import com.intellij.collaboration.api.dto.GraphQLResponseDTO +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.runReadAction import com.intellij.openapi.components.PersistentStateComponent import com.intellij.openapi.components.Service import com.intellij.openapi.components.State @@ -22,7 +24,7 @@ import com.intellij.openapi.util.Disposer import com.intellij.util.EventDispatcher import com.intellij.util.ResourceUtil import com.intellij.util.ThrowableConvertor -import com.intellij.util.concurrency.annotations.RequiresEdt +import com.intellij.util.concurrency.annotations.RequiresBackgroundThread import kotlinx.serialization.Serializable import org.jetbrains.plugins.github.api.GithubApiContentHelper import org.jetbrains.plugins.github.api.GithubApiRequest.Post @@ -38,8 +40,13 @@ import java.util.EventListener import java.util.concurrent.ScheduledFuture @Service(Service.Level.PROJECT) -@State(name = "GhActionsManagerSettings.ActionsCache", storages = [Storage(StoragePathMacros.PRODUCT_WORKSPACE_FILE)]) -class GitHubActionCache(private val project: Project) : PersistentStateComponent { +@State( + name = "GhActionsManagerSettings.ActionsDataCache", + storages = [Storage(StoragePathMacros.PRODUCT_WORKSPACE_FILE)] +) +class GitHubActionDataService( + private val project: Project +) : PersistentStateComponent, Disposable { val actionsCache: Cache = CacheBuilder.newBuilder() .expireAfterWrite(Duration.ofHours(1)) .maximumSize(200) @@ -86,7 +93,6 @@ class GitHubActionCache(private val project: Project) : PersistentStateComponent } actionsToResolve.add(actionName) addListener(listenerMethod) - resolveActions() } fun whenActionsLoaded(listenerMethod: () -> Unit) { @@ -95,7 +101,6 @@ class GitHubActionCache(private val project: Project) : PersistentStateComponent return } addListener(listenerMethod) - resolveActions() } fun getAction(fullActionName: String): GitHubAction? { @@ -110,15 +115,19 @@ class GitHubActionCache(private val project: Project) : PersistentStateComponent private fun addListener(listenerMethod: () -> Unit) { val listenerDisposable = Disposer.newDisposable() + Disposer.register(this, listenerDisposable) actionsLoadedEventDispatcher.addListener(object : ActionsLoadedListener { override fun actionsLoaded() { - listenerMethod() + runReadAction(listenerMethod) listenerDisposable.dispose() // Ensure listener will only run once } }, listenerDisposable) } private fun resolveActions() { + if (actionsToResolve.isEmpty()) { + return + } actionsToResolve.removeAll(actionsCache.asMap().keys) actionsToResolve.forEach { resolveGithubAction(it) @@ -138,7 +147,7 @@ class GitHubActionCache(private val project: Project) : PersistentStateComponent } } - @RequiresEdt + @RequiresBackgroundThread private fun resolveGithubAction(fullActionName: String) { if (actionsCache.getIfPresent(fullActionName) != null) { LOG.debug("Action $fullActionName already resolved") @@ -250,6 +259,10 @@ class GitHubActionCache(private val project: Project) : PersistentStateComponent companion object { - private val LOG = logger() + private val LOG = logger() + } + + override fun dispose() { + task.cancel(true) } } \ No newline at end of file diff --git a/src/main/kotlin/com/dsoftware/ghmanager/psi/HighlightAnnotator.kt b/src/main/kotlin/com/dsoftware/ghmanager/psi/HighlightAnnotator.kt index bf50fb0..e0a5f16 100644 --- a/src/main/kotlin/com/dsoftware/ghmanager/psi/HighlightAnnotator.kt +++ b/src/main/kotlin/com/dsoftware/ghmanager/psi/HighlightAnnotator.kt @@ -24,28 +24,23 @@ class HighlightAnnotator : Annotator { } private fun highlightAction(yamlKeyValue: YAMLKeyValue, holder: AnnotationHolder) { - val gitHubActionCache = yamlKeyValue.project.service() + val gitHubActionDataService = yamlKeyValue.project.service() val actionName = yamlKeyValue.valueText.split("@").firstOrNull() ?: return val currentVersion = yamlKeyValue.valueText.split("@").getOrNull(1) ?: return - gitHubActionCache.whenActionsLoaded { - val latestVersion = gitHubActionCache.getAction(actionName)?.latestVersion + gitHubActionDataService.whenActionLoaded(actionName) { + val latestVersion = gitHubActionDataService.getAction(actionName)?.latestVersion if (VersionCompareTools.isActionOutdated(currentVersion, latestVersion)) { val message = "$currentVersion is outdated. Latest version is $latestVersion" + val startIndex = yamlKeyValue.textRange.startOffset + yamlKeyValue.text.indexOf("@") + 1 val annotationBuilder = holder .newAnnotation(HighlightSeverity.WARNING, message) - .range( - TextRange.create( - yamlKeyValue.textRange.startOffset - + yamlKeyValue.text.indexOf("@") + 1, - yamlKeyValue.textRange.endOffset - ) - ) + .range(TextRange.create(startIndex, yamlKeyValue.textRange.endOffset)) val inspectionManager = yamlKeyValue.project.service() val quickfix = UpdateActionVersionFix(actionName, latestVersion!!) val problemDescriptor = inspectionManager.createProblemDescriptor( yamlKeyValue, message, quickfix, ProblemHighlightType.WEAK_WARNING, true - ); + ) annotationBuilder .newLocalQuickFix(quickfix, problemDescriptor) .registerFix() diff --git a/src/main/kotlin/com/dsoftware/ghmanager/psi/ProjectStartup.kt b/src/main/kotlin/com/dsoftware/ghmanager/psi/ProjectStartup.kt index ef84812..475b778 100644 --- a/src/main/kotlin/com/dsoftware/ghmanager/psi/ProjectStartup.kt +++ b/src/main/kotlin/com/dsoftware/ghmanager/psi/ProjectStartup.kt @@ -38,12 +38,12 @@ class ProjectStartup : ProjectActivity { } runReadAction { val psiManager = project.service() - val gitHubActionCache = project.service() + val gitHubActionDataService = project.service() psiManager.findFile(workflowFile)?.let { val actionNames = Tools.getYamlElementsWithKey(it, FIELD_USES).map { yamlKeyValue -> yamlKeyValue.valueText.split("@").firstOrNull() ?: return@map null }.filterNotNull() - gitHubActionCache.actionsToResolve.addAll(actionNames) + gitHubActionDataService.actionsToResolve.addAll(actionNames) } } } diff --git a/src/main/kotlin/com/dsoftware/ghmanager/ui/RepoTabController.kt b/src/main/kotlin/com/dsoftware/ghmanager/ui/RepoTabController.kt index 1b46d01..a95c24e 100644 --- a/src/main/kotlin/com/dsoftware/ghmanager/ui/RepoTabController.kt +++ b/src/main/kotlin/com/dsoftware/ghmanager/ui/RepoTabController.kt @@ -122,7 +122,7 @@ class RepoTabController( private fun createLogPanel(selectedRunContext: WorkflowRunSelectionContext): JComponent { LOG.debug("Create log panel") val model = LogLoadingModelListener( - selectedRunContext.selectedJobDisposable, + selectedRunContext, selectedRunContext.logDataProviderLoadModel, selectedRunContext.jobSelectionHolder ) @@ -139,7 +139,7 @@ class RepoTabController( private fun createJobsPanel(selectedRunContext: WorkflowRunSelectionContext): JComponent { val jobsLoadingModel = JobsLoadingModelListener( - selectedRunContext.selectedRunDisposable, selectedRunContext.jobDataProviderLoadModel + selectedRunContext, selectedRunContext.jobDataProviderLoadModel ) val jobsPanel = GHLoadingPanelFactory(