From 1bcb0c10d97f53e67c4051c3442b75cedec523d9 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Sat, 13 Jul 2024 17:31:11 +0200 Subject: [PATCH] IJPL-158075 GeneratedSourceFileChangeTrackerImpl - migrate from SingleAlarm to flow GitOrigin-RevId: a2a8a0ae346c6d6673dce8aad7bbc7faaffa87ae --- platform/lang-impl/api-dump-unreviewed.txt | 6 -- .../GeneratedSourceFileChangeTrackerImpl.kt | 88 ++++++++++++++----- .../GeneratedSourceFileChangeTrackerTest.java | 10 +-- 3 files changed, 69 insertions(+), 35 deletions(-) diff --git a/platform/lang-impl/api-dump-unreviewed.txt b/platform/lang-impl/api-dump-unreviewed.txt index 5d1015f84459..184d162ae637 100644 --- a/platform/lang-impl/api-dump-unreviewed.txt +++ b/platform/lang-impl/api-dump-unreviewed.txt @@ -12720,12 +12720,6 @@ a:com.intellij.ide.GeneratedSourceFileChangeTracker - ():V - s:getInstance(com.intellij.openapi.project.Project):com.intellij.ide.GeneratedSourceFileChangeTracker - a:isEditedGeneratedFile(com.intellij.openapi.vfs.VirtualFile):Z -f:com.intellij.ide.GeneratedSourceFileChangeTrackerImpl -- com.intellij.ide.GeneratedSourceFileChangeTracker -- s:IN_TRACKER_TEST:Z -- (com.intellij.openapi.project.Project,kotlinx.coroutines.CoroutineScope):V -- cancelAllAndWait(J,java.util.concurrent.TimeUnit):V -- isEditedGeneratedFile(com.intellij.openapi.vfs.VirtualFile):Z f:com.intellij.ide.NativeIconProvider - com.intellij.ide.IconProvider - com.intellij.openapi.project.DumbAware diff --git a/platform/lang-impl/src/com/intellij/ide/GeneratedSourceFileChangeTrackerImpl.kt b/platform/lang-impl/src/com/intellij/ide/GeneratedSourceFileChangeTrackerImpl.kt index b778414e6bad..f36d5a6f254e 100644 --- a/platform/lang-impl/src/com/intellij/ide/GeneratedSourceFileChangeTrackerImpl.kt +++ b/platform/lang-impl/src/com/intellij/ide/GeneratedSourceFileChangeTrackerImpl.kt @@ -1,9 +1,13 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:OptIn(FlowPreview::class) +@file:Suppress("SSBasedInspection") + package com.intellij.ide import com.intellij.ide.impl.ProjectUtilCore import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.ReadAction +import com.intellij.openapi.application.readAction +import com.intellij.openapi.components.serviceAsync import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.event.DocumentEvent import com.intellij.openapi.editor.event.DocumentListener @@ -18,27 +22,48 @@ import com.intellij.openapi.roots.* import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.LightVirtualFile import com.intellij.ui.EditorNotifications -import com.intellij.util.SingleAlarm -import com.intellij.util.SingleAlarm.Companion.singleAlarm -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.takeWhile +import org.jetbrains.annotations.ApiStatus.Internal import org.jetbrains.annotations.TestOnly import java.util.* import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicReference +@Internal class GeneratedSourceFileChangeTrackerImpl( private val project: Project, private val coroutineScope: CoroutineScope ) : GeneratedSourceFileChangeTracker() { - private val checkingQueue: SingleAlarm private val filesToCheck: MutableSet = Collections.synchronizedSet(HashSet()) private val editedGeneratedFiles: MutableSet = Collections.synchronizedSet(HashSet()) + private val checkRequests = MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + private val checkRequestsJob = AtomicReference() + init { - checkingQueue = singleAlarm(500, coroutineScope, Runnable { this.checkFiles() }) + startHandlingCheckRequests() + } + + private fun startHandlingCheckRequests() { + checkRequestsJob.getAndSet(coroutineScope.launch { + checkRequests + .takeWhile { it } + .debounce(500) + .collectLatest { + checkFiles() + } + })?.cancel() } companion object { // static non-final by design + @JvmField var IN_TRACKER_TEST: Boolean = false private fun isListenerInactive(): Boolean = !IN_TRACKER_TEST && ApplicationManager.getApplication().isUnitTestMode @@ -47,15 +72,34 @@ class GeneratedSourceFileChangeTrackerImpl( @TestOnly fun waitForAlarm(timeout: Long, timeUnit: TimeUnit) { check(!ApplicationManager.getApplication().isWriteAccessAllowed) { "Must not wait for the alarm under write action" } - checkingQueue.waitForAllExecuted(timeout, timeUnit) + try { + val job = checkRequestsJob.getAndSet(null) ?: return + runBlocking { + checkRequests.emit(false) + withTimeout(timeUnit.toMillis(timeout)) { + job.join() + } + } + } + finally { + startHandlingCheckRequests() + } } @TestOnly @Throws(Exception::class) fun cancelAllAndWait(timeout: Long, timeUnit: TimeUnit) { filesToCheck.clear() - checkingQueue.cancel() - waitForAlarm(timeout, timeUnit) + runBlocking { + try { + withTimeout(timeUnit.toMillis(timeout)) { + checkRequestsJob.get()?.cancelAndJoin() + } + } + finally { + startHandlingCheckRequests() + } + } } override fun isEditedGeneratedFile(file: VirtualFile): Boolean = editedGeneratedFiles.contains(file) @@ -85,7 +129,7 @@ class GeneratedSourceFileChangeTrackerImpl( // the background activities of all projects val fileChangeTracker = getInstance(project) as GeneratedSourceFileChangeTrackerImpl fileChangeTracker.filesToCheck.add(file) - fileChangeTracker.checkingQueue.cancelAndRequest() + check(fileChangeTracker.checkRequests.tryEmit(true)) } } } @@ -128,10 +172,10 @@ class GeneratedSourceFileChangeTrackerImpl( private fun resetOnRootsChanged() { filesToCheck.addAll(editedGeneratedFiles) editedGeneratedFiles.clear() - checkingQueue.cancelAndRequest() + check(checkRequests.tryEmit(true)) } - private fun checkFiles() { + private suspend fun checkFiles() { val files = synchronized(filesToCheck) { filesToCheck.toList().also { filesToCheck.clear() @@ -141,27 +185,23 @@ class GeneratedSourceFileChangeTrackerImpl( return } - val i = intArrayOf(0) + val counter = AtomicInteger() - val fileIndex = ProjectFileIndex.getInstance(project) - ReadAction.nonBlocking { - if (project.isDisposed) { - return@nonBlocking - } - - val newEditedGeneratedFiles: MutableList = ArrayList() + val fileIndex = project.serviceAsync() + readAction { + val newEditedGeneratedFiles = ArrayList() try { - while (i[0] < files.size) { + while (counter.get() < files.size) { ProgressManager.checkCanceled() - val file = files[i[0]] + val file = files[counter.get()] if (fileIndex.isInContent(file) || fileIndex.isInLibrary(file)) { if (GeneratedSourcesFilter.isGeneratedSourceByAnyFilter(file, project)) { newEditedGeneratedFiles.add(file) } } - i[0]++ + counter.incrementAndGet() } } finally { @@ -172,6 +212,6 @@ class GeneratedSourceFileChangeTrackerImpl( EditorNotifications.getInstance(project).updateAllNotifications() } } - }.executeSynchronously() + } } } diff --git a/platform/lang-impl/testSources/com/intellij/ide/GeneratedSourceFileChangeTrackerTest.java b/platform/lang-impl/testSources/com/intellij/ide/GeneratedSourceFileChangeTrackerTest.java index 4a0fe14d1718..f5546595d6e4 100644 --- a/platform/lang-impl/testSources/com/intellij/ide/GeneratedSourceFileChangeTrackerTest.java +++ b/platform/lang-impl/testSources/com/intellij/ide/GeneratedSourceFileChangeTrackerTest.java @@ -1,4 +1,4 @@ -// Copyright 2000-2019 JetBrains s.r.o. 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.ide; import com.intellij.openapi.project.Project; @@ -35,26 +35,26 @@ public class GeneratedSourceFileChangeTrackerTest extends CodeInsightFixtureTest super.tearDown(); } - public void testChangeOrdinary() throws Exception { + public void testChangeOrdinary() { PsiFile file = myFixture.configureByText("Ordinary.txt", ""); myFixture.type('a'); assertFalse(isEditedGeneratedFile(file)); } - public void testChangeGenerated() throws Exception { + public void testChangeGenerated() { PsiFile file = myFixture.configureByText("Gen.txt", ""); myFixture.type('a'); assertTrue(isEditedGeneratedFile(file)); } - public void testChangeGeneratedExternally() throws Exception { + public void testChangeGeneratedExternally() { PsiFile file = myFixture.configureByText("Gen.txt", ""); myFixture.saveText(file.getVirtualFile(), "abc"); PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); assertFalse(isEditedGeneratedFile(file)); } - private boolean isEditedGeneratedFile(PsiFile file) throws Exception { + private boolean isEditedGeneratedFile(PsiFile file) { GeneratedSourceFileChangeTrackerImpl tracker = (GeneratedSourceFileChangeTrackerImpl)getTracker(); tracker.waitForAlarm(10, TimeUnit.SECONDS); return tracker.isEditedGeneratedFile(file.getVirtualFile());