From 752dff3f04f57561d87d0970d21dd68cff425268 Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 22 Feb 2017 14:19:58 +0100 Subject: [PATCH] a test for IDEA-CR-18486 run dumb mode listeners outside write action --- .../openapi/project/DumbServiceImpl.java | 44 ++++++------ .../project/DumbServiceImplTest.groovy | 67 +++++++++++++++++++ .../intellij/util/concurrency/Semaphore.java | 10 +++ 3 files changed, 101 insertions(+), 20 deletions(-) create mode 100644 platform/platform-tests/testSrc/com/intellij/openapi/project/DumbServiceImplTest.groovy diff --git a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java index 867141483b86..0a463bf2c3ec 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.project; +import com.google.common.annotations.VisibleForTesting; import com.intellij.ide.IdeBundle; import com.intellij.ide.PowerSaveMode; import com.intellij.openapi.Disposable; @@ -169,33 +170,36 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica @Override public void queueTask(@NotNull final DumbModeTask task) { - TransactionId contextTransaction = TransactionGuard.getInstance().getContextTransaction(); - - final Throwable trace = ourForcedTrace != null ? ourForcedTrace : new Throwable(); // please report exceptions here to peter if (LOG.isDebugEnabled()) LOG.debug("Scheduling task " + task); final Application application = ApplicationManager.getApplication(); if (application.isUnitTestMode() || application.isHeadlessEnvironment()) { - final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); - if (indicator != null) { - indicator.pushState(); - } - AccessToken token = HeavyProcessLatch.INSTANCE.processStarted("Performing indexing task"); - try { - task.performInDumbMode(indicator != null ? indicator : new EmptyProgressIndicator()); - } - finally { - token.finish(); - if (indicator != null) { - indicator.popState(); - } - Disposer.dispose(task); - } - return; + runTaskSynchronously(task); + } else { + queueAsynchronousTask(task); } + } + private static void runTaskSynchronously(@NotNull DumbModeTask task) { + ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator == null) indicator = new EmptyProgressIndicator(); + + indicator.pushState(); + try (AccessToken ignored = HeavyProcessLatch.INSTANCE.processStarted("Performing indexing task")) { + task.performInDumbMode(indicator); + } + finally { + indicator.popState(); + Disposer.dispose(task); + } + } + + @VisibleForTesting + void queueAsynchronousTask(@NotNull DumbModeTask task) { + Throwable trace = ourForcedTrace != null ? ourForcedTrace : new Throwable(); // please report exceptions here to peter + TransactionId contextTransaction = TransactionGuard.getInstance().getContextTransaction(); Runnable runnable = () -> queueTaskOnEdt(task, contextTransaction, trace); - if (application.isDispatchThread()) { + if (ApplicationManager.getApplication().isDispatchThread()) { runnable.run(); // will log errors if not already in a write-safe context } else { TransactionGuard.submitTransaction(myProject, runnable); diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/project/DumbServiceImplTest.groovy b/platform/platform-tests/testSrc/com/intellij/openapi/project/DumbServiceImplTest.groovy new file mode 100644 index 000000000000..e6da011f5943 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/openapi/project/DumbServiceImplTest.groovy @@ -0,0 +1,67 @@ +/* + * Copyright 2000-2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.project + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase +import com.intellij.util.TimeoutUtil +import com.intellij.util.concurrency.Semaphore +import com.intellij.util.ui.UIUtil +import org.jetbrains.annotations.NotNull +/** + * @author peter + */ +class DumbServiceImplTest extends LightPlatformCodeInsightFixtureTestCase { + + void "test runWhenSmart is executed synchronously in smart mode"() { + int invocations = 0 + dumbService.runWhenSmart { invocations++ } + assert invocations == 1 + } + + void "test runWhenSmart is executed on EDT without write action"() { + ApplicationManager.application.assertIsDispatchThread() + int invocations = 0 + + Semaphore semaphore = new Semaphore(1) + dumbService.queueAsynchronousTask(new DumbModeTask() { + @Override + void performInDumbMode(@NotNull ProgressIndicator indicator) { + assert !ApplicationManager.application.dispatchThread + edt { + dumbService.runWhenSmart { + invocations++ + ApplicationManager.application.assertIsDispatchThread() + assert !ApplicationManager.application.writeAccessAllowed + } + } + TimeoutUtil.sleep(100) + semaphore.up() + } + }) + assert dumbService.dumb + assert invocations == 0 + UIUtil.dispatchAllInvocationEvents() + assert semaphore.waitFor(1000) + UIUtil.dispatchAllInvocationEvents() + assert invocations == 1 + } + + private DumbServiceImpl getDumbService() { + (DumbServiceImpl)DumbService.getInstance(project) + } +} diff --git a/platform/util/src/com/intellij/util/concurrency/Semaphore.java b/platform/util/src/com/intellij/util/concurrency/Semaphore.java index aa48040f3e55..b932b844c7bf 100644 --- a/platform/util/src/com/intellij/util/concurrency/Semaphore.java +++ b/platform/util/src/com/intellij/util/concurrency/Semaphore.java @@ -26,6 +26,16 @@ public class Semaphore { */ public Semaphore() { } + /** + * Creates a semaphore and immediately puts it down the specified number of times + */ + public Semaphore(int downs) { + assert downs >= 0 : "A nonnegative amount of 'downs' expected, found " + downs; + for (int i = 0; i < downs; i++) { + down(); + } + } + private static final class Sync extends AbstractQueuedSynchronizer { @Override public int tryAcquireShared(int acquires) {