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 consumer) { + return ProgressManager.getInstance().computePrioritized(() -> doFilterElements(base, parameters, indicator, consumer)); + } + + private boolean doFilterElements(@NotNull ChooseByNameBase base, + @NotNull FindSymbolParameters parameters, + @NotNull ProgressIndicator indicator, + @NotNull Processor consumer) { long start = System.currentTimeMillis(); try { String pattern = parameters.getCompletePattern(); @@ -268,7 +275,7 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider { } boolean processName(String name) { - ProgressManager.checkCanceled(); + indicator.checkCanceled(); int position = findMatchStartingPosition(name, namePattern); if (position < namePattern.length()) { candidateNames.get(position).add(name); diff --git a/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java b/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java index e8b916e59ef9..e4cc76646e6a 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java +++ b/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java @@ -32,6 +32,7 @@ import com.intellij.openapi.fileEditor.TextEditor; import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.openapi.keymap.KeymapUtil; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.*; @@ -154,8 +155,11 @@ public class FileStructurePopup implements Disposable, TreeActionsOwner { if (!ApplicationManager.getApplication().isUnitTestMode() && myPopup.isDisposed()) { return; } - super.rebuildTree(); - myFilteringStructure.rebuild(); + ProgressManager.getInstance().computePrioritized(() -> { + super.rebuildTree(); + myFilteringStructure.rebuild(); + return null; + }); } @Override diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/DefaultChooseByNameItemProvider.java b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/DefaultChooseByNameItemProvider.java index d5ab06e52dd7..c7788d233e9d 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/DefaultChooseByNameItemProvider.java +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/DefaultChooseByNameItemProvider.java @@ -51,8 +51,9 @@ public class DefaultChooseByNameItemProvider implements ChooseByNameInScopeItemP @NotNull FindSymbolParameters parameters, @NotNull ProgressIndicator indicator, @NotNull Processor consumer) { - return filterElements(base, indicator, myContext == null ? null : myContext.getElement(), - () -> base.getNames(parameters.isSearchInLibraries()), consumer, parameters); + return ProgressManager.getInstance().computePrioritized( + () -> filterElements(base, indicator, myContext == null ? null : myContext.getElement(), + () -> base.getNames(parameters.isSearchInLibraries()), consumer, parameters)); } /** diff --git a/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java b/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java index 0eac5749d941..cc73f8fcb4c2 100644 --- a/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java +++ b/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java @@ -7,10 +7,10 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.impl.CoreProgressManager; import com.intellij.openapi.progress.util.StandardProgressIndicatorBase; import com.intellij.util.Consumer; import com.intellij.util.Processor; -import com.intellij.util.io.storage.HeavyProcessLatch; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -41,10 +41,13 @@ public class JobLauncherImpl extends JobLauncher { Boolean result = processImmediatelyIfTooFew(things, wrapper, runInReadAction, thingProcessor); if (result != null) return result.booleanValue(); - HeavyProcessLatch.INSTANCE.stopThreadPrioritizing(); + ProgressManager pm = ProgressManager.getInstance(); + Processor processor = ((CoreProgressManager)pm).isPrioritizedThread(Thread.currentThread()) + ? t -> pm.computePrioritized(() -> thingProcessor.process(t)) + : thingProcessor; List> failedSubTasks = Collections.synchronizedList(new ArrayList<>()); - ApplierCompleter applier = new ApplierCompleter<>(null, runInReadAction, failFastOnAcquireReadAction, wrapper, things, thingProcessor, 0, things.size(), failedSubTasks, null); + ApplierCompleter applier = new ApplierCompleter<>(null, runInReadAction, failFastOnAcquireReadAction, wrapper, things, processor, 0, things.size(), failedSubTasks, null); try { ForkJoinPool.commonPool().execute(applier); // call checkCanceled a bit more often than .invoke() @@ -202,8 +205,6 @@ public class JobLauncherImpl extends JobLauncher { // waits for the job to finish execution (when called on a canceled job in the middle of the execution, wait for finish) @Override public void waitForCompletion(int millis) throws InterruptedException, ExecutionException, TimeoutException { - HeavyProcessLatch.INSTANCE.stopThreadPrioritizing(); - while (!isDone()) { try { myForkJoinTask.get(millis, TimeUnit.MILLISECONDS); diff --git a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java index 3360d14a327d..6797bb09ce52 100644 --- a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java +++ b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java @@ -14,6 +14,7 @@ import com.intellij.openapi.actionSystem.Shortcut; import com.intellij.openapi.actionSystem.impl.ActionManagerImpl; import com.intellij.openapi.application.*; import com.intellij.openapi.application.impl.LaterInvocator; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.FrequentEventDetector; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.keymap.Keymap; @@ -21,6 +22,7 @@ import com.intellij.openapi.keymap.KeymapManager; import com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher; import com.intellij.openapi.keymap.impl.IdeMouseEventDispatcher; import com.intellij.openapi.keymap.impl.KeyState; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.ui.JBPopupMenu; import com.intellij.openapi.util.*; import com.intellij.openapi.util.registry.Registry; @@ -36,7 +38,6 @@ import com.intellij.util.ReflectionUtil; import com.intellij.util.SystemProperties; import com.intellij.util.concurrency.NonUrgentExecutor; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.io.storage.HeavyProcessLatch; import com.intellij.util.lang.JavaVersion; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -72,6 +73,7 @@ public class IdeEventQueue extends EventQueue { private static final Logger FOCUS_AWARE_RUNNABLES_LOG = Logger.getInstance("#com.intellij.ide.IdeEventQueue.runnables"); private static final boolean JAVA11_ON_MAC = SystemInfo.isMac && SystemInfo.isJavaVersionAtLeast(11, 0, 0); private static TransactionGuardImpl ourTransactionGuard; + private static ProgressManager ourProgressManager; /** * Adding/Removing of "idle" listeners should be thread safe. @@ -382,15 +384,21 @@ public class IdeEventQueue extends EventQueue { AWTEvent oldEvent = myCurrentEvent; myCurrentEvent = e; - HeavyProcessLatch.INSTANCE.prioritizeUiActivity(); try (AccessToken ignored = startActivity(e)) { - _dispatchEvent(e); + ProgressManager progressManager = obtainProgressManager(); + if (progressManager != null) { + progressManager.computePrioritized(() -> { + _dispatchEvent(myCurrentEvent); + return null; + }); + } else { + _dispatchEvent(myCurrentEvent); + } } catch (Throwable t) { processException(t); } finally { - HeavyProcessLatch.INSTANCE.stopThreadPrioritizing(); myIsInInputEvent = wasInputEvent; myCurrentEvent = oldEvent; @@ -433,6 +441,15 @@ public class IdeEventQueue extends EventQueue { } } + @Nullable + private static ProgressManager obtainProgressManager() { + ProgressManager manager = ourProgressManager; + if (manager == null) { + ourProgressManager = manager = ServiceManager.getService(ProgressManager.class); + } + return manager; + } + private static boolean isMetaKeyPressedOnLinux(@NotNull AWTEvent e) { if (!Registry.is("keymap.skip.meta.press.on.linux")) return false; boolean metaIsPressed = e instanceof InputEvent && (((InputEvent)e).getModifiersEx() & InputEvent.META_DOWN_MASK) != 0; diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java index 3062b196b302..b81300cfcd19 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java @@ -507,7 +507,6 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App @Override public void dispose() { - HeavyProcessLatch.INSTANCE.stopThreadPrioritizing(); fireApplicationExiting(); ShutDownTracker.getInstance().ensureStopperThreadsFinished(); @@ -1184,7 +1183,6 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App if (!isWriteAccessAllowed()) { assertIsDispatchThread("Write access is allowed from event dispatch thread only"); } - HeavyProcessLatch.INSTANCE.stopThreadPrioritizing(); // let non-cancellable read actions complete faster, if present boolean writeActionPending = myWriteActionPending; if (gatherStatistics && myWriteActionsStack.isEmpty() && !writeActionPending) { ActionPauses.WRITE.started(); diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncEditorLoader.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncEditorLoader.java index bd37149f9b69..e289c8b8b6d0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncEditorLoader.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncEditorLoader.java @@ -85,7 +85,7 @@ public class AsyncEditorLoader { ReadAction .nonBlocking(() -> { waitForCommit(commitDeadline); - Runnable runnable = myTextEditor.loadEditorInBackground(); + Runnable runnable = ProgressManager.getInstance().computePrioritized(() -> myTextEditor.loadEditorInBackground()); future.complete(runnable); return runnable; }) diff --git a/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressManagerImpl.java index 76ab98918073..528b158f9034 100644 --- a/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/progress/impl/ProgressManagerImpl.java @@ -13,9 +13,7 @@ import com.intellij.openapi.progress.util.ProgressWindow; import com.intellij.openapi.util.Key; import com.intellij.openapi.wm.WindowManager; import com.intellij.ui.SystemNotifications; -import com.intellij.util.concurrency.AppExecutorUtil; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.io.storage.HeavyProcessLatch; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; @@ -24,38 +22,13 @@ import javax.swing.*; import java.awt.*; import java.util.Set; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.LockSupport; public class ProgressManagerImpl extends CoreProgressManager implements Disposable { private static final Key SAFE_PROGRESS_INDICATOR = Key.create("SAFE_PROGRESS_INDICATOR"); private final Set myHooks = ContainerUtil.newConcurrentSet(); + private final CheckCanceledHook mySleepHook = __ -> sleepIfNeededToGivePriorityToAnotherThread(); public ProgressManagerImpl() { - HeavyProcessLatch.INSTANCE.addUIActivityListener(new HeavyProcessLatch.HeavyProcessListener() { - private final CheckCanceledHook sleepHook = indicator -> sleepIfNeededToGivePriorityToAnotherThread(); - private final AtomicBoolean scheduled = new AtomicBoolean(); - private final Runnable addHookLater = () -> { - scheduled.set(false); - if (HeavyProcessLatch.INSTANCE.hasPrioritizedThread()) { - addCheckCanceledHook(sleepHook); - } - }; - - @Override - public void processStarted() { - if (scheduled.compareAndSet(false, true)) { - AppExecutorUtil.getAppScheduledExecutorService().schedule(addHookLater, 5, TimeUnit.MILLISECONDS); - } - } - - @Override - public void processFinished() { - removeCheckCanceledHook(sleepHook); - } - - }, this); ExtensionPointImpl.setCheckCanceledAction(ProgressManager::checkCanceled); } @@ -183,11 +156,13 @@ public class ProgressManagerImpl extends CoreProgressManager implements Disposab }; } - private static boolean sleepIfNeededToGivePriorityToAnotherThread() { - if (HeavyProcessLatch.INSTANCE.isInsideLowPriorityThread()) { - LockSupport.parkNanos(1_000_000); - return true; - } - return false; + @Override + protected void prioritizingStarted() { + addCheckCanceledHook(mySleepHook); + } + + @Override + protected void prioritizingFinished() { + removeCheckCanceledHook(mySleepHook); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java index 9c663ab8db31..6faaed0cc59d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java @@ -14,6 +14,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.RuntimeExceptionWithAttachments; import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; import com.intellij.openapi.progress.*; +import com.intellij.openapi.progress.impl.CoreProgressManager; import com.intellij.openapi.progress.impl.ProgressManagerImpl; import com.intellij.openapi.progress.impl.ProgressSuspender; import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase; @@ -259,15 +260,13 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); if (indicator == null) indicator = new EmptyProgressIndicator(); - if (ApplicationManager.getApplication().isDispatchThread()) { - HeavyProcessLatch.INSTANCE.stopThreadPrioritizing(); - } - indicator.pushState(); + ((CoreProgressManager)ProgressManager.getInstance()).suppressPrioritizing(); try (AccessToken ignored = HeavyProcessLatch.INSTANCE.processStarted("Performing indexing task")) { task.performInDumbMode(indicator); } finally { + ((CoreProgressManager)ProgressManager.getInstance()).restorePrioritizing(); indicator.popState(); Disposer.dispose(task); } diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 74920fe1f2cc..257303b04a10 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -1349,8 +1349,8 @@ idea.regexp.search.uses.indices.description=Should IDE find in path use indices idea.io.safe.sync=true idea.io.safe.sync.description=When "Safe Write" is enabled, sync() is invoked after the last write to flush data to disk -ide.prioritize.ui.thread=true -ide.prioritize.ui.thread.description=In presence of UI activity, deprioritizes all other threads for the activity to complete ASAP +ide.prioritize.threads=true +ide.prioritize.threads.description=In presence of UI activity, deprioritizes all other threads for the activity to complete ASAP ide.require.transaction.for.model.changes=true ide.require.transaction.for.model.changes.description=Whether write action can only happen under TransactionGuard diff --git a/platform/util/src/com/intellij/util/io/storage/HeavyProcessLatch.java b/platform/util/src/com/intellij/util/io/storage/HeavyProcessLatch.java index cbe578a1f948..56134beb4144 100644 --- a/platform/util/src/com/intellij/util/io/storage/HeavyProcessLatch.java +++ b/platform/util/src/com/intellij/util/io/storage/HeavyProcessLatch.java @@ -22,12 +22,10 @@ package com.intellij.util.io.storage; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.registry.Registry; import com.intellij.util.EventDispatcher; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; -import javax.swing.*; import java.util.*; public class HeavyProcessLatch { @@ -37,15 +35,6 @@ public class HeavyProcessLatch { private final Set myHeavyProcesses = new THashSet<>(); private final EventDispatcher myEventDispatcher = EventDispatcher.create(HeavyProcessListener.class); - private final EventDispatcher myUIProcessDispatcher = EventDispatcher.create(HeavyProcessListener.class); - private volatile Thread myUiActivityThread; - /** - 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 - */ - private static final int MAX_PRIORITIZATION_MILLIS = 12 * 1000; - private volatile long myPrioritizingStarted; - private final List toExecuteOutOfHeavyActivity = new ArrayList<>(); private HeavyProcessLatch() { @@ -113,11 +102,6 @@ public class HeavyProcessLatch { myEventDispatcher.addListener(listener, parentDisposable); } - public void addUIActivityListener(@NotNull HeavyProcessListener listener, - @NotNull Disposable parentDisposable) { - myUIProcessDispatcher.addListener(listener, parentDisposable); - } - public void executeOutOfHeavyProcess(@NotNull Runnable runnable) { boolean runNow; synchronized (myHeavyProcesses) { @@ -134,69 +118,4 @@ public class HeavyProcessLatch { } } - /** - * Gives current event processed on Swing thread higher priority - * by letting other threads to sleep a bit whenever they call checkCanceled.

- * - * Don't call this method unless you're deep in Swing event dispatching internals. - * @see #stopThreadPrioritizing() - */ - public void prioritizeUiActivity() { - LOG.assertTrue(SwingUtilities.isEventDispatchThread()); - - if (!Registry.is("ide.prioritize.ui.thread", false)) { - return; - } - - myPrioritizingStarted = System.currentTimeMillis(); - - myUiActivityThread = Thread.currentThread(); - myUIProcessDispatcher.getMulticaster().processStarted(); - } - - /** - * Removes priority from Swing thread, if present. Should be invoked before UI thread starts waiting for other threads in idle mode, - * to ensure those other threads complete ASAP.

- * - * If possible, instead of calling this method prefer to move activity into background threads, - * so that UI thread doesn't need to block and wait. - */ - public void stopThreadPrioritizing() { - if (myUiActivityThread == null) return; - - myUiActivityThread = null; - myUIProcessDispatcher.getMulticaster().processFinished(); - } - - /** - * @return whether there is a prioritized thread, but not the current one - */ - public boolean isInsideLowPriorityThread() { - Thread uiThread = myUiActivityThread; - if (uiThread != null && uiThread != Thread.currentThread()) { - Thread.State state = uiThread.getState(); - if (state == Thread.State.WAITING || state == Thread.State.TIMED_WAITING || state == Thread.State.BLOCKED) { - return false; - } - - long time = System.currentTimeMillis() - myPrioritizingStarted; - if (time < 5) { - return false; // don't sleep when EDT activities are very short (e.g. empty processing of mouseMoved events) - } - - if (time > MAX_PRIORITIZATION_MILLIS) { - stopThreadPrioritizing(); - return false; - } - return true; - } - return false; - } - - /** - * @return whether there is a prioritized thread currently - */ - public boolean hasPrioritizedThread() { - return myUiActivityThread != null; - } } \ No newline at end of file