void (@NotNull UpdateInfo updateInfo, @NotNull DefaultMutableTreeNode node) { assertIsDispatchThread(); Object oldElementFromDescriptor = getElementFromDescriptor(updateInfo.getDescriptor()); if (isNodeNull(oldElementFromDescriptor)) return; UpdateInfo loaded = getLoadedInBackground(oldElementFromDescriptor); if (loaded != null) { loaded.apply(updateInfo); return; } addToLoadedInBackground(oldElementFromDescriptor, updateInfo); maybeSetBusyAndScheduleWaiterForReady(true, oldElementFromDescriptor); if (!isNodeBeingBuilt(node)) { LoadingNode loadingNode = new LoadingNode(getLoadingNodeText()); myTreeModel.insertNodeInto(loadingNode, node, node.getChildCount()); } removeFromUnbuilt(node); Ref<LoadedChildren> children = new Ref<>(); Ref<Object> elementFromDescriptor = new Ref<>(); DefaultMutableTreeNode[] nodeToProcessActions = new DefaultMutableTreeNode[1]; TreeConsumer<Void> finalizeRunnable = new TreeConsumer<>("AbstractTreeUi.queueBackgroundUpdate: finalize") { @Override public void perform() { invokeLaterIfNeeded(false, new TreeRunnable("AbstractTreeUi.queueBackgroundUpdate: finalize later") { @Override public void perform() { if (isReleased()) return; removeLoading(node, false); removeFromLoadedInBackground(elementFromDescriptor.get()); removeFromLoadedInBackground(oldElementFromDescriptor); if (nodeToProcessActions[0] != null) { processNodeActionsIfReady(nodeToProcessActions[0]); } } }); } }; Runnable buildRunnable = new TreeRunnable("AbstractTreeUi.queueBackgroundUpdate: build") { @Override public void perform() { if (updateInfo.getPass().isExpired()) { finalizeRunnable.run(); return; } if (!updateInfo.isDescriptorIsUpToDate()) { update(updateInfo.getDescriptor(), true); } if (!updateInfo.isUpdateChildren()) { nodeToProcessActions[0] = node; return; } Object element = getElementFromDescriptor(updateInfo.getDescriptor()); if (element == null) { removeFromLoadedInBackground(oldElementFromDescriptor); finalizeRunnable.run(); return; } elementFromDescriptor.set(element); Object[] loadedElements = getChildrenFor(element); LoadedChildren loaded = new LoadedChildren(loadedElements); for (Object each : loadedElements) { NodeDescriptor<?> existingDesc = getDescriptorFrom(getNodeForElement(each, true)); NodeDescriptor<?> eachChildDescriptor = isValid(existingDesc, updateInfo.getDescriptor()) ? existingDesc : getTreeStructure().createDescriptor(each, updateInfo.getDescriptor()); execute(new TreeRunnable("AbstractTreeUi.queueBackgroundUpdate") { @Override public void perform() { try { loaded.putDescriptor(each, eachChildDescriptor, update(eachChildDescriptor, true).blockingGet(0)); } catch (TimeoutException | ExecutionException e) { LOG.error(e); } } }); } children.set(loaded); } @Override public @NotNull @NonNls String toString() { return "runnable=" + oldElementFromDescriptor; } }; Runnable updateRunnable = new TreeRunnable("AbstractTreeUi.queueBackgroundUpdate: update") { @Override public void perform() { if (updateInfo.getPass().isExpired()) { finalizeRunnable.run(); return; } if (children.get() == null) { finalizeRunnable.run(); return; } if (isRerunNeeded(updateInfo.getPass())) { removeFromLoadedInBackground(elementFromDescriptor.get()); getUpdater().requeue(updateInfo.getPass()); return; } removeFromLoadedInBackground(elementFromDescriptor.get()); if (myUnbuiltNodes.contains(node)) { Pair<Boolean, LoadedChildren> unbuilt = processUnbuilt(node, updateInfo.getDescriptor(), updateInfo.getPass(), isExpanded(node, updateInfo.isWasExpanded()), children.get()); if (unbuilt.getFirst()) { nodeToProcessActions[0] = node; return; } } ActionCallback callback = updateNodeChildren(node, updateInfo.getPass(), children.get(), true, updateInfo.isCanSmartExpand(), updateInfo.isForceUpdate(), true, true); callback.doWhenDone(new TreeRunnable("AbstractTreeUi.queueBackgroundUpdate: on done updateNodeChildren") { @Override public void perform() { if (isRerunNeeded(updateInfo.getPass())) { getUpdater().requeue(updateInfo.getPass()); return; } Object element = elementFromDescriptor.get(); if (element != null) { removeLoading(node, false); nodeToProcessActions[0] = node; } } }); } }; queueToBackground(buildRunnable, updateRunnable) .onSuccess(finalizeRunnable) .onError(new TreeConsumer<>("AbstractTreeUi.queueBackgroundUpdate: on rejected") { @Override public void perform() { updateInfo.getPass().expire(); } }); }