make isInNonCancelableSection() an instance method to open possibilities for optimisations; test added

This commit is contained in:
Alexey Kudravtsev
2016-11-10 15:59:29 +03:00
parent b034ae9a4b
commit fc71870d63
4 changed files with 70 additions and 27 deletions
@@ -141,9 +141,9 @@ public abstract class ProgressManager extends ProgressIndicatorProvider {
@Nullable JComponent parentComponent);
/**
* Runs a specified <code>process</code> in a background thread and shows a progress dialog, which can be made non-modal by pressing
* background button. Upon successful termination of the process a <code>successRunnable</code> will be called in Swing UI thread and
* <code>canceledRunnable</code> will be called if terminated on behalf of the user by pressing either cancel button, while running in
* Runs a specified {@code process} in a background thread and shows a progress dialog, which can be made non-modal by pressing
* background button. Upon successful termination of the process a {@code successRunnable} will be called in Swing UI thread and
* {@code canceledRunnable} will be called if terminated on behalf of the user by pressing either cancel button, while running in
* a modal state or stop button if running in background.
*
* @param project the project in the context of which the operation is executed.
@@ -159,9 +159,9 @@ public abstract class ProgressManager extends ProgressIndicatorProvider {
@Nullable Runnable successRunnable,
@Nullable Runnable canceledRunnable);
/**
* Runs a specified <code>process</code> in a background thread and shows a progress dialog, which can be made non-modal by pressing
* background button. Upon successful termination of the process a <code>successRunnable</code> will be called in Swing UI thread and
* <code>canceledRunnable</code> will be called if terminated on behalf of the user by pressing either cancel button, while running in
* Runs a specified {@code process} in a background thread and shows a progress dialog, which can be made non-modal by pressing
* background button. Upon successful termination of the process a {@code successRunnable} will be called in Swing UI thread and
* {@code canceledRunnable} will be called if terminated on behalf of the user by pressing either cancel button, while running in
* a modal state or stop button if running in background.
*
* @param project the project in the context of which the operation is executed.
@@ -180,7 +180,7 @@ public abstract class ProgressManager extends ProgressIndicatorProvider {
@NotNull PerformInBackgroundOption option);
/**
* Runs a specified <code>task</code> in either background/foreground thread and shows a progress dialog.
* Runs a specified {@code task} in either background/foreground thread and shows a progress dialog.
*
* @param task task to run (either {@link Task.Modal} or {@link Task.Backgroundable}).
*/
@@ -226,7 +226,7 @@ public abstract class ProgressManager extends ProgressIndicatorProvider {
* This method attempts to run provided action synchronously in a read action, so that, if possible, it wouldn't impact any pending,
* executing or future write actions (for this to work effectively the action should invoke {@link ProgressManager#checkCanceled()} or
* {@link ProgressIndicator#checkCanceled()} often enough).
* It returns <code>true</code> if action was executed successfully. It returns <code>false</code> if the action was not
* It returns {@code true} if action was executed successfully. It returns {@code false} if the action was not
* executed successfully, i.e. if:
* <ul>
* <li>write action was in progress when the method was called</li>
@@ -240,17 +240,5 @@ public abstract class ProgressManager extends ProgressIndicatorProvider {
*/
public abstract boolean runInReadActionWithWriteActionPriority(@NotNull final Runnable action);
public static boolean isInNonCancelableSection() {
ProgressIndicator indicator = getGlobalProgressIndicator();
while (indicator != null) {
if (indicator instanceof NonCancelableSection) return true;
if (indicator instanceof WrappedProgressIndicator) {
indicator = ((WrappedProgressIndicator)indicator).getOriginalProgressIndicator();
}
else {
break;
}
}
return false;
}
public abstract boolean isInNonCancelableSection();
}
@@ -21,6 +21,7 @@ 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.Logger;
import com.intellij.openapi.progress.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
@@ -50,6 +51,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class CoreProgressManager extends ProgressManager implements Disposable {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.progress.impl.CoreProgressManager");
static final int CHECK_CANCELED_DELAY_MILLIS = 10;
final AtomicInteger myCurrentUnsafeProgressCount = new AtomicInteger(0);
private final AtomicInteger myCurrentModalProgressCount = new AtomicInteger(0);
@@ -76,6 +79,11 @@ public class CoreProgressManager extends ProgressManager implements Disposable {
// multiset here (instead of a set) is for simplifying add/remove indicators on process-with-progress start/end with possibly identical indicators.
private static final Collection<ProgressIndicator> nonStandardIndicators = ConcurrentHashMultiset.create();
/** true if running in non-cancelable section started with
* {@link #startNonCancelableSection()} or {@link #executeNonCancelableSection(Runnable)} in this thread
*/
private static final ThreadLocal<Boolean> isInNonCancelableSection = new ThreadLocal<Boolean>(); // do not supply initial value to conserve memory
public CoreProgressManager() {
HeavyProcessLatch.INSTANCE.addUIActivityListener(new HeavyProcessLatch.HeavyProcessListener() {
@Override
@@ -206,7 +214,18 @@ public class CoreProgressManager extends ProgressManager implements Disposable {
@Override
public void executeNonCancelableSection(@NotNull Runnable runnable) {
executeProcessUnderProgress(runnable, NonCancelableIndicator.INSTANCE);
if (isInNonCancelableSection()) {
runnable.run();
}
else {
try {
isInNonCancelableSection.set(Boolean.TRUE);
executeProcessUnderProgress(runnable, NonCancelableIndicator.INSTANCE);
}
finally {
isInNonCancelableSection.remove();
}
}
}
@Override
@@ -656,19 +675,28 @@ public class CoreProgressManager extends ProgressManager implements Disposable {
@NotNull
@Override
public final NonCancelableSection startNonCancelableSection() {
LOG.warn("Use executeNonCancelableSection() instead");
if (isInNonCancelableSection()) return NonCancelableSection.EMPTY;
final ProgressIndicator myOld = getProgressIndicator();
final Thread currentThread = Thread.currentThread();
NonCancelableIndicator nonCancelor = new NonCancelableIndicator() {
final NonCancelableIndicator nonCancelor = new NonCancelableIndicator() {
@Override
public void done() {
setCurrentIndicator(currentThread, myOld);
isInNonCancelableSection.remove();
}
};
isInNonCancelableSection.set(Boolean.TRUE);
setCurrentIndicator(currentThread, nonCancelor);
return nonCancelor;
}
@Override
public boolean isInNonCancelableSection() {
return isInNonCancelableSection.get() != null;
}
@NotNull
public static ModalityState getCurrentThreadProgressModality() {
ProgressIndicator indicator = threadTopLevelIndicators.get(Thread.currentThread().getId());
@@ -710,15 +738,16 @@ public class CoreProgressManager extends ProgressManager implements Disposable {
return myTask.toString();
}
}
protected static class TaskRunnable extends TaskContainer {
static class TaskRunnable extends TaskContainer {
private final ProgressIndicator myIndicator;
private final Runnable myContinuation;
public TaskRunnable(@NotNull Task task, @NotNull ProgressIndicator indicator) {
TaskRunnable(@NotNull Task task, @NotNull ProgressIndicator indicator) {
this(task, indicator, null);
}
public TaskRunnable(@NotNull Task task, @NotNull ProgressIndicator indicator, @Nullable Runnable continuation) {
TaskRunnable(@NotNull Task task, @NotNull ProgressIndicator indicator, @Nullable Runnable continuation) {
super(task);
myIndicator = indicator;
myContinuation = continuation;
@@ -386,7 +386,7 @@ public class ProjectManagerImpl extends ProjectManagerEx implements Disposable {
}
private static boolean canCancelProjectLoading() {
return !ProgressManager.isInNonCancelableSection();
return !ProgressManager.getInstance().isInNonCancelableSection();
}
@Override
@@ -635,4 +635,30 @@ public class ProgressIndicatorTest extends LightPlatformTestCase {
ApplicationManager.getApplication().invokeLater(semaphore::up);
assertTrue("invokeAndWait would deadlock", semaphore.waitFor(1000));
}
public void testNonCancelableSectionDetectedCorrectly() {
ProgressManager progressManager = ProgressManager.getInstance();
assertFalse(progressManager.isInNonCancelableSection());
progressManager.run(new Task.Modal(getProject(), "", false) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
assertFalse(indicator instanceof NonCancelableIndicator);
assertFalse(progressManager.isInNonCancelableSection());
progressManager.executeNonCancelableSection(() -> {
assertTrue(progressManager.getProgressIndicator() instanceof NonCancelableIndicator);
assertTrue(progressManager.isInNonCancelableSection());
progressManager.executeProcessUnderProgress(() -> {
assertFalse(progressManager.getProgressIndicator() instanceof NonCancelableIndicator);
assertTrue(progressManager.isInNonCancelableSection());
}, new DaemonProgressIndicator());
assertTrue(progressManager.isInNonCancelableSection());
});
assertFalse(progressManager.isInNonCancelableSection());
}
});
assertFalse(progressManager.isInNonCancelableSection());
}
}