diff --git a/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressIndicatorUtils.java b/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressIndicatorUtils.java index b028ed6b6a8b..2dfacec9cb38 100644 --- a/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressIndicatorUtils.java +++ b/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressIndicatorUtils.java @@ -82,7 +82,7 @@ public class ProgressIndicatorUtils { } /** - * Same as {@link #runInReadActionWithWriteActionPriority(Runnable)}, optionally allowing to pass a {@link ProgressIndicatorUtils} + * Same as {@link #runInReadActionWithWriteActionPriority(Runnable)}, optionally allowing to pass a {@link ProgressIndicator} * instance, which can be used to cancel action externally. */ public static boolean runInReadActionWithWriteActionPriority(@NotNull final Runnable action, @@ -161,7 +161,6 @@ public class ProgressIndicatorUtils { public static CompletableFuture scheduleWithWriteActionPriority(@NotNull final ProgressIndicator progressIndicator, @NotNull final Executor executor, @NotNull final ReadTask readTask) { - final Application application = ApplicationManager.getApplication(); // invoke later even if on EDT // to avoid tasks eagerly restarting immediately, allocating many pooled threads // which get cancelled too soon when a next write action arrives in the same EDT batch @@ -171,8 +170,8 @@ public class ProgressIndicatorUtils { // to tolerate any immediate modality changes (e.g. https://youtrack.jetbrains.com/issue/IDEA-135180) CompletableFuture future = new CompletableFuture<>(); - //noinspection SSBasedInspection EdtInvocationManager.getInstance().invokeLater(() -> { + final Application application = ApplicationManager.getApplication(); if (application.isDisposed() || progressIndicator.isCanceled()) { future.complete(null); return; @@ -207,6 +206,7 @@ public class ProgressIndicatorUtils { application.invokeLater(new Runnable() { @Override public void run() { + application.removeApplicationListener(listener); // remove listener early to prevent firing it during continuation execution try { if (!progressIndicator.isCanceled()) { continuation.getAction().run(); @@ -232,7 +232,6 @@ public class ProgressIndicatorUtils { }); } catch (RuntimeException | Error e) { - application.removeApplicationListener(listener); future.completeExceptionally(e); throw e; } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/progress/impl/ProgressIndicatorTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/progress/impl/ProgressIndicatorTest.java index 47b26432ff63..39bbce3c812f 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/progress/impl/ProgressIndicatorTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/progress/impl/ProgressIndicatorTest.java @@ -21,6 +21,7 @@ import com.intellij.concurrency.SensitiveProgressWrapper; import com.intellij.ide.util.DelegatingProgressIndicator; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.progress.*; import com.intellij.openapi.progress.util.ProgressIndicatorBase; @@ -43,14 +44,15 @@ import com.intellij.util.containers.Stack; import com.intellij.util.ui.UIUtil; import gnu.trove.TLongArrayList; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.List; import java.util.Random; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; /** * @author yole @@ -148,7 +150,6 @@ public class ProgressIndicatorTest extends LightPlatformTestCase { final AtomicBoolean afterContinuation = new AtomicBoolean(); final ProgressIndicatorBase indicator = new ProgressIndicatorBase(); ProgressIndicatorUtils.scheduleWithWriteActionPriority(indicator, new ReadTask() { - @Nullable @Override public Continuation performInReadAction(@NotNull ProgressIndicator indicator) throws ProcessCanceledException { return new Continuation(() -> afterContinuation.set(true)); @@ -660,4 +661,30 @@ public class ProgressIndicatorTest extends LightPlatformTestCase { }); assertFalse(progressManager.isInNonCancelableSection()); } + + public void testProgressIndicatorUtilsScheduleWithWriteActionPriorityMustRemoveListenerBeforeContinuationStartsExecutingInEDT() throws Throwable { + final AtomicBoolean canceled = new AtomicBoolean(); + final ProgressIndicatorBase indicator = new ProgressIndicatorBase(); + AtomicReference> future = new AtomicReference<>(); + future.set(ProgressIndicatorUtils.scheduleWithWriteActionPriority(indicator, new ReadTask() { + @Override + public Continuation performInReadAction(@NotNull ProgressIndicator indicator) throws ProcessCanceledException { + return new Continuation(() -> { + assertFalse(future.get().isDone()); + WriteAction.run(() -> { + }); // when I start write action from the continuation, the read task must not cancel + }); + } + + @Override + public void onCanceled(@NotNull ProgressIndicator indicator) { + canceled.set(true); + fail(); + } + })); + while (!future.get().isDone()) { + UIUtil.dispatchAllInvocationEvents(); + } + assertFalse(canceled.get()); + } }