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
This commit is contained in:
Sergei Tachenov
2025-08-14 13:39:19 +00:00
committed by intellij-monorepo-bot
parent 8e268c7d87
commit cf70eee6ae
@@ -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<String, AbstractProjectViewPane> newPanes = loadPanes();
Map<AbstractProjectViewPane, Boolean> 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<String, AbstractProjectViewPane> loadPanes() {
private synchronized @NotNull Map<String, AbstractProjectViewPane> loadPanes() {
Map<String, AbstractProjectViewPane> map = new LinkedHashMap<>();
List<AbstractProjectViewPane> 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<Element> 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);
}
}