From 398c8228cb21e1df2f8a167e1560d2646107949e Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 26 Apr 2018 12:54:16 +0200 Subject: [PATCH] add NonBlockingReadAction API for easier running of bg read actions --- .../server/DefaultMessageHandler.java | 18 +- .../openapi/application/AppUIExecutor.java | 2 +- .../openapi/application/Application.java | 5 - .../application/AsyncExecutionService.java | 23 +++ .../application/NonBlockingReadAction.java | 52 ++++++ .../openapi/application/ReadAction.java | 25 +++ .../execution/impl/AsyncFilterRunner.java | 33 ++-- .../application/impl/ApplicationImpl.java | 5 - .../impl/AsyncExecutionServiceImpl.java | 27 +++ .../impl/NonBlockingReadActionImpl.java | 168 ++++++++++++++++++ .../impl/text/AsyncHighlighterUpdater.java | 71 +++----- .../src/META-INF/PlatformExtensions.xml | 3 + ...avenProjectImportNotificationListener.java | 3 +- .../maven/GradleProjectStartupActivity.java | 5 +- .../maven/ImportMavenRepositoriesTask.java | 31 +--- .../MvcProjectWithoutLibraryNotificator.java | 27 +-- 16 files changed, 357 insertions(+), 141 deletions(-) create mode 100644 platform/core-api/src/com/intellij/openapi/application/AsyncExecutionService.java create mode 100644 platform/core-api/src/com/intellij/openapi/application/NonBlockingReadAction.java create mode 100644 platform/platform-impl/src/com/intellij/openapi/application/impl/AsyncExecutionServiceImpl.java create mode 100644 platform/platform-impl/src/com/intellij/openapi/application/impl/NonBlockingReadActionImpl.java diff --git a/java/compiler/impl/src/com/intellij/compiler/server/DefaultMessageHandler.java b/java/compiler/impl/src/com/intellij/compiler/server/DefaultMessageHandler.java index 603b86faafe7..b19b96031f81 100644 --- a/java/compiler/impl/src/com/intellij/compiler/server/DefaultMessageHandler.java +++ b/java/compiler/impl/src/com/intellij/compiler/server/DefaultMessageHandler.java @@ -7,9 +7,6 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.util.ProgressIndicatorUtils; -import com.intellij.openapi.progress.util.ReadTask; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; @@ -83,20 +80,7 @@ public abstract class DefaultMessageHandler implements BuilderMessageHandler { protected abstract void handleBuildEvent(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event); private void handleConstantSearchTask(final Channel channel, final UUID sessionId, final CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask task) { - ProgressIndicatorUtils.scheduleWithWriteActionPriority(myTaskExecutor, new ReadTask() { - @Override - public Continuation runBackgroundProcess(@NotNull ProgressIndicator indicator) throws ProcessCanceledException { - return DumbService.getInstance(myProject).runReadActionInSmartMode(() -> { - doHandleConstantSearchTask(channel, sessionId, task); - return null; - }); - } - - @Override - public void onCanceled(@NotNull ProgressIndicator indicator) { - DumbService.getInstance(myProject).runWhenSmart(() -> handleConstantSearchTask(channel, sessionId, task)); - } - }); + ReadAction.nonBlocking(() -> doHandleConstantSearchTask(channel, sessionId, task)).inSmartMode(myProject).submit(myTaskExecutor); } private void doHandleConstantSearchTask(Channel channel, UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask task) { diff --git a/platform/core-api/src/com/intellij/openapi/application/AppUIExecutor.java b/platform/core-api/src/com/intellij/openapi/application/AppUIExecutor.java index b7ef24b8791c..b117734bf292 100644 --- a/platform/core-api/src/com/intellij/openapi/application/AppUIExecutor.java +++ b/platform/core-api/src/com/intellij/openapi/application/AppUIExecutor.java @@ -25,7 +25,7 @@ public interface AppUIExecutor extends Executor { */ @NotNull static AppUIExecutor onUiThread(@NotNull ModalityState modality) { - return ApplicationManager.getApplication().createUIExecutor(modality); + return AsyncExecutionService.getService().createUIExecutor(modality); } /** diff --git a/platform/core-api/src/com/intellij/openapi/application/Application.java b/platform/core-api/src/com/intellij/openapi/application/Application.java index d4319b66dec7..b3cf4008c443 100644 --- a/platform/core-api/src/com/intellij/openapi/application/Application.java +++ b/platform/core-api/src/com/intellij/openapi/application/Application.java @@ -435,9 +435,4 @@ public interface Application extends ComponentManager { boolean isEAP(); - /** Use {@link AppUIExecutor#onUiThread} */ - @NotNull - default AppUIExecutor createUIExecutor(@NotNull ModalityState modalityState) { - throw new UnsupportedOperationException("createUIExecutor is not implemented in " + getClass()); - } } diff --git a/platform/core-api/src/com/intellij/openapi/application/AsyncExecutionService.java b/platform/core-api/src/com/intellij/openapi/application/AsyncExecutionService.java new file mode 100644 index 000000000000..34b9a157da17 --- /dev/null +++ b/platform/core-api/src/com/intellij/openapi/application/AsyncExecutionService.java @@ -0,0 +1,23 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.openapi.application; + +import com.intellij.openapi.components.ServiceManager; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.Callable; + +/** + * An internal service not supposed to be used directly + */ +public abstract class AsyncExecutionService { + @NotNull + protected abstract AppUIExecutor createUIExecutor(@NotNull ModalityState modalityState); + + @NotNull + protected abstract NonBlockingReadAction buildNonBlockingReadAction(@NotNull Callable computation); + + @NotNull + static AsyncExecutionService getService() { + return ServiceManager.getService(AsyncExecutionService.class); + } +} diff --git a/platform/core-api/src/com/intellij/openapi/application/NonBlockingReadAction.java b/platform/core-api/src/com/intellij/openapi/application/NonBlockingReadAction.java new file mode 100644 index 000000000000..24c7013223cc --- /dev/null +++ b/platform/core-api/src/com/intellij/openapi/application/NonBlockingReadAction.java @@ -0,0 +1,52 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.openapi.application; + +import com.intellij.openapi.project.Project; +import com.intellij.util.concurrency.AppExecutorUtil; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.concurrency.CancellablePromise; + +import java.util.concurrent.Executor; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; + +/** + * An utility for running non-blocking read actions in background thread. + * "Interruptible" means to prevent UI freezes, when a write action is about to occur, a read action can be interrupted by a + * {@link com.intellij.openapi.progress.ProcessCanceledException} and then restarted. + */ +public interface NonBlockingReadAction { + + /** + * @return a copy of this builder that runs read actions only when index is available. + * @see com.intellij.openapi.project.DumbService + */ + @Contract(pure=true) + NonBlockingReadAction inSmartMode(@NotNull Project project); + + /** + * @return a copy of this builder that cancels submitted read actions after they become obsolete (i.e. when the provided condition returns true). If {@code expireWhen} is called several times, any of the corresponding conditions being {@code true} is sufficient for cancelling + * the activity. The conditions are checked inside a read action, either on background or on UI thread. + */ + @Contract(pure=true) + NonBlockingReadAction expireWhen(@NotNull BooleanSupplier expireCondition); + + /** + * @return a copy of this builder that completes submitted read actions on UI thread with the given modality state. + * The read actions are still executed on background thread, but the callbacks on their completion + * are invoked on UI thread, and no write action is allowed to interfere before that and possibly invalidate the result. + */ + @Contract(pure=true) + NonBlockingReadAction finishOnUiThread(@NotNull ModalityState modality, @NotNull Consumer uiThreadAction); + + /** + * Submit this computation to be performed in a non-blocking read action on background thread. The returned promise + * is completed on the same thread (in the same read action), or on UI thread if {@link #finishOnUiThread} has been called. + * @param backgroundThreadExecutor an executor to actually run the computation. Common examples are + * {@link AppExecutorUtil#getAppExecutorService()} or + * {@link com.intellij.util.concurrency.BoundedTaskExecutor} on top of that. + */ + CancellablePromise submit(@NotNull Executor backgroundThreadExecutor); + +} diff --git a/platform/core-api/src/com/intellij/openapi/application/ReadAction.java b/platform/core-api/src/com/intellij/openapi/application/ReadAction.java index 809177ea8b8d..978a64eeb2cb 100644 --- a/platform/core-api/src/com/intellij/openapi/application/ReadAction.java +++ b/platform/core-api/src/com/intellij/openapi/application/ReadAction.java @@ -17,8 +17,11 @@ package com.intellij.openapi.application; import com.intellij.openapi.util.ThrowableComputable; import com.intellij.util.ThrowableRunnable; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import java.util.concurrent.Callable; + public abstract class ReadAction extends BaseActionRunnable { /** * @deprecated use {@link #run(ThrowableRunnable)} or {@link #compute(ThrowableComputable)} instead @@ -53,4 +56,26 @@ public abstract class ReadAction extends BaseActionRunnable { public static T compute(@NotNull ThrowableComputable action) throws E { return ApplicationManager.getApplication().runReadAction(action); } + + /** + * Create an {@link NonBlockingReadAction} builder to run the given Runnable in non-blocking read action on a background thread. + */ + @NotNull + @Contract(pure=true) + public static NonBlockingReadAction nonBlocking(@NotNull Runnable task) { + return nonBlocking(() -> { + task.run(); + return null; + }); + } + + /** + * Create an {@link NonBlockingReadAction} builder to run the given Callable in a non-blocking read action on a background thread. + */ + @NotNull + @Contract(pure=true) + public static NonBlockingReadAction nonBlocking(@NotNull Callable task) { + return AsyncExecutionService.getService().buildNonBlockingReadAction(task); + } + } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/execution/impl/AsyncFilterRunner.java b/platform/platform-impl/src/com/intellij/execution/impl/AsyncFilterRunner.java index 30abb52496e5..80244c05b13e 100644 --- a/platform/platform-impl/src/com/intellij/execution/impl/AsyncFilterRunner.java +++ b/platform/platform-impl/src/com/intellij/execution/impl/AsyncFilterRunner.java @@ -18,22 +18,26 @@ package com.intellij.execution.impl; import com.intellij.execution.filters.Filter; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.editor.impl.DocumentImpl; import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.util.ProgressIndicatorUtils; import com.intellij.util.TimeoutUtil; import com.intellij.util.concurrency.SequentialTaskExecutor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.concurrency.Promise; import java.util.ArrayList; import java.util.List; import java.util.Queue; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; /** @@ -59,25 +63,22 @@ class AsyncFilterRunner { if (ApplicationManager.getApplication().isWriteAccessAllowed()) { runTasks(); highlightAvailableResults(); - } else if (isQuick(ourExecutor.submit(this::runFiltersInBackground))) { + return; + } + + Promise promise = ReadAction + .nonBlocking(this::runTasks) + .finishOnUiThread(ModalityState.any(), __ -> highlightAvailableResults()) + .submit(ourExecutor); + + if (isQuick(promise)) { highlightAvailableResults(); } } - private void runFiltersInBackground() { - while (true) { - boolean finished = ProgressIndicatorUtils.runInReadActionWithWriteActionPriority(this::runTasks); - if (hasResults()) { - ApplicationManager.getApplication().invokeLater(this::highlightAvailableResults, ModalityState.any()); - } - if (finished) return; - ProgressIndicatorUtils.yieldToPendingWriteActions(); - } - } - - private static boolean isQuick(Future future) { + private static boolean isQuick(Promise future) { try { - future.get(5, TimeUnit.MILLISECONDS); + future.blockingGet(5, TimeUnit.MILLISECONDS); return true; } catch (TimeoutException ignored) { 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 9acc459157f8..4d39a264d62d 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 @@ -1502,9 +1502,4 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App Disposer.register(disposable, () -> myDispatcher.getListeners().addAll(listeners)); } - @NotNull - @Override - public AppUIExecutor createUIExecutor(@NotNull ModalityState modalityState) { - return new AppUIExecutorImpl(modalityState); - } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/AsyncExecutionServiceImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/AsyncExecutionServiceImpl.java new file mode 100644 index 000000000000..68f3d50c1410 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/AsyncExecutionServiceImpl.java @@ -0,0 +1,27 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.openapi.application.impl; + +import com.intellij.openapi.application.AppUIExecutor; +import com.intellij.openapi.application.AsyncExecutionService; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.NonBlockingReadAction; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.Callable; + +/** + * @author peter + */ +public class AsyncExecutionServiceImpl extends AsyncExecutionService { + @NotNull + @Override + public AppUIExecutor createUIExecutor(@NotNull ModalityState modalityState) { + return new AppUIExecutorImpl(modalityState); + } + + @NotNull + @Override + public NonBlockingReadAction buildNonBlockingReadAction(Callable computation) { + return new NonBlockingReadActionImpl<>(null, null, () -> false, computation); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/NonBlockingReadActionImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/NonBlockingReadActionImpl.java new file mode 100644 index 000000000000..d3b46fc3907c --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/NonBlockingReadActionImpl.java @@ -0,0 +1,168 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.openapi.application.impl; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.NonBlockingReadAction; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.util.ProgressIndicatorUtils; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.util.concurrency.Semaphore; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.concurrency.AsyncPromise; +import org.jetbrains.concurrency.CancellablePromise; +import org.jetbrains.concurrency.Promises; + +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; + +/** + * @author peter + */ +class NonBlockingReadActionImpl implements NonBlockingReadAction { + private final @Nullable Pair> myEdtFinish; + private final @Nullable DumbService myRequireSmartMode; //todo a more pluggable constraint API + private final BooleanSupplier myExpireCondition; + private final Callable myComputation; + + NonBlockingReadActionImpl(@Nullable Pair> edtFinish, + @Nullable DumbService requireSmartMode, + @NotNull BooleanSupplier expireCondition, + @NotNull Callable computation) { + myEdtFinish = edtFinish; + myRequireSmartMode = requireSmartMode; + myExpireCondition = expireCondition; + myComputation = computation; + } + + @Override + public NonBlockingReadAction inSmartMode(@NotNull Project project) { + return new NonBlockingReadActionImpl<>(myEdtFinish, DumbService.getInstance(project), myExpireCondition, myComputation).expireWhen(project::isDisposed); + } + + @Override + public NonBlockingReadAction expireWhen(@NotNull BooleanSupplier expireCondition) { + return new NonBlockingReadActionImpl<>(myEdtFinish, myRequireSmartMode, () -> myExpireCondition.getAsBoolean() || expireCondition.getAsBoolean(), myComputation); + } + + @Override + public NonBlockingReadAction finishOnUiThread(@NotNull ModalityState modality, @NotNull Consumer uiThreadAction) { + return new NonBlockingReadActionImpl<>(Pair.create(modality, uiThreadAction), myRequireSmartMode, myExpireCondition, myComputation); + } + + @Override + public CancellablePromise submit(@NotNull Executor backgroundThreadExecutor) { + AsyncPromise promise = new AsyncPromise<>(); + new Submission(promise, backgroundThreadExecutor).transferToBgThread(); + return promise; + } + + private class Submission { + private final AsyncPromise promise; + @NotNull private final Executor backendExecutor; + private volatile ProgressIndicator currentIndicator; + private final ModalityState creationModality = ModalityState.defaultModalityState(); + + Submission(AsyncPromise promise, @NotNull Executor backgroundThreadExecutor) { + this.promise = promise; + backendExecutor = backgroundThreadExecutor; + promise.onError(__ -> { + ProgressIndicator indicator = currentIndicator; + if (indicator != null) { + indicator.cancel(); + } + }); + } + + void transferToBgThread() { + backendExecutor.execute(() -> { + try { + ProgressIndicator indicator = new EmptyProgressIndicator(creationModality); + currentIndicator = indicator; + ProgressIndicatorUtils.runInReadActionWithWriteActionPriority(() -> insideReadAction(indicator), indicator); + } + finally { + currentIndicator = null; + } + + if (Promises.isPending(promise)) { + rescheduleLater(); + } + }); + } + + private void rescheduleLater() { + if (myRequireSmartMode != null) { + myRequireSmartMode.runWhenSmart(this::transferToBgThread); + } else { + ApplicationManager.getApplication().invokeLater(this::transferToBgThread, ModalityState.any()); + } + } + + void insideReadAction(ProgressIndicator indicator) { + try { + if (checkObsolete() || !constraintsAreSatisfied()) return; + + T result = myComputation.call(); + + if (myEdtFinish != null) { + safeTransferToEdt(result, myEdtFinish); + } else { + promise.setResult(result); + } + } + catch (Throwable e) { + if (!indicator.isCanceled()) { + promise.setError(e); + } + } + } + + private boolean constraintsAreSatisfied() { + return myRequireSmartMode == null || !myRequireSmartMode.isDumb(); + } + + private boolean checkObsolete() { + if (myExpireCondition.getAsBoolean()) { + promise.cancel(); + return true; + } + return false; + } + + void safeTransferToEdt(T result, Pair> edtFinish) { + if (Promises.isRejected(promise)) return; + + Semaphore semaphore = new Semaphore(1); + ApplicationManager.getApplication().invokeLater(() -> { + if (checkObsolete()) { + semaphore.up(); + return; + } + + // complete the promise now to prevent write actions inside custom callback from cancelling it + promise.setResult(result); + + // now background thread may release its read lock, and we continue on EDT, invoking custom callback + semaphore.up(); + + if (Promises.isFulfilled(promise)) { // in case another thread managed to cancel it just before `setResult` + edtFinish.second.accept(result); + } + }, edtFinish.first); + + // don't release read action until we're on EDT, to avoid result invalidation in between + while (!semaphore.waitFor(10)) { + ProgressManager.checkCanceled(); + } + } + + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncHighlighterUpdater.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncHighlighterUpdater.java index 8d3a9e6aa758..60d28f761ffc 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncHighlighterUpdater.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncHighlighterUpdater.java @@ -16,14 +16,12 @@ package com.intellij.openapi.fileEditor.impl.text; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.highlighter.EditorHighlighter; import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.util.ProgressIndicatorUtils; -import com.intellij.openapi.progress.util.ReadTask; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.concurrency.AppExecutorUtil; @@ -31,54 +29,39 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.TestOnly; +import org.jetbrains.concurrency.CancellablePromise; import java.util.Map; -import java.util.concurrent.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * @author peter */ -public class AsyncHighlighterUpdater extends ReadTask { +public class AsyncHighlighterUpdater { private static final ExecutorService ourExecutor = AppExecutorUtil.createBoundedApplicationPoolExecutor("AsyncEditorLoader Pool", 2); - private static final Map> ourHighlighterFutures = ContainerUtil.newConcurrentMap(); - private final Project myProject; - private final Editor myEditor; - private final VirtualFile myFile; - - private AsyncHighlighterUpdater(Project project, Editor editor, VirtualFile file) { - myProject = project; - myEditor = editor; - myFile = file; - } - - @Override - public Continuation performInReadAction(@NotNull ProgressIndicator indicator) throws ProcessCanceledException { - if (!isEverythingValid()) return null; - - EditorHighlighter highlighter = EditorHighlighterFactory.getInstance().createEditorHighlighter(myProject, myFile); - highlighter.setText(myEditor.getDocument().getImmutableCharSequence()); - return new Continuation(() -> ((EditorEx)myEditor).setHighlighter(highlighter)); - } - - @Override - public void onCanceled(@NotNull ProgressIndicator indicator) { - updateHighlighters(myProject, myEditor, myFile); - } - - private boolean isEverythingValid() { - return !myProject.isDisposed() && !myEditor.isDisposed() && myFile.isValid(); - } + private static final Map> ourHighlighterFutures = ContainerUtil.newConcurrentMap(); public static void updateHighlighters(@NotNull Project project, @NotNull Editor editor, @NotNull VirtualFile file) { - AsyncHighlighterUpdater task = new AsyncHighlighterUpdater(project, editor, file); - if (task.isEverythingValid()) { - CompletableFuture future = ProgressIndicatorUtils.scheduleWithWriteActionPriority(ourExecutor, task); - Future prev = ourHighlighterFutures.put(editor, future); - if (prev != null) { - prev.cancel(false); - } - future.whenComplete((a, b) -> ourHighlighterFutures.remove(editor, future)); + CancellablePromise promise = ReadAction + .nonBlocking(() -> updateHighlighter(project, editor, file)) + .expireWhen(() -> !file.isValid() || editor.isDisposed() || project.isDisposed()) + .finishOnUiThread(ModalityState.any(), highlighter -> ((EditorEx)editor).setHighlighter(highlighter)) + .submit(ourExecutor); + + CancellablePromise prev = ourHighlighterFutures.put(editor, promise); + if (prev != null) { + prev.cancel(); } + promise.onProcessed(__ -> ourHighlighterFutures.remove(editor, promise)); + } + + @NotNull + private static EditorHighlighter updateHighlighter(@NotNull Project project, @NotNull Editor editor, @NotNull VirtualFile file) { + EditorHighlighter highlighter = EditorHighlighterFactory.getInstance().createEditorHighlighter(project, file); + highlighter.setText(editor.getDocument().getImmutableCharSequence()); + return highlighter; } @TestOnly @@ -89,12 +72,12 @@ public class AsyncHighlighterUpdater extends ReadTask { } @TestOnly - private static void waitForFuture(Future future) { + private static void waitForFuture(CancellablePromise future) { int iteration = 0; while (!future.isDone() && iteration++ < 1000) { UIUtil.dispatchAllInvocationEvents(); try { - future.get(10, TimeUnit.MILLISECONDS); + future.blockingGet(10, TimeUnit.MILLISECONDS); return; } catch (TimeoutException ignore) { diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index ee41843ee953..7703882bc1d6 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -480,6 +480,9 @@ + + diff --git a/plugins/gradle/java/src/integrations/maven/GradleMavenProjectImportNotificationListener.java b/plugins/gradle/java/src/integrations/maven/GradleMavenProjectImportNotificationListener.java index 3ad92d9e7b97..6fa4d641024a 100644 --- a/plugins/gradle/java/src/integrations/maven/GradleMavenProjectImportNotificationListener.java +++ b/plugins/gradle/java/src/integrations/maven/GradleMavenProjectImportNotificationListener.java @@ -18,7 +18,6 @@ package org.jetbrains.plugins.gradle.integrations.maven; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType; -import com.intellij.openapi.progress.util.ProgressIndicatorUtils; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.gradle.util.GradleConstants; @@ -37,7 +36,7 @@ public class GradleMavenProjectImportNotificationListener extends ExternalSystem && id.getType() == ExternalSystemTaskType.RESOLVE_PROJECT) { final Project project = id.findProject(); if (project == null) return; - ProgressIndicatorUtils.scheduleWithWriteActionPriority(new ImportMavenRepositoriesTask(project)); + new ImportMavenRepositoriesTask(project).schedule(); } } } diff --git a/plugins/gradle/java/src/integrations/maven/GradleProjectStartupActivity.java b/plugins/gradle/java/src/integrations/maven/GradleProjectStartupActivity.java index 92814b8a6a58..5b7be3ab8a45 100644 --- a/plugins/gradle/java/src/integrations/maven/GradleProjectStartupActivity.java +++ b/plugins/gradle/java/src/integrations/maven/GradleProjectStartupActivity.java @@ -15,8 +15,6 @@ */ package org.jetbrains.plugins.gradle.integrations.maven; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.progress.util.ProgressIndicatorUtils; import com.intellij.openapi.project.Project; import com.intellij.openapi.startup.StartupActivity; import org.jetbrains.annotations.NotNull; @@ -29,7 +27,6 @@ public class GradleProjectStartupActivity implements StartupActivity { @Override public void runActivity(@NotNull final Project project) { - if (ApplicationManager.getApplication().isUnitTestMode()) return; - ProgressIndicatorUtils.scheduleWithWriteActionPriority(new ImportMavenRepositoriesTask(project)); + new ImportMavenRepositoriesTask(project).schedule(); } } diff --git a/plugins/gradle/java/src/integrations/maven/ImportMavenRepositoriesTask.java b/plugins/gradle/java/src/integrations/maven/ImportMavenRepositoriesTask.java index da66ae170f2f..985c8fab0c3e 100644 --- a/plugins/gradle/java/src/integrations/maven/ImportMavenRepositoriesTask.java +++ b/plugins/gradle/java/src/integrations/maven/ImportMavenRepositoriesTask.java @@ -20,11 +20,6 @@ import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.util.ProgressIndicatorUtils; -import com.intellij.openapi.progress.util.ReadTask; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; @@ -33,6 +28,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; +import com.intellij.util.concurrency.AppExecutorUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -60,32 +56,24 @@ import java.util.stream.Collectors; * @author Vladislav.Soroka * @since 10/29/13 */ -public class ImportMavenRepositoriesTask extends ReadTask { +class ImportMavenRepositoriesTask { @NotNull private final MavenRemoteRepository mavenCentralRemoteRepository; private final Project myProject; - private final DumbService myDumbService; - public ImportMavenRepositoriesTask(Project project) { + ImportMavenRepositoriesTask(Project project) { myProject = project; - myDumbService = DumbService.getInstance(myProject); mavenCentralRemoteRepository = new MavenRemoteRepository("central", null, "https://repo1.maven.org/maven2/", null, null, null); } - @Override - public Continuation runBackgroundProcess(@NotNull ProgressIndicator indicator) throws ProcessCanceledException { - return myDumbService.runReadActionInSmartMode(() -> { - performTask(); - return null; - }); + void schedule() { + if (ApplicationManager.getApplication().isUnitTestMode()) return; + ReadAction.nonBlocking(this::performTask).inSmartMode(myProject).submit(AppExecutorUtil.getAppExecutorService()); } private void performTask() { - if(myProject.isDisposed()) return; - if (ApplicationManager.getApplication().isUnitTestMode()) return; - final LocalFileSystem localFileSystem = LocalFileSystem.getInstance(); final List psiFileList = ContainerUtil.newArrayList(); @@ -146,13 +134,6 @@ public class ImportMavenRepositoriesTask extends ReadTask { }); } - @Override - public void onCanceled(@NotNull ProgressIndicator indicator) { - if (!myProject.isDisposed()) { - ProgressIndicatorUtils.scheduleWithWriteActionPriority(this); - } - } - @NotNull private static Collection findClosableBlocks(@NotNull final PsiElement element, @NotNull final String... blockNames) { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/MvcProjectWithoutLibraryNotificator.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/MvcProjectWithoutLibraryNotificator.java index 16f57d0084f4..4659f87f8ca8 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/MvcProjectWithoutLibraryNotificator.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/MvcProjectWithoutLibraryNotificator.java @@ -19,18 +19,16 @@ import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationType; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.util.ProgressIndicatorUtils; -import com.intellij.openapi.progress.util.ReadTask; import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.startup.StartupActivity; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.util.concurrency.AppExecutorUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -44,10 +42,8 @@ public class MvcProjectWithoutLibraryNotificator implements StartupActivity, Dum @Override public void runActivity(@NotNull final Project project) { if (ApplicationManager.getApplication().isUnitTestMode()) return; - ProgressIndicatorUtils.scheduleWithWriteActionPriority(new ReadTask() { - @Override - public void computeInReadAction(@NotNull ProgressIndicator indicator) { - if (project.isDisposed()) return; + + ReadAction.nonBlocking(() -> { final Pair pair = findModuleWithoutLibrary(project); if (pair == null) return; @@ -76,20 +72,7 @@ public class MvcProjectWithoutLibraryNotificator implements StartupActivity, Dum } } ).notify(project); - } - - @Override - public Continuation runBackgroundProcess(@NotNull final ProgressIndicator indicator) { - return DumbService.getInstance(project).runReadActionInSmartMode(() -> performInReadAction(indicator)); - } - - @Override - public void onCanceled(@NotNull ProgressIndicator indicator) { - if (!project.isDisposed()) { - ProgressIndicatorUtils.scheduleWithWriteActionPriority(this); - } - } - }); + }).inSmartMode(project).submit(AppExecutorUtil.getAppExecutorService()); } @Nullable