From 56920bd51b7d8d03ecd6cd0eb91d5e1974e529bc Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 29 May 2019 16:20:01 +0200 Subject: [PATCH] add ProgressManager#computePrioritized to be able to speed up bg calculations during heavy activities like indexing and use it in Goto File, Structure View and editor loading this generalizes the approach from HeavyProcessLatch#prioritizeUiThread GitOrigin-RevId: 4ae6510f1f8a4a887cb7d3c7e243adf76f5bd6f7 --- .../openapi/progress/ProgressManager.java | 9 + .../progress/impl/CoreProgressManager.java | 162 ++++++++++++++++++ .../source/tree/LazyParseableElement.java | 10 +- .../codeInsight/lookup/impl/LookupImpl.java | 4 - .../actions/GotoDeclarationAction.java | 4 +- .../ide/actions/GotoFileItemProvider.java | 9 +- .../intellij/ide/util/FileStructurePopup.java | 8 +- .../DefaultChooseByNameItemProvider.java | 5 +- .../intellij/concurrency/JobLauncherImpl.java | 11 +- .../src/com/intellij/ide/IdeEventQueue.java | 25 ++- .../application/impl/ApplicationImpl.java | 2 - .../impl/text/AsyncEditorLoader.java | 2 +- .../progress/impl/ProgressManagerImpl.java | 43 +---- .../openapi/project/DumbServiceImpl.java | 7 +- .../util/resources/misc/registry.properties | 4 +- .../util/io/storage/HeavyProcessLatch.java | 81 --------- 16 files changed, 234 insertions(+), 152 deletions(-) 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