deprioritize other threads when a user performs a UI activity (disabled for now)

This commit is contained in:
peter
2016-02-24 15:36:25 +01:00
parent 7a9a22235c
commit 6066adeff0
10 changed files with 129 additions and 10 deletions
@@ -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<ProgressIndicator> currentIndicators = ContainerUtil.createConcurrentLongObjectMap();
// threads which are running under canceled indicator
static final Set<Thread> 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<ProgressIndicator> 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;
}
}
}
@@ -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
@@ -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<T> 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);
@@ -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;
@@ -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);
}
@@ -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);
}
@@ -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;
@@ -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<K, UI, V extends Component> extends JCompo
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
HeavyProcessLatch.INSTANCE.prioritizeUiActivity();
if (!myDisposed) {
select(callback, key, ui);
}
@@ -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
@@ -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<String> myHeavyProcesses = new THashSet<String>();
private final EventDispatcher<HeavyProcessListener> 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;
}
}