From 9b0cb377b48dea10717aed4d8e88eba0935fd45c Mon Sep 17 00:00:00 2001 From: Sergey Patrikeev Date: Wed, 5 Aug 2020 09:55:37 +0300 Subject: [PATCH] IDEA-247316: Fix "Pausing Indexing Applies to All Windows Rather than Current Project". What caused the indexing to stop for all projects: 1) IndexUpdateRunner creates worker threads in a common pool of the fixed size N. These worker threads are used to index different projects one by one. 2) When indexing of some project is suspended, all worker threads are blocked (on object monitor of the "ProgressSuspender.freezeIfNeeded") on "ProgressIndicator.checkCancelled" of the corresponding project and there is no worker thread that can finish indexing of other projects. To fix this, we need to skip IndexingJob-s that have suspended progress indicator and to temporarily disable suspension when indexing a single file. GitOrigin-RevId: 5b69a41279557f14ee251d5669656a454ccaf569 --- .../contentQueue/IndexUpdateRunner.java | 51 ++++++++++++++++--- .../progress/impl/ProgressSuspender.java | 16 ++++++ 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/util/indexing/contentQueue/IndexUpdateRunner.java b/platform/lang-impl/src/com/intellij/util/indexing/contentQueue/IndexUpdateRunner.java index 571656078016..4520ae470792 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/contentQueue/IndexUpdateRunner.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/contentQueue/IndexUpdateRunner.java @@ -5,6 +5,8 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.WrappedProgressIndicator; +import com.intellij.openapi.progress.impl.ProgressSuspender; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtilRt; @@ -82,7 +84,9 @@ public final class IndexUpdateRunner { indicator.setIndeterminate(false); CachedFileContentLoader contentLoader = new CurrentProjectHintedCachedFileContentLoader(project); - IndexingJob indexingJob = new IndexingJob(project, indicator, contentLoader, files); + ProgressIndicator originalIndicator = unwrapAll(indicator); + ProgressSuspender originalSuspender = ProgressSuspender.getSuspender(originalIndicator); + IndexingJob indexingJob = new IndexingJob(project, indicator, contentLoader, files, originalIndicator, originalSuspender); if (ApplicationManager.getApplication().isWriteAccessAllowed()) { // If the current thread has acquired the write lock, we can't grant it to worker threads, so we must do the work in the current thread. while (!indexingJob.areAllFilesProcessed()) { @@ -106,12 +110,13 @@ public final class IndexUpdateRunner { numberOfRunningWorkers.incrementAndGet(); } while (!project.isDisposed() && !indexingJob.areAllFilesProcessed() && indexingJob.myError.get() == null) { - // Add a worker if the previous died for whatever reason, to avoid waiting for nothing. - if (numberOfRunningWorkers.get() < myNumberOfIndexingThreads) { + // Internally checks for suspension of the indexing and blocks the current thread if necessary. + indicator.checkCanceled(); + // Add workers if the previous have stopped for whatever reason. + while (numberOfRunningWorkers.get() < myNumberOfIndexingThreads) { myIndexingExecutor.execute(worker); numberOfRunningWorkers.incrementAndGet(); } - indicator.checkCanceled(); try { if (indexingJob.myAllFilesAreProcessedLatch.await(100, TimeUnit.MILLISECONDS)) { break; @@ -140,22 +145,40 @@ public final class IndexUpdateRunner { // Drops finished, cancelled and failed jobs from {@code ourIndexingJobs}. Does not throw exceptions. private void indexJobsFairly() { while (!ourIndexingJobs.isEmpty()) { + boolean allJobsAreSuspended = true; for (IndexingJob job : ourIndexingJobs) { + ProgressIndicator jobIndicator = job.myIndicator; if (job.myProject.isDisposed() || job.myNoMoreFilesInQueue.get() - || job.myIndicator.isCanceled() + || jobIndicator.isCanceled() || job.myError.get() != null) { ourIndexingJobs.remove(job); + allJobsAreSuspended = false; continue; } + ProgressSuspender suspender = job.myOriginalProgressSuspender; + if (suspender != null && suspender.isSuspended()) { + continue; + } + allJobsAreSuspended = false; try { - indexOneFileOfJob(job); + Runnable work = () -> indexOneFileOfJob(job); + if (suspender != null) { + // Here it is important to use the original progress indicator which is directly associated with the ProgressSuspender. + suspender.executeNonSuspendableSection(job.myOriginalProgressIndicator, work); + } else { + work.run(); + } } catch (Throwable e) { job.myError.compareAndSet(null, e); ourIndexingJobs.remove(job); } } + if (allJobsAreSuspended) { + // To avoid busy-looping. + break; + } } } @@ -355,6 +378,14 @@ public final class IndexUpdateRunner { return file.getPath(); } + private static @NotNull ProgressIndicator unwrapAll(@NotNull ProgressIndicator indicator) { + // Can't use "ProgressWrapper.unwrapAll" here because it unwraps "ProgressWrapper"s only (not any "WrappedProgressIndicator") + while (indicator instanceof WrappedProgressIndicator) { + indicator = ((WrappedProgressIndicator)indicator).getOriginalProgressIndicator(); + } + return indicator; + } + private static class IndexingJob { final Project myProject; final CachedFileContentLoader myContentLoader; @@ -363,19 +394,25 @@ public final class IndexUpdateRunner { final int myTotalFiles; final AtomicBoolean myNoMoreFilesInQueue = new AtomicBoolean(); final CountDownLatch myAllFilesAreProcessedLatch; + final ProgressIndicator myOriginalProgressIndicator; + @Nullable final ProgressSuspender myOriginalProgressSuspender; final IndexingJobStatistics myStatistics = new IndexingJobStatistics(); final AtomicReference myError = new AtomicReference<>(); IndexingJob(@NotNull Project project, @NotNull ProgressIndicator indicator, @NotNull CachedFileContentLoader contentLoader, - @NotNull Collection files) { + @NotNull Collection files, + @NotNull ProgressIndicator originalProgressIndicator, + @Nullable ProgressSuspender originalProgressSuspender) { myProject = project; myIndicator = indicator; myTotalFiles = files.size(); myContentLoader = contentLoader; myQueueOfFiles = new ArrayBlockingQueue<>(files.size(), false, files); myAllFilesAreProcessedLatch = new CountDownLatch(files.size()); + myOriginalProgressIndicator = originalProgressIndicator; + myOriginalProgressSuspender = originalProgressSuspender; } public void oneMoreFileProcessed() { diff --git a/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressSuspender.java b/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressSuspender.java index ceaf5136628e..763a2e351579 100644 --- a/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressSuspender.java +++ b/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressSuspender.java @@ -19,7 +19,9 @@ import org.jetbrains.annotations.Nullable; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** * @author peter @@ -37,6 +39,7 @@ public final class ProgressSuspender implements AutoCloseable { private volatile boolean mySuspended; private final CoreProgressManager.CheckCanceledHook myHook = this::freezeIfNeeded; private final Set myProgresses = ContainerUtil.newConcurrentSet(); + private final Map myProgressesInNonSuspendableSections = new ConcurrentHashMap<>(); private boolean myClosed; private ProgressSuspender(@NotNull ProgressIndicatorEx progress, @NotNull String suspendedText) { @@ -73,6 +76,15 @@ public final class ProgressSuspender implements AutoCloseable { return new ProgressSuspender((ProgressIndicatorEx)indicator, suspendedText); } + public void executeNonSuspendableSection(@NotNull ProgressIndicator indicator, @NotNull Runnable runnable) { + myProgressesInNonSuspendableSections.compute(indicator, ((__, number) -> (number == null ? 0 : number) + 1)); + try { + runnable.run(); + } finally { + myProgressesInNonSuspendableSections.compute(indicator, (__, number) -> (number == null || number <= 1 ? null : number - 1)); + } + } + @Nullable public static ProgressSuspender getSuspender(@NotNull ProgressIndicator indicator) { return indicator instanceof UserDataHolder ? ((UserDataHolder)indicator).getUserData(PROGRESS_SUSPENDER) : null; @@ -143,6 +155,10 @@ public final class ProgressSuspender implements AutoCloseable { return false; } + if (myProgressesInNonSuspendableSections.containsKey(current)) { + return false; + } + synchronized (myLock) { while (mySuspended) { try {