diff --git a/platform/core-api/src/com/intellij/openapi/progress/ProgressManager.java b/platform/core-api/src/com/intellij/openapi/progress/ProgressManager.java
index 757c564678f0..f823b139b900 100644
--- a/platform/core-api/src/com/intellij/openapi/progress/ProgressManager.java
+++ b/platform/core-api/src/com/intellij/openapi/progress/ProgressManager.java
@@ -248,4 +248,13 @@ public abstract class ProgressManager extends ProgressIndicatorProvider {
public abstract boolean runInReadActionWithWriteActionPriority(@NotNull final Runnable action, @Nullable ProgressIndicator indicator);
public abstract boolean isInNonCancelableSection();
+
+ /**
+ * Performs the given computation while giving more priority to the current thread
+ * (by forcing all other non-prioritized threads to sleep a bit whenever they call {@link #checkCanceled()}.
+ *
+ * This is intended for relatively short (expected to be under 10 seconds) background activities that the user is waiting for
+ * (e.g. code navigation), and which shouldn't be slowed down by CPU-intensive background tasks like highlighting or indexing.
+ */
+ public abstract T computePrioritized(@NotNull ThrowableComputable computable) throws E;
}
\ No newline at end of file
diff --git a/platform/core-impl/src/com/intellij/openapi/progress/impl/CoreProgressManager.java b/platform/core-impl/src/com/intellij/openapi/progress/impl/CoreProgressManager.java
index e5e4cd52e772..aaf23556bafc 100644
--- a/platform/core-impl/src/com/intellij/openapi/progress/impl/CoreProgressManager.java
+++ b/platform/core-impl/src/com/intellij/openapi/progress/impl/CoreProgressManager.java
@@ -3,10 +3,12 @@ package com.intellij.openapi.progress.impl;
import com.google.common.collect.ConcurrentHashMultiset;
import com.intellij.concurrency.JobScheduler;
+import com.intellij.diagnostic.ThreadDumper;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ex.ApplicationEx;
+import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.*;
import com.intellij.openapi.project.Project;
@@ -14,8 +16,11 @@ import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.ThrowableComputable;
+import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
import com.intellij.util.ExceptionUtil;
+import com.intellij.util.ObjectUtils;
+import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.containers.ConcurrentLongObjectMap;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.SmartHashSet;
@@ -28,6 +33,7 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.LockSupport;
public class CoreProgressManager extends ProgressManager implements Disposable {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.progress.impl.CoreProgressManager");
@@ -674,6 +680,162 @@ public class CoreProgressManager extends ProgressManager implements Disposable {
return isInNonCancelableSection.get() != null;
}
+ private static final long MAX_PRIORITIZATION_NANOS = TimeUnit.SECONDS.toNanos(12);
+ private static final Thread[] NO_THREADS = new Thread[0];
+ private final Set myPrioritizedThreads = ContainerUtil.newConcurrentSet();
+ private volatile Thread[] myEffectivePrioritizedThreads = NO_THREADS;
+ private volatile int myDeprioritizations = 0;
+ private final Object myPrioritizationLock = ObjectUtils.sentinel("myPrioritizationLock");
+ private volatile long myPrioritizingStarted = 0;
+
+ @Override
+ public T computePrioritized(@NotNull ThrowableComputable computable) throws E {
+ Thread thread = Thread.currentThread();
+
+ if (!Registry.is("ide.prioritize.threads") || isPrioritizedThread(thread)) {
+ return computable.compute();
+ }
+
+ synchronized (myPrioritizationLock) {
+ if (myPrioritizedThreads.isEmpty()) {
+ myPrioritizingStarted = System.nanoTime();
+ }
+ myPrioritizedThreads.add(thread);
+ updateEffectivePrioritized();
+ }
+ try {
+ return computable.compute();
+ }
+ finally {
+ synchronized (myPrioritizationLock) {
+ myPrioritizedThreads.remove(thread);
+ updateEffectivePrioritized();
+ }
+ }
+ }
+
+ private void updateEffectivePrioritized() {
+ Thread[] prev = myEffectivePrioritizedThreads;
+ Thread[] current = myDeprioritizations > 0 || myPrioritizedThreads.isEmpty() ? NO_THREADS : myPrioritizedThreads.toArray(NO_THREADS);
+ myEffectivePrioritizedThreads = current;
+ if (prev.length == 0 && current.length > 0) {
+ prioritizingStarted();
+ } else if (prev.length > 0 && current.length == 0) {
+ prioritizingFinished();
+ }
+ }
+
+ protected void prioritizingStarted() {}
+ protected void prioritizingFinished() {}
+
+ @ApiStatus.Internal
+ public boolean isPrioritizedThread(@NotNull Thread from) {
+ return myPrioritizedThreads.contains(from);
+ }
+
+ @ApiStatus.Internal
+ public void suppressPrioritizing() {
+ synchronized (myPrioritizationLock) {
+ if (++myDeprioritizations == 100) {
+ Attachment attachment = new Attachment("threadDump.txt", ThreadDumper.dumpThreadsToString());
+ attachment.setIncluded(true);
+ LOG.error("A suspiciously high nesting of suppressPrioritizing, forgot to call restorePrioritizing?", attachment);
+ }
+ updateEffectivePrioritized();
+ }
+ }
+
+ @ApiStatus.Internal
+ public void restorePrioritizing() {
+ synchronized (myPrioritizationLock) {
+ if (--myDeprioritizations < 0) {
+ myDeprioritizations = 0;
+ LOG.error("Unmatched suppressPrioritizing/restorePrioritizing");
+ }
+ updateEffectivePrioritized();
+ }
+ }
+
+ protected boolean sleepIfNeededToGivePriorityToAnotherThread() {
+ if (!isCurrentThreadEffectivelyPrioritized() && checkLowPriorityReallyApplicable()) {
+ LockSupport.parkNanos(1_000_000);
+ avoidBlockingPrioritizingThread();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isCurrentThreadEffectivelyPrioritized() {
+ Thread current = Thread.currentThread();
+ for (Thread prioritized : myEffectivePrioritizedThreads) {
+ if (prioritized == current) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkLowPriorityReallyApplicable() {
+ long time = System.nanoTime() - myPrioritizingStarted;
+ if (time < 5_000_000) {
+ return false; // don't sleep when activities are very short (e.g. empty processing of mouseMoved events)
+ }
+
+ if (avoidBlockingPrioritizingThread()) {
+ return false;
+ }
+
+ if (ApplicationManager.getApplication().isDispatchThread()) {
+ return false; // EDT always has high priority
+ }
+
+ if (time > MAX_PRIORITIZATION_NANOS) {
+ // Don't wait forever in case someone forgot to stop prioritizing before waiting for other threads to complete
+ // wait just for 12 seconds; this will be noticeable (and we'll get 2 thread dumps) but not fatal
+ stopAllPrioritization();
+ return false;
+ }
+ return true;
+ }
+
+ private boolean avoidBlockingPrioritizingThread() {
+ if (isAnyPrioritizedThreadBlocked()) {
+ // the current thread could hold a lock that prioritized threads are waiting for
+ suppressPrioritizing();
+ checkLaterThreadsAreUnblocked();
+ return true;
+ }
+ return false;
+ }
+
+ private void checkLaterThreadsAreUnblocked() {
+ AppExecutorUtil.getAppScheduledExecutorService().schedule(() -> {
+ if (isAnyPrioritizedThreadBlocked()) {
+ checkLaterThreadsAreUnblocked();
+ }
+ else {
+ restorePrioritizing();
+ }
+ }, 5, TimeUnit.MILLISECONDS);
+ }
+
+ private void stopAllPrioritization() {
+ synchronized (myPrioritizationLock) {
+ myPrioritizedThreads.clear();
+ updateEffectivePrioritized();
+ }
+ }
+
+ private boolean isAnyPrioritizedThreadBlocked() {
+ for (Thread thread : myEffectivePrioritizedThreads) {
+ Thread.State state = thread.getState();
+ if (state == Thread.State.WAITING || state == Thread.State.TIMED_WAITING || state == Thread.State.BLOCKED) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@NotNull
public static ModalityState getCurrentThreadProgressModality() {
ProgressIndicator indicator = threadTopLevelIndicators.get(Thread.currentThread().getId());
diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/tree/LazyParseableElement.java b/platform/core-impl/src/com/intellij/psi/impl/source/tree/LazyParseableElement.java
index ab0cd73e7dcc..1b414069c95a 100644
--- a/platform/core-impl/src/com/intellij/psi/impl/source/tree/LazyParseableElement.java
+++ b/platform/core-impl/src/com/intellij/psi/impl/source/tree/LazyParseableElement.java
@@ -20,7 +20,6 @@
package com.intellij.psi.impl.source.tree;
import com.intellij.diagnostic.PluginException;
-import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.diagnostic.LogUtil;
import com.intellij.openapi.diagnostic.Logger;
@@ -31,7 +30,6 @@ import com.intellij.psi.impl.DebugUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.ILazyParseableElementTypeBase;
import com.intellij.reference.SoftReference;
-import com.intellij.util.io.storage.HeavyProcessLatch;
import com.intellij.util.text.CharArrayUtil;
import com.intellij.util.text.ImmutableCharSequence;
import org.jetbrains.annotations.NonNls;
@@ -177,12 +175,6 @@ public class LazyParseableElement extends CompositeElement {
}
if (myParsed) return;
- if (ApplicationManager.getApplication().isDispatchThread()) {
- // we don't want to wait under lock on EDT while another thread is parsing the same chameleon
- // and sleeping in ProgressManagerImpl.sleepIfNeededToGivePriorityToAnotherThread because EDT is occupied
- HeavyProcessLatch.INSTANCE.stopThreadPrioritizing();
- }
-
CharSequence text;
synchronized (lock) {
if (myParsed) return;
@@ -214,7 +206,7 @@ public class LazyParseableElement extends CompositeElement {
myText = new SoftReference<>(text);
});
}
-}
+ }
private void assertTextLengthIntact(CharSequence text, TreeElement child) {
int length = 0;
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java
index 5c3f7776bc2f..1d46df3b5080 100644
--- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java
+++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java
@@ -50,7 +50,6 @@ import com.intellij.ui.components.JBList;
import com.intellij.util.CollectConsumer;
import com.intellij.util.ExceptionUtil;
import com.intellij.util.containers.ContainerUtil;
-import com.intellij.util.io.storage.HeavyProcessLatch;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.accessibility.AccessibleContextUtil;
import com.intellij.util.ui.accessibility.ScreenReader;
@@ -1262,9 +1261,6 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable,
}
private T withLock(Computable computable) {
- if (ApplicationManager.getApplication().isDispatchThread()) {
- HeavyProcessLatch.INSTANCE.stopThreadPrioritizing();
- }
synchronized (myArrangerLock) {
return computable.compute();
}
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/navigation/actions/GotoDeclarationAction.java b/platform/lang-impl/src/com/intellij/codeInsight/navigation/actions/GotoDeclarationAction.java
index 9768030bad63..c75ba367e1c5 100644
--- a/platform/lang-impl/src/com/intellij/codeInsight/navigation/actions/GotoDeclarationAction.java
+++ b/platform/lang-impl/src/com/intellij/codeInsight/navigation/actions/GotoDeclarationAction.java
@@ -35,6 +35,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
@@ -135,7 +136,8 @@ public class GotoDeclarationAction extends BaseCodeInsightAction implements Code
return ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
DumbService.getInstance(project).setAlternativeResolveEnabled(true);
try {
- return ApplicationManager.getApplication().runReadAction(computable);
+ ThrowableComputable inRead = () -> ApplicationManager.getApplication().runReadAction(computable);
+ return ProgressManager.getInstance().computePrioritized(inRead);
}
finally {
DumbService.getInstance(project).setAlternativeResolveEnabled(false);
diff --git a/platform/lang-impl/src/com/intellij/ide/actions/GotoFileItemProvider.java b/platform/lang-impl/src/com/intellij/ide/actions/GotoFileItemProvider.java
index 878a382578b8..7b7dd5a34a9b 100644
--- a/platform/lang-impl/src/com/intellij/ide/actions/GotoFileItemProvider.java
+++ b/platform/lang-impl/src/com/intellij/ide/actions/GotoFileItemProvider.java
@@ -56,6 +56,13 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider {
@NotNull FindSymbolParameters parameters,
@NotNull ProgressIndicator indicator,
@NotNull Processor