mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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
This commit is contained in:
committed by
intellij-monorepo-bot
parent
5a8170b90e
commit
56920bd51b
@@ -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()}.<p></p>
|
||||
*
|
||||
* 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, E extends Throwable> T computePrioritized(@NotNull ThrowableComputable<T, E> computable) throws E;
|
||||
}
|
||||
@@ -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<Thread> 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, E extends Throwable> T computePrioritized(@NotNull ThrowableComputable<T, E> 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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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> T withLock(Computable<T> computable) {
|
||||
if (ApplicationManager.getApplication().isDispatchThread()) {
|
||||
HeavyProcessLatch.INSTANCE.stopThreadPrioritizing();
|
||||
}
|
||||
synchronized (myArrangerLock) {
|
||||
return computable.compute();
|
||||
}
|
||||
|
||||
+3
-1
@@ -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<T, RuntimeException> inRead = () -> ApplicationManager.getApplication().runReadAction(computable);
|
||||
return ProgressManager.getInstance().computePrioritized(inRead);
|
||||
}
|
||||
finally {
|
||||
DumbService.getInstance(project).setAlternativeResolveEnabled(false);
|
||||
|
||||
@@ -56,6 +56,13 @@ public class GotoFileItemProvider extends DefaultChooseByNameItemProvider {
|
||||
@NotNull FindSymbolParameters parameters,
|
||||
@NotNull ProgressIndicator indicator,
|
||||
@NotNull Processor<Object> consumer) {
|
||||
return ProgressManager.getInstance().computePrioritized(() -> doFilterElements(base, parameters, indicator, consumer));
|
||||
}
|
||||
|
||||
private boolean doFilterElements(@NotNull ChooseByNameBase base,
|
||||
@NotNull FindSymbolParameters parameters,
|
||||
@NotNull ProgressIndicator indicator,
|
||||
@NotNull Processor<Object> 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);
|
||||
|
||||
@@ -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
|
||||
|
||||
+3
-2
@@ -51,8 +51,9 @@ public class DefaultChooseByNameItemProvider implements ChooseByNameInScopeItemP
|
||||
@NotNull FindSymbolParameters parameters,
|
||||
@NotNull ProgressIndicator indicator,
|
||||
@NotNull Processor<Object> 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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<? super T> processor = ((CoreProgressManager)pm).isPrioritizedThread(Thread.currentThread())
|
||||
? t -> pm.computePrioritized(() -> thingProcessor.process(t))
|
||||
: thingProcessor;
|
||||
|
||||
List<ApplierCompleter<T>> failedSubTasks = Collections.synchronizedList(new ArrayList<>());
|
||||
ApplierCompleter<T> applier = new ApplierCompleter<>(null, runInReadAction, failFastOnAcquireReadAction, wrapper, things, thingProcessor, 0, things.size(), failedSubTasks, null);
|
||||
ApplierCompleter<T> 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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
+1
-1
@@ -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;
|
||||
})
|
||||
|
||||
+9
-34
@@ -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<Boolean> SAFE_PROGRESS_INDICATOR = Key.create("SAFE_PROGRESS_INDICATOR");
|
||||
private final Set<CheckCanceledHook> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String> myHeavyProcesses = new THashSet<>();
|
||||
private final EventDispatcher<HeavyProcessListener> myEventDispatcher = EventDispatcher.create(HeavyProcessListener.class);
|
||||
|
||||
private final EventDispatcher<HeavyProcessListener> 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<Runnable> 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.<p></p>
|
||||
*
|
||||
* 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.<p></p>
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user