optimisation: in case of thousands of events do not call expensive Future.schedule...
authorAlexey Kudravtsev <cdr@intellij.com>
Wed, 16 Nov 2016 16:03:58 +0000 (19:03 +0300)
committerAlexey Kudravtsev <cdr@intellij.com>
Thu, 17 Nov 2016 12:42:09 +0000 (15:42 +0300)
java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java
platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java
platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonListeners.java

index 5748a63335648f20ae14d8b487024a2936365ebf..d8880bec47497337c83b8538114ac28e811a515b 100644 (file)
@@ -1524,7 +1524,6 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase {
     boolean succ2 = ProjectManagerEx.getInstanceEx().openProject(alienProject);
     assertTrue(succ2);
     DaemonProgressIndicator.setDebug(true);
-    final DaemonProgressIndicator[] indicator = new DaemonProgressIndicator[1];
 
     try {
       Module alienModule = doCreateRealModuleIn("x", alienProject, getModuleType());
@@ -1552,7 +1551,6 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase {
       di.runPasses(getFile(), getEditor().getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, true, () -> {
         if (checked[0]) return;
         checked[0] = true;
-        indicator[0] = myDaemonCodeAnalyzer.getUpdateProgress();
         typeInAlienEditor(alienEditor, 'x');
       });
     }
@@ -1660,7 +1658,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase {
           long end = System.currentTimeMillis();
           long interruptTime = end - now;
           interruptTimes[finalI] = interruptTime;
-          assertNull(codeAnalyzer.getUpdateProgress());
+          assertTrue(codeAnalyzer.getUpdateProgress().isCanceled());
           System.out.println(interruptTime);
           throw new ProcessCanceledException();
         };
@@ -1731,7 +1729,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase {
         long end = System.currentTimeMillis();
         long interruptTime = end - now;
         interruptTimes[finalI] = interruptTime;
-        assertNull(codeAnalyzer.getUpdateProgress());
+        assertTrue(codeAnalyzer.getUpdateProgress().isCanceled());
         //System.out.println(interruptTime);
         throw new ProcessCanceledException();
       };
index da0d6b351686d00086e9aa389bdba910d7b0c063..ecaf9f96a27b5365b4b7da9093e7ceb2fc71edad 100644 (file)
@@ -60,6 +60,7 @@ import com.intellij.openapi.progress.ProgressManager;
 import com.intellij.openapi.project.DumbService;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.EmptyRunnable;
 import com.intellij.openapi.util.Key;
 import com.intellij.openapi.util.TextRange;
 import com.intellij.openapi.vfs.VirtualFile;
@@ -73,7 +74,6 @@ import com.intellij.psi.util.PsiModificationTracker;
 import com.intellij.psi.util.PsiUtilCore;
 import com.intellij.util.*;
 import com.intellij.util.concurrency.EdtExecutorService;
-import com.intellij.util.concurrency.FixedFuture;
 import com.intellij.util.io.storage.HeavyProcessLatch;
 import com.intellij.util.ui.UIUtil;
 import gnu.trove.THashMap;
@@ -85,10 +85,7 @@ import org.jetbrains.annotations.Nullable;
 import org.jetbrains.annotations.TestOnly;
 
 import java.util.*;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
+import java.util.concurrent.*;
 
 /**
  * This class also controls the auto-reparse and auto-hints.
@@ -105,14 +102,14 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements Pers
   private final DaemonCodeAnalyzerSettings mySettings;
   @NotNull private final EditorTracker myEditorTracker;
   @NotNull private final PsiDocumentManager myPsiDocumentManager;
-  private DaemonProgressIndicator myUpdateProgress; //guarded by this
+  private DaemonProgressIndicator myUpdateProgress = new DaemonProgressIndicator(); //guarded by this
 
   private final Runnable myUpdateRunnable = createUpdateRunnable();
 
   // use scheduler instead of Alarm because the latter requires ModalityState.current() which is obtainable from EDT only which requires too many invokeLaters
   private final ScheduledExecutorService myAlarm = EdtExecutorService.getScheduledExecutorInstance();
   @NotNull
-  private volatile Future myUpdateRunnableFuture = new FixedFuture<>(null);
+  private volatile ScheduledFuture<?> myUpdateRunnableFuture = myAlarm.schedule(EmptyRunnable.getInstance(), 0, TimeUnit.NANOSECONDS);
   private boolean myUpdateByTimerEnabled = true;
   private final Collection<VirtualFile> myDisabledHintsFiles = new THashSet<>();
   private final Collection<VirtualFile> myDisabledHighlightingFiles = new THashSet<>();
@@ -165,8 +162,9 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements Pers
   }
 
   @Override
-  public void dispose() {
-
+  public synchronized void dispose() {
+    myUpdateProgress = null; // leak of highlight session via user data
+    myUpdateRunnableFuture.cancel(true);
   }
 
   @NotNull
@@ -569,8 +567,13 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements Pers
 
   @Override
   public void restart() {
+    doRestart();
+  }
+
+  // return true if the progress was really canceled
+  boolean doRestart() {
     myFileStatusMap.markAllFilesDirty("Global restart");
-    stopProcess(true, "Global restart");
+    return stopProcess(true, "Global restart");
   }
 
   @Override
@@ -618,7 +621,7 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements Pers
   }
 
   synchronized boolean isRunning() {
-    return myUpdateProgress != null && !myUpdateProgress.isCanceled();
+    return !myUpdateProgress.isCanceled();
   }
   
   @TestOnly
@@ -627,22 +630,29 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements Pers
     return isRunning() || !myUpdateRunnableFuture.isDone();
   }
 
-  synchronized void stopProcess(boolean toRestartAlarm, @NotNull @NonNls String reason) {
-    cancelUpdateProgress(toRestartAlarm, reason);
-    myUpdateRunnableFuture.cancel(false);
+  // return true if the progress really was canceled
+  synchronized boolean stopProcess(boolean toRestartAlarm, @NotNull @NonNls String reason) {
+    boolean canceled = cancelUpdateProgress(toRestartAlarm, reason);
+    // optimisation: this check is to avoid too many re-schedules in case of thousands of events spikes
     boolean restart = toRestartAlarm && !myDisposed && myInitialized;
-    if (restart) {
+    if (restart && myUpdateRunnableFuture.isDone()) {
+      myUpdateRunnableFuture.cancel(false);
       myUpdateRunnableFuture = myAlarm.schedule(myUpdateRunnable, mySettings.AUTOREPARSE_DELAY, TimeUnit.MILLISECONDS);
     }
+
+    return canceled;
   }
 
-  private synchronized void cancelUpdateProgress(final boolean start, @NonNls String reason) {
-    if (myUpdateProgress != null) {
-      PassExecutorService.log(myUpdateProgress, null, "Cancel", reason, start);
+  // return true if the progress really was canceled
+  private synchronized boolean cancelUpdateProgress(boolean toRestartAlarm, @NonNls String reason) {
+    boolean wasCanceled = myUpdateProgress.isCanceled();
+    myPassExecutorService.cancelAll(false);
+    if (!wasCanceled) {
+      PassExecutorService.log(myUpdateProgress, null, "Cancel", reason, toRestartAlarm);
       myUpdateProgress.cancel();
-      myPassExecutorService.cancelAll(false);
-      myUpdateProgress = null;
+      return true;
     }
+    return false;
   }
 
 
@@ -885,7 +895,7 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements Pers
   @NotNull
   private synchronized DaemonProgressIndicator createUpdateProgress() {
     DaemonProgressIndicator old = myUpdateProgress;
-    if (old != null && !old.isCanceled()) {
+    if (!old.isCanceled()) {
       old.cancel();
     }
     DaemonProgressIndicator progress = new DaemonProgressIndicator() {
@@ -909,6 +919,7 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements Pers
   }
 
   @TestOnly
+  @NotNull
   synchronized DaemonProgressIndicator getUpdateProgress() {
     return myUpdateProgress;
   }
index c7678f3d1921cde6e28e8e34886c9ed7bc3a79b3..cae0df26f42c00faf3a8ab4facf704c91f635853 100644 (file)
@@ -39,7 +39,6 @@ import com.intellij.openapi.command.undo.UndoManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.editor.*;
 import com.intellij.openapi.editor.actionSystem.DocCommandGroupId;
-import com.intellij.openapi.editor.colors.EditorColorsListener;
 import com.intellij.openapi.editor.colors.EditorColorsManager;
 import com.intellij.openapi.editor.colors.EditorColorsScheme;
 import com.intellij.openapi.editor.event.*;
@@ -276,12 +275,7 @@ public class DaemonListeners implements Disposable {
     });
 
     connection.subscribe(PowerSaveMode.TOPIC, () -> stopDaemon(true, "Power save mode change"));
-    connection.subscribe(EditorColorsManager.TOPIC, new EditorColorsListener() {
-      @Override
-      public void globalSchemeChange(EditorColorsScheme scheme) {
-        stopDaemonAndRestartAllFiles("Editor color scheme changed");
-      }
-    });
+    connection.subscribe(EditorColorsManager.TOPIC, scheme -> stopDaemonAndRestartAllFiles("Editor color scheme changed"));
 
     commandProcessor.addCommandListener(new MyCommandListener(), this);
     application.addApplicationListener(new MyApplicationListener(), this);
@@ -587,13 +581,15 @@ public class DaemonListeners implements Disposable {
   }
 
   private void stopDaemon(boolean toRestartAlarm, @NonNls @NotNull String reason) {
-    myDaemonEventPublisher.daemonCancelEventOccurred(reason);
-    myDaemonCodeAnalyzer.stopProcess(toRestartAlarm, reason);
+    if (myDaemonCodeAnalyzer.stopProcess(toRestartAlarm, reason)) {
+      myDaemonEventPublisher.daemonCancelEventOccurred(reason);
+    }
   }
 
   private void stopDaemonAndRestartAllFiles(@NotNull String reason) {
-    myDaemonEventPublisher.daemonCancelEventOccurred(reason);
-    myDaemonCodeAnalyzer.restart();
+    if (myDaemonCodeAnalyzer.doRestart()) {
+      myDaemonEventPublisher.daemonCancelEventOccurred(reason);
+    }
   }
 
   static void repaintErrorStripeRenderer(@NotNull Editor editor, @NotNull Project project) {