diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfoUpdaterImpl.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfoUpdaterImpl.java index 20f770ee557c..cfa30bffb753 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfoUpdaterImpl.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightInfoUpdaterImpl.java @@ -285,6 +285,30 @@ final class HighlightInfoUpdaterImpl extends HighlightInfoUpdater implements Dis @NotNull HighlightingSession session) { Map data = getData(psiFile, hostDocument); ToolHighlights toolHighlights = newInfos.isEmpty() ? data.get(toolId) : data.computeIfAbsent(toolId, __ -> new ToolHighlights()); + runSynchronized(toolHighlights, () -> { + List oldInfos = toolHighlights == null ? null : toolHighlights.elementHighlights.get(visitedPsiElement); + List newInfosToStore; + if (oldInfos != null || !newInfos.isEmpty()) { + newInfosToStore = List.copyOf(newInfos); + if (LOG.isDebugEnabled()) { + //noinspection removal + LOG.debug("psiElementVisited: " + visitedPsiElement + " in " + visitedPsiElement.getTextRange() + + (psiFile.getViewProvider() instanceof InjectedFileViewProvider ? + " injected in " + InjectedLanguageManager.getInstance(project).injectedToHost(psiFile, psiFile.getTextRange()) : "") + + "; tool:" + toolId + "; infos:" + newInfosToStore + "; oldInfos:" + oldInfos + "; document:" + hostDocument); + } + MarkupModelEx markup = (MarkupModelEx)DocumentMarkupModel.forDocument(hostDocument, project, true); + setHighlightersInRange(newInfosToStore, oldInfos, markup, session); + } + else { + newInfosToStore = List.of(); + } + // store back only after markup model changes are applied to avoid PCE thrown in the middle leaving corrupted data behind + putInfosForVisitedPsi(data, toolId, visitedPsiElement, newInfosToStore, toolHighlights); + }); + } + + private void runSynchronized(@Nullable ToolHighlights toolHighlights, @NotNull Runnable runnable) { // Sometimes multiple file editors are submitted for highlighting, some of which may have the same underlying document, // e.g., when the editor for file v is opened along with the git log with "preview diff" for the same file. // In this case, it's possible that several instances of e.g., LocalInspectionPass can run in parallel, @@ -292,27 +316,9 @@ final class HighlightInfoUpdaterImpl extends HighlightInfoUpdater implements Dis // so we need to guard `ToolHighlights` against parallel modification: Object monitor = toolHighlights == null ? this : toolHighlights; synchronized (monitor) { - List oldInfos = toolHighlights == null ? null : toolHighlights.elementHighlights.get(visitedPsiElement); - if (oldInfos != null || !newInfos.isEmpty()) { - newInfos = List.copyOf(newInfos); - if (LOG.isDebugEnabled()) { - //noinspection removal - LOG.debug("psiElementVisited: " + visitedPsiElement + " in " + visitedPsiElement.getTextRange() + - (psiFile.getViewProvider() instanceof InjectedFileViewProvider ? - " injected in " + InjectedLanguageManager.getInstance(project).injectedToHost(psiFile, psiFile.getTextRange()) : "") + - "; tool:" + toolId + "; infos:" + newInfos + "; oldInfos:" + oldInfos + "; document:" + hostDocument); - } - MarkupModelEx markup = (MarkupModelEx)DocumentMarkupModel.forDocument(hostDocument, project, true); - setHighlightersInRange(newInfos, oldInfos, markup, session); - } - else { - newInfos = List.of(); - } - // store back only after markup model changes are applied to avoid PCE thrown in the middle leaving corrupted data behind - putInfosForVisitedPsi(data, toolId, visitedPsiElement, newInfos, toolHighlights); + runnable.run(); } } - private static void setHighlightersInRange(@NotNull List newInfos, @Nullable List oldInfos, @NotNull MarkupModelEx markup, @@ -371,35 +377,37 @@ final class HighlightInfoUpdaterImpl extends HighlightInfoUpdater implements Dis // remove all highlight infos from `data` generated by tools absent in 'actualToolsRun' - static void removeHighlightsForObsoleteTools(@NotNull PsiFile containingFile, - @NotNull Document hostDocument, - @NotNull List injectedFragments, - @NotNull Set> actualToolsRun, - @NotNull HighlightingSession highlightingSession) { - for (PsiFile psiFile: ContainerUtil.append(injectedFragments, containingFile)) { - getData(psiFile, hostDocument).entrySet().removeIf(entry -> { - Object toolId = entry.getKey(); - ToolHighlights toolHighlights = entry.getValue(); - if (UNKNOWN_ID.equals(toolId) || !isInspectionToolId(toolId)) { - return false; - } + void removeHighlightsForObsoleteTools(@NotNull PsiFile containingFile, + @NotNull Document hostDocument, + @NotNull List injectedFragments, + @NotNull Set> actualToolsRun, + @NotNull HighlightingSession highlightingSession) { + synchronized (this) { + for (PsiFile psiFile: ContainerUtil.append(injectedFragments, containingFile)) { + getData(psiFile, hostDocument).entrySet().removeIf(entry -> { + Object toolId = entry.getKey(); + ToolHighlights toolHighlights = entry.getValue(); + if (UNKNOWN_ID.equals(toolId) || !isInspectionToolId(toolId)) { + return false; + } - if (actualToolsRun.contains(Pair.create(toolId, psiFile))) { - return false; - } - for (List highlights : toolHighlights.elementHighlights.values()) { - for (HighlightInfo info : highlights) { - RangeHighlighterEx highlighter = info.highlighter; - if (highlighter != null) { - if (LOG.isTraceEnabled()) { - LOG.trace("removeHighlightsForObsoleteTools: " + highlighter); + if (actualToolsRun.contains(Pair.create(toolId, psiFile))) { + return false; + } + for (List highlights : toolHighlights.elementHighlights.values()) { + for (HighlightInfo info : highlights) { + RangeHighlighterEx highlighter = info.highlighter; + if (highlighter != null) { + if (LOG.isTraceEnabled()) { + LOG.trace("removeHighlightsForObsoleteTools: " + highlighter); + } + UpdateHighlightersUtil.disposeWithFileLevelIgnoreErrors(highlighter, info, highlightingSession); } - UpdateHighlightersUtil.disposeWithFileLevelIgnoreErrors(highlighter, info, highlightingSession); } } - } - return true; - }); + return true; + }); + } } } @@ -408,7 +416,7 @@ final class HighlightInfoUpdaterImpl extends HighlightInfoUpdater implements Dis } // TODO very dirty method which throws all incrementality away, but we'd need to rewrite too many inspections to get rid of it - static void removeWarningsInsideErrors(@NotNull List injectedFragments, + void removeWarningsInsideErrors(@NotNull List injectedFragments, @NotNull Document hostDocument, @NotNull HighlightingSession highlightingSession) { HighlightersRecycler recycler = new HighlightersRecycler(); @@ -441,20 +449,22 @@ final class HighlightInfoUpdaterImpl extends HighlightInfoUpdater implements Dis recycler.recycleHighlighter(highlighter); ToolHighlights elementHighlights = map.get(info.toolId); - for (Map.Entry> elementEntry : elementHighlights.elementHighlights.entrySet()) { - List infos = elementEntry.getValue(); - int i = infos.indexOf(info); - if (i != -1) { - List listMinusInfo = ContainerUtil.concat(infos.subList(0, i), infos.subList(i + 1, infos.size())); - if (listMinusInfo.isEmpty()) { - elementHighlights.elementHighlights.remove(elementEntry.getKey()); + runSynchronized(elementHighlights, () -> { + for (Map.Entry> elementEntry : elementHighlights.elementHighlights.entrySet()) { + List infos = elementEntry.getValue(); + int i = infos.indexOf(info); + if (i != -1) { + List listMinusInfo = ContainerUtil.concat(infos.subList(0, i), infos.subList(i + 1, infos.size())); + if (listMinusInfo.isEmpty()) { + elementHighlights.elementHighlights.remove(elementEntry.getKey()); + } + else { + elementEntry.setValue(listMinusInfo); + } + break; } - else { - elementEntry.setValue(listMinusInfo); - } - break; } - } + }); } } return true; @@ -471,7 +481,7 @@ final class HighlightInfoUpdaterImpl extends HighlightInfoUpdater implements Dis * after inspections completed, save their latencies (from corresponding {@link InspectionRunner.InspectionContext#holder}) * to use later in {@link com.intellij.codeInsight.daemon.impl.InspectionProfilerDataHolder#sortByLatencies(PsiFile, List, HighlightInfoUpdaterImpl)} */ - static void saveLatencies(@NotNull PsiFile psiFile, @NotNull Map latencies) { + void saveLatencies(@NotNull PsiFile psiFile, @NotNull Map latencies) { if (!psiFile.getViewProvider().isPhysical()) { // ignore editor text fields/consoles etc. return; @@ -483,10 +493,13 @@ final class HighlightInfoUpdaterImpl extends HighlightInfoUpdater implements Dis ToolHighlights toolHighlights = map.get(toolId); // no point saving latencies if nothing was reported if (toolHighlights == null) continue; - ToolLatencies lats = entry.getValue(); - toolHighlights.latencies = new ToolLatencies(merge(toolHighlights.latencies.errorLatency, lats.errorLatency), - merge(toolHighlights.latencies.warningLatency, lats.warningLatency), - merge(toolHighlights.latencies.otherLatency, lats.otherLatency)); + + runSynchronized(toolHighlights, () -> { + ToolLatencies lats = entry.getValue(); + toolHighlights.latencies = new ToolLatencies(merge(toolHighlights.latencies.errorLatency, lats.errorLatency), + merge(toolHighlights.latencies.warningLatency, lats.warningLatency), + merge(toolHighlights.latencies.otherLatency, lats.otherLatency)); + }); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InspectionProfilerDataHolder.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InspectionProfilerDataHolder.java index 9ab1514898c7..6d9701739100 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InspectionProfilerDataHolder.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/InspectionProfilerDataHolder.java @@ -28,7 +28,7 @@ class InspectionProfilerDataHolder { Math.max(0, context.holder().toolStamps.errorStamp - context.holder().toolStamps.initTimeStamp), Math.max(0, context.holder().toolStamps.warningStamp - context.holder().toolStamps.initTimeStamp), Math.max(0, context.holder().toolStamps.otherStamp - context.holder().toolStamps.initTimeStamp)))); - HighlightInfoUpdaterImpl.saveLatencies(psiFile, latencies); + highlightInfoUpdater.saveLatencies(psiFile, latencies); } /** diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java index 5ad2546cb6ec..6e5105b08b2f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java @@ -147,8 +147,10 @@ public final class LocalInspectionsPass extends ProgressableTextEditorHighlighti injectedFragments = runner.getInjectedFragments(); } Set> pairs = ContainerUtil.map2Set(resultContexts, context -> Pair.create(context.tool().getShortName(), context.psiFile())); - HighlightInfoUpdaterImpl.removeHighlightsForObsoleteTools(getFile(), getDocument(), injectedFragments, pairs, getHighlightingSession()); - HighlightInfoUpdaterImpl.removeWarningsInsideErrors(injectedFragments, getDocument(), getHighlightingSession()); // must be the last + if (myHighlightInfoUpdater instanceof HighlightInfoUpdaterImpl impl) { + impl.removeHighlightsForObsoleteTools(getFile(), getDocument(), injectedFragments, pairs, getHighlightingSession()); + impl.removeWarningsInsideErrors(injectedFragments, getDocument(), getHighlightingSession()); // must be the last + } } private static final TextAttributes NONEMPTY_TEXT_ATTRIBUTES = new UnmodifiableTextAttributes(){