From cf70eee6ae8a8fefbf4984ee86419cec148b8efa Mon Sep 17 00:00:00 2001 From: Sergei Tachenov Date: Wed, 13 Aug 2025 15:32:44 +0300 Subject: [PATCH] IJPL-201073 Synchronize pane loading in the Project View One problem was that the code was only partially synchronized. This could've led to a variety of issues. For example, when setupImpl() calls ensurePanesLoaded(), it might return immediately because isExtensionsLoaded is true, but because ensurePanesLoaded() itself was not synchronized, it could've been possible that some other thread is executing it right now. So it would be possible that isExtensionsLoaded is true, but the panes were not actually loaded yet. Another problem was that getExtensions() is in theory cancellable, especially on the first invocation. If the first invocation happens in a cancellable action update (through getSelectInTargets), then it could throw an exception, leaving isExtensionsLoaded in the true state, thus making sure that the panes will never be loaded at all. Fix both issues by synchronizing the entire pane loading process and by introducing isExtensionsLoading, so now we can both prevent recursion by setting it immediately and allow for a second loading attempt if the previous one fails (so isExtensionsLoaded remains false). Take care to make sure that the code following isExtensionsLoaded = true is exception-safe. GitOrigin-RevId: ef8a4760a03180777f8ae535d534421948af6fb8 --- .../ide/projectView/impl/ProjectViewImpl.java | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewImpl.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewImpl.java index 22112a2d303a..44abb481f1ea 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewImpl.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewImpl.java @@ -107,8 +107,10 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo public static final @NonNls String ANDROID_VIEW_ID = "AndroidView"; private final CopyPasteDelegator copyPasteDelegator; + // all these booleans must be accessed only in synchronized code private boolean isInitialized; - private final AtomicBoolean isExtensionsLoaded = new AtomicBoolean(false); + private boolean isExtensionsLoading = false; + private boolean isExtensionsLoaded = false; private final @NotNull Project project; private boolean firstShow = true; @@ -1037,7 +1039,7 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo } private synchronized void reloadPanes() { - if (project.isDisposed() || !isExtensionsLoaded.get()) return; // panes will be loaded later + if (project.isDisposed() || !isExtensionsLoaded) return; // panes will be loaded later Map newPanes = loadPanes(); Map oldPanes = new IdentityHashMap<>(); @@ -1056,23 +1058,35 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo } } - private void ensurePanesLoaded() { - if (project.isDisposed() || isExtensionsLoaded.getAndSet(true)) { - // avoid recursive loading + private synchronized void ensurePanesLoaded() { + // one boolean is about avoiding recursion, the other actually checks if the job was already done + if (project.isDisposed() || isExtensionsLoading || isExtensionsLoaded) { return; } - for (AbstractProjectViewPane pane : loadPanes().values()) { - if (pane.isInitiallyVisible()) { - addProjectPane(pane); + isExtensionsLoading = true; + try { + for (AbstractProjectViewPane pane : loadPanes().values()) { + try { + if (pane.isInitiallyVisible()) { + addProjectPane(pane); + } + } + catch (Throwable e) { + LOG.warn("An exception occurred when trying to add the pane " + pane.getId() + ", it may not appear or may have inconsistent state"); + } } } + finally { + isExtensionsLoading = false; + } } - private @NotNull Map loadPanes() { + private synchronized @NotNull Map loadPanes() { Map map = new LinkedHashMap<>(); List toSort = new ArrayList<>(AbstractProjectViewPane.EP.getExtensions(project)); toSort.sort(PANE_WEIGHT_COMPARATOR); + isExtensionsLoaded = true; // safe code starts here, must not throw exceptions or the project view will be empty forever for (AbstractProjectViewPane pane : toSort) { AbstractProjectViewPane added = map.computeIfAbsent(pane.getId(), id -> pane); if (pane != added) { @@ -1423,7 +1437,7 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo return ProjectViewPane.ID; } - private void readPaneState(@NotNull Element panesElement) { + private synchronized void readPaneState(@NotNull Element panesElement) { List paneElements = panesElement.getChildren(ELEMENT_PANE); for (Element paneElement : paneElements) { String paneId = paneElement.getAttributeValue(ATTRIBUTE_ID); @@ -1441,11 +1455,12 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo } } - private static void applyPaneState(@NotNull AbstractProjectViewPane pane, @NotNull Element element) { + private static synchronized void applyPaneState(@NotNull AbstractProjectViewPane pane, @NotNull Element element) { try { pane.readExternal(element); } - catch (InvalidDataException ignored) { + catch (Throwable e) { + LOG.warn("An exception occurred when initializing the pane " + pane.getId() + ", its state may be inconsistent", e); } }