execute node.update() in background because it could call smartPointer.isValid() which is ridiculously expensive in CLion

This commit is contained in:
Alexey Kudravtsev
2018-01-24 19:04:36 +03:00
parent 1bbdcb5b51
commit b88d1e3042
6 changed files with 158 additions and 64 deletions
@@ -21,6 +21,9 @@ import com.intellij.openapi.extensions.ExtensionPoint;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.extensions.ExtensionsArea;
import com.intellij.openapi.fileEditor.FileEditorLocation;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
@@ -112,13 +115,13 @@ public class UsageNodeTreeBuilderTest extends LightPlatformTestCase {
UsageGroupingRuleProvider provider = new UsageGroupingRuleProvider() {
@NotNull
@Override
public UsageGroupingRule[] getActiveRules(Project project) {
public UsageGroupingRule[] getActiveRules(@NotNull Project project) {
return rules;
}
@NotNull
@Override
public AnAction[] createGroupingActions(UsageView view) {
public AnAction[] createGroupingActions(@NotNull UsageView view) {
return AnAction.EMPTY_ARRAY;
}
};
@@ -130,6 +133,13 @@ public class UsageNodeTreeBuilderTest extends LightPlatformTestCase {
usageView.appendUsage(usage);
}
UIUtil.dispatchAllInvocationEvents();
ProgressManager.getInstance().run(new Task.Modal(getProject(), "waiting", false) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
usageView.waitForUpdateRequestsCompletion();
}
});
UIUtil.dispatchAllInvocationEvents();
return usageView.getRoot();
}
@@ -6,7 +6,6 @@ package com.intellij.usages.impl;
import com.intellij.find.FindManager;
import com.intellij.find.findUsages.FindUsagesHandler;
import com.intellij.find.findUsages.FindUsagesManager;
import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
import com.intellij.find.impl.FindManagerImpl;
import com.intellij.ide.actions.exclusion.ExclusionHandler;
import com.intellij.ide.impl.TypeSafeDataProviderAdapter;
@@ -16,6 +15,9 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.encoding.EncodingManager;
@@ -31,6 +33,7 @@ import com.intellij.usageView.UsageInfo;
import com.intellij.usages.*;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.tree.TreeNode;
@@ -57,9 +60,7 @@ public class UsageViewTest extends LightPlatformCodeInsightFixtureTestCase {
usages[i] = createUsage(psiFile,i);
}
UsageView usageView = UsageViewManager.getInstance(getProject()).createUsageView(UsageTarget.EMPTY_ARRAY, usages, new UsageViewPresentation(), null);
Disposer.register(myFixture.getTestRootDisposable(), usageView);
UsageView usageView = createUsageView(usages);
((EncodingManagerImpl)EncodingManager.getInstance()).clearDocumentQueue();
FileDocumentManager.getInstance().saveAllDocuments();
@@ -73,8 +74,7 @@ public class UsageViewTest extends LightPlatformCodeInsightFixtureTestCase {
PsiFile psiFile = myFixture.addFileToProject("X.java", "public class X{ int xxx; } //comment");
Usage usage = createUsage(psiFile, psiFile.getText().indexOf("xxx"));
UsageView usageView = UsageViewManager.getInstance(getProject()).createUsageView(UsageTarget.EMPTY_ARRAY, new Usage[]{usage}, new UsageViewPresentation(), null);
Disposer.register(myFixture.getTestRootDisposable(), usageView);
UsageView usageView = createUsageView(new Usage[]{usage});
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(getProject());
Document document = documentManager.getDocument(psiFile);
@@ -87,8 +87,7 @@ public class UsageViewTest extends LightPlatformCodeInsightFixtureTestCase {
PsiFile psiFile = myFixture.addFileToProject("X.java", "public class X{ int xxx; } //comment");
Usage usage = new UsageInfo2UsageAdapter(new UsageInfo(psiFile, psiFile.getText().indexOf("xxx"), StringUtil.indexOfSubstringEnd(psiFile.getText(),"xxx")));
UsageView usageView = UsageViewManager.getInstance(getProject()).createUsageView(UsageTarget.EMPTY_ARRAY, new Usage[]{usage}, new UsageViewPresentation(), null);
Disposer.register(myFixture.getTestRootDisposable(), usageView);
UsageView usageView = createUsageView(new Usage[]{usage});
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(getProject());
Document document = documentManager.getDocument(psiFile);
@@ -118,11 +117,17 @@ public class UsageViewTest extends LightPlatformCodeInsightFixtureTestCase {
PsiNamedElement bar = (PsiNamedElement)members[members.length - 3];
assertEquals("bar", bar.getName());
UsageTarget target = new PsiElement2UsageTargetAdapter(bar);
FindUsagesManager usagesManager = ((FindManagerImpl)FindManager.getInstance(getProject())).getFindUsagesManager();
FindUsagesHandler handler = usagesManager.getNewFindUsagesHandler(bar, false);
UsageViewImpl usageView =
(UsageViewImpl)usagesManager.doFindUsages(new PsiElement[]{bar}, PsiElement.EMPTY_ARRAY, handler, handler.getFindUsagesOptions(), false);
ProgressManager.getInstance().run(new Task.Modal(getProject(), "waiting", false) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
usageView.waitForUpdateRequestsCompletion();
usageView.drainQueuedUsageNodes();
}
});
Disposer.register(myFixture.getTestRootDisposable(), usageView);
@@ -156,9 +161,7 @@ public class UsageViewTest extends LightPlatformCodeInsightFixtureTestCase {
PsiFile psiFile = myFixture.addFileToProject("X.java", "public class X{ int xxx; } //comment");
Usage usage = new UsageInfo2UsageAdapter(new UsageInfo(psiFile, psiFile.getText().indexOf("xxx"), StringUtil.indexOfSubstringEnd(psiFile.getText(),"xxx")));
UsageViewImpl usageView =
(UsageViewImpl)UsageViewManager.getInstance(getProject()).createUsageView(UsageTarget.EMPTY_ARRAY, new Usage[]{usage}, new UsageViewPresentation(), null);
Disposer.register(myFixture.getTestRootDisposable(), usageView);
UsageViewImpl usageView = createUsageView(new Usage[]{usage});
usageView.excludeUsages(new Usage[]{usage});
UIUtil.dispatchAllInvocationEvents();
@@ -204,14 +207,27 @@ public class UsageViewTest extends LightPlatformCodeInsightFixtureTestCase {
" 1 public class X{ int xxx; } //comment\n", StringUtil.convertLineSeparators(text));
}
@NotNull
private UsageViewImpl createUsageView(@NotNull Usage[] usages) {
UsageViewImpl usageView =
(UsageViewImpl)UsageViewManager.getInstance(getProject()).createUsageView(UsageTarget.EMPTY_ARRAY, usages, new UsageViewPresentation(), null);
Disposer.register(myFixture.getTestRootDisposable(), usageView);
ProgressManager.getInstance().run(new Task.Modal(getProject(), "Waiting For Usages", false) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
usageView.drainQueuedUsageNodes();
}
});
UIUtil.dispatchAllInvocationEvents();
return usageView;
}
public void testExcludeNodeMustExcludeChildrenAndParents() {
PsiFile psiFile = myFixture.addFileToProject("X.java", "public class X{ int xxx; } //comment");
Usage usage = new UsageInfo2UsageAdapter(new UsageInfo(psiFile, psiFile.getText().indexOf("xxx"), StringUtil.indexOfSubstringEnd(psiFile.getText(),"xxx")));
UsageViewImpl usageView =
(UsageViewImpl)UsageViewManager.getInstance(getProject()).createUsageView(UsageTarget.EMPTY_ARRAY, new Usage[]{usage}, new UsageViewPresentation(), null);
Disposer.register(myFixture.getTestRootDisposable(), usageView);
UIUtil.dispatchAllInvocationEvents();
UsageViewImpl usageView = createUsageView(new Usage[]{usage});
Node[] usageNode = new Node[1];
TreeUtil.traverse(usageView.getRoot(), node -> {
@@ -15,6 +15,7 @@
*/
package com.intellij.usages.impl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.usages.UsageView;
import com.intellij.util.BitUtil;
import com.intellij.util.Consumer;
@@ -30,7 +31,7 @@ import java.util.Vector;
public abstract class Node extends DefaultMutableTreeNode {
private int myCachedTextHash;
private byte myCachedFlags; // bit packed flags below:
private byte myCachedFlags; // guarded by this; bit packed flags below:
static final byte EXCLUDED_MASK = 1<<3;
private static final byte UPDATED_MASK = 1<<4;
@@ -41,11 +42,11 @@ public abstract class Node extends DefaultMutableTreeNode {
@MagicConstant(intValues = {CACHED_INVALID_MASK, CACHED_READ_ONLY_MASK, READ_ONLY_COMPUTED_MASK, EXCLUDED_MASK, UPDATED_MASK})
private @interface FlagConstant {}
private boolean isFlagSet(@FlagConstant byte mask) {
private synchronized boolean isFlagSet(@FlagConstant byte mask) {
return BitUtil.isSet(myCachedFlags, mask);
}
private void setFlag(@FlagConstant byte mask, boolean value) {
private synchronized void setFlag(@FlagConstant byte mask, boolean value) {
myCachedFlags = BitUtil.set(myCachedFlags, mask, value);
}
@@ -92,6 +93,8 @@ public abstract class Node extends DefaultMutableTreeNode {
}
final synchronized void update(@NotNull UsageView view, @NotNull Consumer<Node> edtNodeChangedQueue) {
// performance: always update in background because smart pointer' isValid() can cause PSI chameleons expansion which is ridiculously expensive in cpp
assert !ApplicationManager.getApplication().isDispatchThread();
boolean isDataValid = isDataValid();
boolean isReadOnly = isDataReadOnly();
String text = getText(view);
@@ -127,9 +130,11 @@ public abstract class Node extends DefaultMutableTreeNode {
// same as DefaultMutableTreeNode.insert() except it doesn't try to remove the newChild from its parent since we know it's new
void insertNewNode(@NotNull Node newChild, int childIndex) {
ApplicationManager.getApplication().assertIsDispatchThread();
if (children == null) {
children = new Vector();
}
//noinspection unchecked
children.insertElementAt(newChild, childIndex);
}
@@ -498,8 +498,12 @@ class SearchForUsagesRunnable implements Runnable {
}
}
UsageViewImpl usageView = myUsageViewRef.get();
if (usageView != null) {
usageView.waitForUpdateRequestsCompletion();
}
if (myListener != null) {
myListener.findingUsagesFinished(myUsageViewRef.get());
myListener.findingUsagesFinished(usageView);
}
}
@@ -12,6 +12,7 @@ import com.intellij.navigation.NavigationItem;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.progress.ProgressIndicator;
@@ -22,10 +23,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.SimpleToolWindowPanel;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Factory;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.*;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
@@ -53,6 +51,7 @@ import com.intellij.util.concurrency.EdtExecutorService;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.LinkedMultiMap;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.containers.Queue;
import com.intellij.util.enumeration.EmptyEnumeration;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.DialogUtil;
@@ -77,9 +76,7 @@ import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -158,6 +155,7 @@ public class UsageViewImpl implements UsageView {
private Usage myOriginUsage;
@Nullable private Runnable myRerunActivity;
private boolean myDisposeSmartPointersOnClose = true;
private final Queue<Future<?>> updateRequests = new Queue<>(10); // guarded by insertionRequests
public UsageViewImpl(@NotNull final Project project,
@NotNull UsageViewPresentation presentation,
@@ -469,6 +467,7 @@ public class UsageViewImpl implements UsageView {
Rectangle visibleRect = myTree.getVisibleRect();
int rowForLocation = myTree.getClosestRowForLocation(0, visibleRect.y);
int visibleRowCount = getVisibleRowCount();
List<Node> toUpdate = new ArrayList<>();
for (int i = rowForLocation + visibleRowCount + 1; i >= rowForLocation; i--) {
final TreePath eachPath = myTree.getPathForRow(i);
if (eachPath == null) continue;
@@ -476,10 +475,14 @@ public class UsageViewImpl implements UsageView {
treeState.invalidatePathBounds(eachPath);
Object node = eachPath.getLastPathComponent();
if (node instanceof UsageNode) {
((UsageNode)node).update(this, edtNodeChangedQueue);
toUpdate.add((Node)node);
}
}
myTree.repaint(visibleRect);
queueUpdateBulk(toUpdate, ()->{
if (!isDisposed()) {
myTree.repaint(visibleRect);
}
});
}
else {
myTree.setCellRenderer(myUsageViewTreeCellRenderer);
@@ -637,7 +640,9 @@ public class UsageViewImpl implements UsageView {
if (component instanceof Node) {
Node node = (Node)component;
if (!expandingAll && node.needsUpdate()) {
checkNodeValidity(node, path);
List<Node> toUpdate = new ArrayList<>();
checkNodeValidity(node, path, toUpdate);
queueUpdateBulk(toUpdate, EmptyRunnable.getInstance());
}
}
}
@@ -886,17 +891,17 @@ public class UsageViewImpl implements UsageView {
reset();
myBuilder.setGroupingRules(getActiveGroupingRules(myProject, getUsageViewSettings()));
myBuilder.setFilteringRules(getActiveFilteringRules(myProject));
ApplicationManager.getApplication().runReadAction(() -> {
for (Usage usage : allUsages) {
if (!usage.isValid()) {
continue;
}
if (usage instanceof MergeableUsage) {
((MergeableUsage)usage).reset();
}
appendUsage(usage);
for (int i = allUsages.size() - 1; i >= 0; i--) {
Usage usage = allUsages.get(i);
if (!usage.isValid()) {
allUsages.remove(i);
continue;
}
});
if (usage instanceof MergeableUsage) {
((MergeableUsage)usage).reset();
}
}
appendUsagesInBulk(allUsages);
if (myTree != null) {
excludeUsages(excludedUsages.toArray(Usage.EMPTY_ARRAY));
}
@@ -1081,10 +1086,50 @@ public class UsageViewImpl implements UsageView {
@Override
public void appendUsage(@NotNull Usage usage) {
doAppendUsage(usage);
if (ApplicationManager.getApplication().isDispatchThread()) {
addUpdateRequest(ApplicationManager.getApplication().executeOnPooledThread(() -> ReadAction.run(() -> doAppendUsage(usage))));
}
else {
doAppendUsage(usage);
}
}
private void addUpdateRequest(@NotNull Future<?> request) {
synchronized (updateRequests) {
while (!updateRequests.isEmpty() && updateRequests.peekFirst().isDone()) {
updateRequests.pullFirst();
}
updateRequests.addLast(request);
}
}
void waitForUpdateRequestsCompletion() {
assert !ApplicationManager.getApplication().isDispatchThread();
while (true) {
Future<?> request;
synchronized (updateRequests) {
request = updateRequests.isEmpty() ? null : updateRequests.pullFirst();
}
if (request == null) break;
try {
request.get();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
void appendUsagesInBulk(@NotNull Collection<Usage> usages) {
addUpdateRequest(ApplicationManager.getApplication().executeOnPooledThread(() -> ReadAction.run(() -> {
for (Usage usage : usages) {
doAppendUsage(usage);
}
})));
}
public UsageNode doAppendUsage(@NotNull Usage usage) {
assert !ApplicationManager.getApplication().isDispatchThread();
// invoke in ReadAction to be be sure that usages are not invalidated while the tree is being built
ApplicationManager.getApplication().assertReadAccessAllowed();
if (!usage.isValid()) {
@@ -1175,28 +1220,46 @@ public class UsageViewImpl implements UsageView {
ApplicationManager.getApplication().assertIsDispatchThread();
if (myProject.isDisposed()) return;
TreeNode root = (TreeNode)myTree.getModel().getRoot();
checkNodeValidity(root, new TreePath(root));
List<Node> toUpdate = new ArrayList<>();
checkNodeValidity(root, new TreePath(root), toUpdate);
queueUpdateBulk(toUpdate, EmptyRunnable.getInstance());
updateOnSelectionChanged();
}
private void queueUpdateBulk(@NotNull List<Node> toUpdate, @NotNull Runnable onCompletedInEdt) {
if (toUpdate.isEmpty()) return;
addUpdateRequest(ApplicationManager.getApplication().executeOnPooledThread(() -> {
for (Node node : toUpdate) {
try {
if (isDisposed()) break;
ReadAction.run(() -> node.update(this, edtNodeChangedQueue));
}
catch (IndexNotReadyException ignore) {
}
}
ApplicationManager.getApplication().invokeLater(onCompletedInEdt);
}));
}
private void updateImmediatelyNodesUpToRoot(@NotNull Collection<Node> nodes) {
ApplicationManager.getApplication().assertIsDispatchThread();
if (myProject.isDisposed()) return;
TreeNode root = (TreeNode)myTree.getModel().getRoot();
Set<Node> updated = new HashSet<>();
Set<Node> queued = new HashSet<>();
List<Node> toUpdate = new ArrayList<>();
while (true) {
Set<Node> parents = new HashSet<>();
for (Node node : nodes) {
node.update(this, edtNodeChangedQueue);
toUpdate.add(node);
TreeNode parent = node.getParent();
if (parent != root && parent instanceof Node && updated.add((Node)parent)) {
if (parent != root && parent instanceof Node && queued.add((Node)parent)) {
parents.add((Node)parent);
}
}
if (parents.isEmpty()) break;
nodes = parents;
}
queueUpdateBulk(toUpdate, EmptyRunnable.getInstance());
updateImmediately();
}
@@ -1212,7 +1275,7 @@ public class UsageViewImpl implements UsageView {
}
}
private void checkNodeValidity(@NotNull TreeNode node, @NotNull TreePath path) {
private void checkNodeValidity(@NotNull TreeNode node, @NotNull TreePath path, @NotNull List<Node> result) {
ApplicationManager.getApplication().assertIsDispatchThread();
boolean shouldCheckChildren = true;
if (myTree.isCollapsed(path)) {
@@ -1230,17 +1293,13 @@ public class UsageViewImpl implements UsageView {
if (shouldCheckChildren && isVisible != UsageViewTreeCellRenderer.RowLocation.AFTER_VISIBLE_RECT) {
for (int i=0; i < node.getChildCount(); i++) {
TreeNode child = node.getChildAt(i);
checkNodeValidity(child, path.pathByAddingChild(child));
checkNodeValidity(child, path.pathByAddingChild(child), result);
}
}
// call update last, to let children a chance to update their cache first
if (node instanceof Node && node != getModelRoot() && isVisible == UsageViewTreeCellRenderer.RowLocation.INSIDE_VISIBLE_RECT) {
try {
((Node)node).update(this, edtNodeChangedQueue);
}
catch (IndexNotReadyException ignore) {
}
result.add((Node)node);
}
}
@@ -46,6 +46,7 @@ import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -67,8 +68,15 @@ public class UsageViewManagerImpl extends UsageViewManager {
@NotNull UsageViewPresentation presentation,
Factory<UsageSearcher> usageSearcherFactory) {
UsageViewImpl usageView = new UsageViewImpl(myProject, presentation, targets, usageSearcherFactory);
appendUsages(usages, usageView);
usageView.appendUsagesInBulk(Arrays.asList(usages));
ProgressManager.getInstance().run(new Task.Modal(myProject, "Waiting For Usages", false) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
usageView.waitForUpdateRequestsCompletion();
}
});
usageView.setSearchInProgress(false);
return usageView;
}
@@ -205,14 +213,6 @@ public class UsageViewManagerImpl extends UsageViewManager {
}
}
protected static void appendUsages(@NotNull final Usage[] foundUsages, @NotNull final UsageViewImpl usageView) {
ApplicationManager.getApplication().runReadAction(() -> {
for (Usage foundUsage : foundUsages) {
usageView.appendUsage(foundUsage);
}
});
}
public static void showTooManyUsagesWarningLater(@NotNull final Project project,
@NotNull final TooManyUsagesStatus tooManyUsagesStatus,