restart indexing on root change, cancel on project disposing (IDEA-96277, IDEA-115651, some "already disposed" exceptions)

This commit is contained in:
peter
2014-06-18 16:09:05 +02:00
parent 6a5377e1c8
commit 934a53e53f
7 changed files with 122 additions and 25 deletions
@@ -15,14 +15,18 @@
*/
package com.intellij.openapi.project;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.progress.ProgressIndicator;
import org.jetbrains.annotations.NotNull;
/**
* @author peter
*/
public abstract class DumbModeTask {
public abstract class DumbModeTask implements Disposable {
public abstract void performInDumbMode(@NotNull ProgressIndicator indicator);
@Override
public void dispose() {
}
}
@@ -176,7 +176,9 @@ public abstract class DumbService {
return new ArrayList<T>(collection);
}
public abstract void queueTask(DumbModeTask task);
public abstract void queueTask(@NotNull DumbModeTask task);
public abstract void cancelTask(@NotNull DumbModeTask task);
public abstract JComponent wrapGently(@NotNull JComponent dumbUnawareContent, @NotNull Disposable parentDisposable);
@@ -21,6 +21,7 @@ import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.project.DumbModeTask;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -50,10 +51,14 @@ public class MockDumbService extends DumbService {
}
@Override
public void queueTask(DumbModeTask task) {
public void queueTask(@NotNull DumbModeTask task) {
task.performInDumbMode(new EmptyProgressIndicator());
Disposer.dispose(task);
}
@Override
public void cancelTask(@NotNull DumbModeTask task) { }
@Override
public JComponent wrapGently(@NotNull JComponent dumbUnawareContent, @NotNull Disposable parentDisposable) {
throw new UnsupportedOperationException();
@@ -164,14 +164,21 @@ public class PushedFilePropertiesUpdater {
});
}
private void queueTask(Runnable task) {
myTasks.offer(task);
DumbService.getInstance(myProject).queueTask(new DumbModeTask() {
private void queueTask(Runnable action) {
myTasks.offer(action);
final DumbModeTask task = new DumbModeTask() {
@Override
public void performInDumbMode(@NotNull ProgressIndicator indicator) {
performPushTasks();
}
};
myProject.getMessageBus().connect(task).subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
@Override
public void rootsChanged(ModuleRootEvent event) {
DumbService.getInstance(myProject).cancelTask(task);
}
});
DumbService.getInstance(myProject).queueTask(task);
}
private void performPushTasks() {
@@ -19,6 +19,7 @@
*/
package com.intellij.util.indexing;
import com.intellij.ProjectTopics;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.caches.FileContent;
import com.intellij.ide.startup.StartupManagerEx;
@@ -29,6 +30,8 @@ import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.*;
import com.intellij.openapi.roots.ContentIterator;
import com.intellij.openapi.roots.ModuleRootAdapter;
import com.intellij.openapi.roots.ModuleRootEvent;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
import com.intellij.openapi.roots.impl.ProjectRootManagerComponent;
@@ -128,7 +131,7 @@ public class FileBasedIndexProjectHandler extends AbstractProjectComponent imple
@Override
public void disposeComponent() {
// done mostly for tests. In real life this is noop, because the set was removed on project closing
// done mostly for tests. In real life this is no-op, because the set was removed on project closing
myIndex.removeIndexableSet(this);
}
@@ -140,7 +143,7 @@ public class FileBasedIndexProjectHandler extends AbstractProjectComponent imple
return null;
}
return new DumbModeTask() {
final DumbModeTask task = new DumbModeTask() {
@Override
public void performInDumbMode(@NotNull ProgressIndicator indicator) {
final Collection<VirtualFile> files = index.getFilesToUpdate(project);
@@ -149,6 +152,13 @@ public class FileBasedIndexProjectHandler extends AbstractProjectComponent imple
reindexRefreshedFiles(indicator, files, project, index);
}
};
project.getMessageBus().connect(task).subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
@Override
public void rootsChanged(ModuleRootEvent event) {
DumbService.getInstance(project).cancelTask(task);
}
});
return task;
}
private static void reindexRefreshedFiles(ProgressIndicator indicator,
@@ -15,6 +15,7 @@
*/
package com.intellij.util.indexing;
import com.intellij.ProjectTopics;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.caches.FileContent;
import com.intellij.ide.startup.impl.StartupManagerImpl;
@@ -24,8 +25,11 @@ import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.CacheUpdateRunner;
import com.intellij.openapi.project.DumbModeTask;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.CollectingContentIterator;
import com.intellij.openapi.roots.ModuleRootAdapter;
import com.intellij.openapi.roots.ModuleRootEvent;
import com.intellij.openapi.roots.impl.PushedFilePropertiesUpdater;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.vfs.VirtualFile;
@@ -48,6 +52,12 @@ public class UnindexedFilesUpdater extends DumbModeTask {
public UnindexedFilesUpdater(final Project project, boolean onStartup) {
myProject = project;
myOnStartup = onStartup;
project.getMessageBus().connect(this).subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
@Override
public void rootsChanged(ModuleRootEvent event) {
DumbService.getInstance(project).cancelTask(UnindexedFilesUpdater.this);
}
});
}
private void updateUnindexedFiles(ProgressIndicator indicator) {
@@ -17,7 +17,6 @@ package com.intellij.openapi.project;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.caches.CacheUpdater;
import com.intellij.ide.util.DelegatingProgressIndicator;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
@@ -28,6 +27,7 @@ import com.intellij.openapi.progress.*;
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.ShutDownTracker;
import com.intellij.openapi.wm.AppIconScheme;
@@ -37,9 +37,9 @@ import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
import com.intellij.openapi.wm.ex.StatusBarEx;
import com.intellij.ui.AppIcon;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Queue;
import com.intellij.util.io.storage.HeavyProcessLatch;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -48,28 +48,55 @@ import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
public class DumbServiceImpl extends DumbService {
public class DumbServiceImpl extends DumbService implements Disposable {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.project.DumbServiceImpl");
private volatile boolean myDumb = false;
private final DumbModeListener myPublisher;
private final Queue<DumbModeTask> myUpdatesQueue = new Queue<DumbModeTask>(5);
/**
* Per-task progress indicators. Modified from EDT only.
* The task is removed from this map after it's finished or when the project is disposed.
*/
private final Map<DumbModeTask, ProgressIndicatorEx> myProgresses = ContainerUtil.newConcurrentMap();
private final Queue<Runnable> myRunWhenSmartQueue = new Queue<Runnable>(5);
private final Project myProject;
public DumbServiceImpl(Project project) {
myProject = project;
myPublisher = project.getMessageBus().syncPublisher(DUMB_MODE);
}
@SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass"})
public static DumbServiceImpl getInstance(@NotNull Project project) {
return (DumbServiceImpl)DumbService.getInstance(project);
}
@Override
public void queueTask(final DumbModeTask task) {
public void queueTask(@NotNull final DumbModeTask task) {
scheduleCacheUpdate(task, true);
}
public DumbServiceImpl(Project project, MessageBus bus) {
myProject = project;
myPublisher = bus.syncPublisher(DUMB_MODE);
@Override
public void cancelTask(@NotNull DumbModeTask task) {
ProgressIndicatorEx indicator = myProgresses.get(task);
if (indicator != null) {
indicator.cancel();
}
}
@Override
public void dispose() {
ApplicationManager.getApplication().assertIsDispatchThread();
myUpdatesQueue.clear();
myRunWhenSmartQueue.clear();
for (DumbModeTask task : new ArrayList<DumbModeTask>(myProgresses.keySet())) {
cancelTask(task);
Disposer.dispose(task);
}
}
@Override
@@ -134,6 +161,7 @@ public class DumbServiceImpl extends DumbService {
if (indicator != null) {
indicator.popState();
}
Disposer.dispose(task);
}
return;
}
@@ -145,6 +173,14 @@ public class DumbServiceImpl extends DumbService {
return;
}
myUpdatesQueue.addLast(task);
myProgresses.put(task, new ProgressIndicatorBase());
Disposer.register(task, new Disposable() {
@Override
public void dispose() {
application.assertIsDispatchThread();
myProgresses.remove(task);
}
});
// ok to test and set the flag like this, because the change is always done from dispatch thread
if (!myDumb) {
// always change dumb status inside write action.
@@ -294,14 +330,12 @@ public class DumbServiceImpl extends DumbService {
((ProgressIndicatorEx)indicator).addStateDelegate(new AppIconProgress());
}
final ProgressIndicator proxy = new DelegatingProgressIndicator(indicator);
final ShutDownTracker shutdownTracker = ShutDownTracker.getInstance();
final Thread self = Thread.currentThread();
try {
HeavyProcessLatch.INSTANCE.processStarted();
shutdownTracker.registerStopperThread(self);
runTasks(proxy);
runTasks(indicator);
}
catch (RuntimeException e) {
LOG.error(e);
@@ -313,16 +347,26 @@ public class DumbServiceImpl extends DumbService {
}
}
private void runTasks(ProgressIndicator indicator) {
private void runTasks(ProgressIndicator visibleIndicator) {
DumbModeTask prevTask = null;
while (true) {
DumbModeTask task = getNextTask();
DumbModeTask task = getNextTask(prevTask);
if (task == null) break;
try {
if (ApplicationManager.getApplication().isInternal()) LOG.info("Running dumb mode task: " + task);
final ProgressIndicatorEx indicator = myProgresses.get(task);
assert indicator != null;
indicator.checkCanceled();
if (visibleIndicator instanceof ProgressIndicatorEx) {
indicator.addStateDelegate((ProgressIndicatorEx)visibleIndicator);
}
indicator.setIndeterminate(true);
indicator.setText(IdeBundle.message("progress.indexing.scanning"));
task.performInDumbMode(indicator);
}
catch (ProcessCanceledException ignored) {
@@ -330,6 +374,7 @@ public class DumbServiceImpl extends DumbService {
catch (Throwable unexpected) {
LOG.error(unexpected);
}
prevTask = task;
}
}
@@ -337,16 +382,30 @@ public class DumbServiceImpl extends DumbService {
}
@Nullable
private DumbModeTask getNextTask() {
private DumbModeTask getNextTask(@Nullable final DumbModeTask prevTask) {
final Ref<DumbModeTask> nextTask = Ref.create();
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
if (myProject.isDisposed()) return;
if (myUpdatesQueue.isEmpty()) {
updateFinished();
} else {
nextTask.set(myUpdatesQueue.pullFirst());
if (prevTask != null) {
Disposer.dispose(prevTask);
}
while (true) {
if (myUpdatesQueue.isEmpty()) {
updateFinished();
return;
}
DumbModeTask queuedTask = myUpdatesQueue.pullFirst();
if (myProgresses.get(queuedTask).isCanceled()) {
Disposer.dispose(queuedTask);
continue;
}
nextTask.set(queuedTask);
return;
}
}
});