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 707eb4eb9607..38726864980b 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 @@ -27,10 +27,13 @@ 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.TimeoutUtil; import com.intellij.util.containers.ConcurrentLongObjectMap; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.SmartHashSet; +import com.intellij.util.io.storage.HeavyProcessLatch; import gnu.trove.THashMap; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -51,6 +54,7 @@ public class CoreProgressManager extends ProgressManager implements Disposable { private final AtomicInteger myCurrentModalProgressCount = new AtomicInteger(0); private static final boolean ENABLED = !"disabled".equals(System.getProperty("idea.ProcessCanceledException")); + private static final boolean ourMaySleepInCheckCanceled = Registry.is("ide.prioritize.ui.thread", false); private ScheduledFuture myCheckCancelledFuture; // guarded by threadsUnderIndicator // indicator -> threads which are running under this indicator. guarded by threadsUnderIndicator. @@ -59,7 +63,7 @@ public class CoreProgressManager extends ProgressManager implements Disposable { private static final ConcurrentLongObjectMap currentIndicators = ContainerUtil.createConcurrentLongObjectMap(); // threads which are running under canceled indicator static final Set threadsUnderCanceledIndicator = ContainerUtil.newConcurrentSet(); - private static volatile boolean thereIsProcessUnderCanceledIndicator; + private static volatile boolean shouldCheckCanceled; /** active (i.e. which have {@link #executeProcessUnderProgress(Runnable, ProgressIndicator)} method running) indicators * which are not inherited from {@link StandardProgressIndicator}. @@ -68,6 +72,21 @@ public class CoreProgressManager extends ProgressManager implements Disposable { */ private static final Collection nonStandardIndicators = ConcurrentHashMultiset.create(); + public CoreProgressManager() { + HeavyProcessLatch.INSTANCE.addListener(this, new HeavyProcessLatch.HeavyProcessListener() { + @Override + public void processStarted() { + updateShouldCheckCanceled(); + } + + @Override + public void processFinished() { + updateShouldCheckCanceled(); + } + + }); + } + @NotNull private ScheduledFuture startBackgroundIndicatorPing() { return JobScheduler.getScheduler().scheduleWithFixedDelay(new Runnable() { @@ -95,13 +114,24 @@ public class CoreProgressManager extends ProgressManager implements Disposable { } } + public boolean sleepIfNeeded() { + if (ourMaySleepInCheckCanceled && HeavyProcessLatch.INSTANCE.isInsideLowPriorityThread()) { + TimeoutUtil.sleep(1); + return true; + } + return false; + } + @Override protected void doCheckCanceled() throws ProcessCanceledException { - if (thereIsProcessUnderCanceledIndicator) { - final ProgressIndicator progress = getProgressIndicator(); - if (progress != null && ENABLED) { - progress.checkCanceled(); - } + if (!shouldCheckCanceled) return; + + final ProgressIndicator progress = getProgressIndicator(); + if (progress != null && ENABLED) { + progress.checkCanceled(); + } + else { + sleepIfNeeded(); } } @@ -202,6 +232,7 @@ public class CoreProgressManager extends ProgressManager implements Disposable { } }, null); + //noinspection ThrowableResultOfMethodCallIgnored Throwable t = exception.get(); if (t != null) { if (t instanceof Error) throw (Error)t; @@ -434,7 +465,7 @@ public class CoreProgressManager extends ProgressManager implements Disposable { } } - thereIsProcessUnderCanceledIndicator = !threadsUnderCanceledIndicator.isEmpty(); + updateShouldCheckCanceled(); } try { @@ -467,11 +498,22 @@ public class CoreProgressManager extends ProgressManager implements Disposable { threadsUnderCanceledIndicator.remove(currentThread); } } - thereIsProcessUnderCanceledIndicator = !threadsUnderCanceledIndicator.isEmpty(); + updateShouldCheckCanceled(); } } } + private static void updateShouldCheckCanceled() { + if (ourMaySleepInCheckCanceled && HeavyProcessLatch.INSTANCE.hasPrioritizedThread()) { + shouldCheckCanceled = true; + return; + } + + synchronized (threadsUnderIndicator) { + shouldCheckCanceled = !threadsUnderCanceledIndicator.isEmpty(); + } + } + @Override protected void indicatorCanceled(@NotNull ProgressIndicator indicator) { // mark threads running under this indicator as canceled @@ -492,7 +534,8 @@ public class CoreProgressManager extends ProgressManager implements Disposable { if (underCancelledIndicator) { threadsUnderCanceledIndicator.add(thread); - thereIsProcessUnderCanceledIndicator = true; + //noinspection AssignmentToStaticFieldFromInstanceMethod + shouldCheckCanceled = true; } } } diff --git a/platform/core-impl/src/com/intellij/openapi/progress/util/AbstractProgressIndicatorBase.java b/platform/core-impl/src/com/intellij/openapi/progress/util/AbstractProgressIndicatorBase.java index 57490f6cfe7f..cec29fed579a 100644 --- a/platform/core-impl/src/com/intellij/openapi/progress/util/AbstractProgressIndicatorBase.java +++ b/platform/core-impl/src/com/intellij/openapi/progress/util/AbstractProgressIndicatorBase.java @@ -22,6 +22,7 @@ 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.util.UserDataHolderBase; import com.intellij.ui.mac.foundation.MacUtil; import com.intellij.util.containers.ContainerUtil; @@ -34,6 +35,7 @@ import java.util.Set; public class AbstractProgressIndicatorBase extends UserDataHolderBase implements ProgressIndicatorStacked { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.progress.util.ProgressIndicatorBase"); + private static final CoreProgressManager ourProgressManager = (CoreProgressManager)ProgressManager.getInstance(); private volatile String myText; private volatile double myFraction; @@ -126,6 +128,11 @@ public class AbstractProgressIndicatorBase extends UserDataHolderBase implements if (isCanceled() && isCancelable()) { throw new ProcessCanceledException(); } + if (ourProgressManager.sleepIfNeeded()) { + if (isCanceled() && isCancelable()) { + throw new ProcessCanceledException(); + } + } } @Override diff --git a/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java b/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java index 41a35bc2e87a..fc705c315f06 100644 --- a/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java +++ b/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java @@ -24,6 +24,7 @@ import com.intellij.openapi.progress.util.AbstractProgressIndicatorBase; import com.intellij.util.Consumer; import com.intellij.util.IncorrectOperationException; import com.intellij.util.Processor; +import com.intellij.util.io.storage.HeavyProcessLatch; import jsr166e.ForkJoinPool; import jsr166e.ForkJoinTask; import jsr166e.ForkJoinWorkerThread; @@ -96,6 +97,8 @@ public class JobLauncherImpl extends JobLauncher { Boolean result = processImmediatelyIfTooFew(things, wrapper, runInReadAction, thingProcessor); if (result != null) return result.booleanValue(); + HeavyProcessLatch.INSTANCE.stopThreadPrioritizing(); + ApplierCompleter applier = new ApplierCompleter<>(null, runInReadAction, wrapper, things, thingProcessor, 0, things.size(), null); try { pool.invoke(applier); @@ -269,6 +272,8 @@ 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 5c84967309aa..b7b57f9ae21b 100644 --- a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java +++ b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java @@ -47,6 +47,7 @@ import com.intellij.util.Alarm; import com.intellij.util.ReflectionUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; +import com.intellij.util.io.storage.HeavyProcessLatch; import com.intellij.util.ui.MouseEventAdapter; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -379,6 +380,9 @@ public class IdeEventQueue extends EventQueue { boolean wasInputEvent = myIsInInputEvent; myIsInInputEvent = e instanceof InputEvent || e instanceof InputMethodEvent || e instanceof WindowEvent || e instanceof ActionEvent; + if (myIsInInputEvent) { + HeavyProcessLatch.INSTANCE.prioritizeUiActivity(); + } AWTEvent oldEvent = myCurrentEvent; myCurrentEvent = e; 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 9e8ca14e3726..78c5a1dd47f5 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 @@ -544,6 +544,7 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App @Override public void dispose() { + HeavyProcessLatch.INSTANCE.stopThreadPrioritizing(); fireApplicationExiting(); ShutDownTracker.getInstance().ensureStopperThreadsFinished(); @@ -1221,6 +1222,7 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App private void startWrite(/*@NotNull*/ Class clazz) { assertIsDispatchThread(getStatus(), "Write access is allowed from event dispatch thread only"); + HeavyProcessLatch.INSTANCE.stopThreadPrioritizing(); // let non-cancellable read actions complete faster, if present boolean writeActionPending = myWriteActionPending; myWriteActionPending = true; if (gatherWriteActionStatistics && myWriteActionsStack.isEmpty()) { @@ -1435,6 +1437,7 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App if (myDoNotSave) return; if (mySaveSettingsIsInProgress.compareAndSet(false, true)) { + HeavyProcessLatch.INSTANCE.prioritizeUiActivity(); try { StoreUtil.save(ServiceKt.getStateStore(this), null); } diff --git a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectImpl.java index 603c035b81a1..b066e9826de9 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectImpl.java @@ -32,6 +32,7 @@ import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressIndicatorProvider; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbAwareRunnable; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; @@ -47,6 +48,7 @@ import com.intellij.openapi.wm.WindowManager; import com.intellij.openapi.wm.impl.FrameTitleBuilder; import com.intellij.psi.impl.DebugUtil; import com.intellij.util.TimedReference; +import com.intellij.util.io.storage.HeavyProcessLatch; import com.intellij.util.pico.ConstructorInjectionComponentAdapter; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -310,6 +312,8 @@ public class ProjectImpl extends PlatformComponentManagerImpl implements Project return; } + HeavyProcessLatch.INSTANCE.prioritizeUiActivity(); + try { StoreUtil.save(ServiceKt.getStateStore(this), this); } diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeFrameImpl.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeFrameImpl.java index 74dc45d0ba6c..11be75a591cb 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeFrameImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeFrameImpl.java @@ -34,6 +34,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.ex.ApplicationInfoEx; import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbAwareRunnable; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; @@ -52,6 +53,7 @@ import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame; import com.intellij.ui.*; import com.intellij.ui.mac.MacMainFrameDecorator; import com.intellij.util.Alarm; +import com.intellij.util.io.storage.HeavyProcessLatch; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -260,6 +262,8 @@ public class IdeFrameImpl extends JFrame implements IdeFrameEx, DataProvider { final Application app = ApplicationManager.getApplication(); app.invokeLater(new DumbAwareRunnable() { public void run() { + HeavyProcessLatch.INSTANCE.prioritizeUiActivity(); + if (app.isDisposed()) { ApplicationManagerEx.getApplicationEx().exit(); return; diff --git a/platform/platform-impl/src/com/intellij/ui/CardLayoutPanel.java b/platform/platform-impl/src/com/intellij/ui/CardLayoutPanel.java index 6f5eff4ba987..4649ba58efa8 100644 --- a/platform/platform-impl/src/com/intellij/ui/CardLayoutPanel.java +++ b/platform/platform-impl/src/com/intellij/ui/CardLayoutPanel.java @@ -19,6 +19,7 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.util.ActionCallback; +import com.intellij.util.io.storage.HeavyProcessLatch; import com.intellij.util.ui.JBInsets; import javax.accessibility.Accessible; @@ -130,6 +131,7 @@ public abstract class CardLayoutPanel extends JCompo ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { + HeavyProcessLatch.INSTANCE.prioritizeUiActivity(); if (!myDisposed) { select(callback, key, ui); } diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index c35e2c225079..8823c7c03b8a 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -710,5 +710,8 @@ idea.regexp.search.uses.indices.description=Should Idea 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=false +ide.prioritize.ui.thread.description=In presence of UI activity, deprioritizes all other threads for the activity to complete ASAP. Changing requires restart. + dumb.aware.run.configurations=false dumb.aware.run.configurations.description=Enable executing run configurations in dumb mode 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 3bd6468d289e..430872786150 100644 --- a/platform/util/src/com/intellij/util/io/storage/HeavyProcessLatch.java +++ b/platform/util/src/com/intellij/util/io/storage/HeavyProcessLatch.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,14 +25,17 @@ import com.intellij.util.EventDispatcher; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; +import javax.swing.*; import java.util.EventListener; import java.util.Set; public class HeavyProcessLatch { public static final HeavyProcessLatch INSTANCE = new HeavyProcessLatch(); + private static final String UI_ACTIVITY = "UI Activity"; private final Set myHeavyProcesses = new THashSet(); private final EventDispatcher myEventDispatcher = EventDispatcher.create(HeavyProcessListener.class); + private volatile Thread myUiActivityThread; private HeavyProcessLatch() { } @@ -95,4 +98,45 @@ public class HeavyProcessLatch { public void addListener(@NotNull Disposable parentDisposable, @NotNull HeavyProcessListener listener) { myEventDispatcher.addListener(listener, parentDisposable); } + + /** + * Gives current event processed on Swing thread higher priority + * @see #stopThreadPrioritizing() + */ + public void prioritizeUiActivity() { + myUiActivityThread = Thread.currentThread(); + processStarted(UI_ACTIVITY); + //noinspection SSBasedInspection + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + stopThreadPrioritizing(); + } + }); + } + + /** + * Removes priority from Swing thread, if present. Should be invoked before a thread starts waiting for other threads in idle mode, + * to ensure those other threads complete ASAP. + * @see #prioritizeUiActivity() + */ + public void stopThreadPrioritizing() { + myUiActivityThread = null; + processFinished(UI_ACTIVITY); + } + + /** + * @return whether there is a prioritized thread, but not the current one + */ + public boolean isInsideLowPriorityThread() { + Thread thread = myUiActivityThread; + return thread != null && thread != Thread.currentThread(); + } + + /** + * @return whether there is a prioritized thread currently + */ + public boolean hasPrioritizedThread() { + return myUiActivityThread != null; + } } \ No newline at end of file