From 26e13c93152d562729640dd1c85593e3cf07d19e Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Tue, 15 Jul 2025 17:15:00 +0200 Subject: [PATCH] do not try to commit all documents synchronously if there are too many of them in the queue, it will freeze EDT (part of IJPL-50077 freezing on replace) GitOrigin-RevId: 2a10e2f817fa46ab0854cddc7cff9dd676695b3a --- .../psi/impl/PsiDocumentManagerBase.java | 49 +++++++++---------- .../psi/impl/PsiDocumentManagerImplTest.java | 37 ++++++++++++-- 2 files changed, 55 insertions(+), 31 deletions(-) diff --git a/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java b/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java index 12d63fbbcd70..4e6505c3fc3c 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java +++ b/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java @@ -78,8 +78,6 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen private final Map myUncommittedDocumentTraces = CollectionFactory.createConcurrentWeakMap(); private /*non-static*/ final Key UNCOMMITTED_INFO_KEY = Key.create("UNCOMMITTED_INFO"); - @ApiStatus.Internal - protected boolean myStopTrackingDocuments; private boolean myPerformBackgroundCommit = true; @SuppressWarnings("ThreadLocalNotStaticFinal") @@ -983,7 +981,9 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen @Override public void beforeDocumentChange(@NotNull DocumentEvent event) { - if (myStopTrackingDocuments || myProject.isDisposed()) return; + if (myProject.isDisposed()) { + return; + } Document document = event.getDocument(); VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); @@ -1026,7 +1026,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen @Override public void documentChanged(@NotNull DocumentEvent event) { - if (myStopTrackingDocuments || myProject.isDisposed()) { + if (myProject.isDisposed()) { return; } @@ -1088,32 +1088,27 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen } // optimisation: avoid documents piling up during batch processing - if (isUncommited(document) && areTooManyDocumentsInTheQueue(myUncommittedDocuments)) { - // must not commit during document save - if (PomModelImpl.isAllowPsiModification() - // it can happen that document(forUseInNonAWTThread=true) outside write action caused this - && ApplicationManager.getApplication().isWriteAccessAllowed()) { - // commit one document to avoid OOME - for (Document uncommitted : myUncommittedDocuments) { - if (uncommitted != event.getDocument()) { - commitDocument(uncommitted); - break; + if (myUnitTestMode && isUncommited(document) && areTooManyDocumentsInTheQueue(myUncommittedDocuments)) { + try { + Logger.getInstance(getClass()).error( + "Too many uncommitted documents (" +myUncommittedDocuments.size()+"):\n" + + StringUtil.join(myUncommittedDocuments, "\n") + myProject); + // must not commit during document save + if (PomModelImpl.isAllowPsiModification() + // it can happen that document(forUseInNonAWTThread=true) outside write action caused this + && ApplicationManager.getApplication().isWriteAccessAllowed()) { + // commit one document to avoid OOME + for (Document uncommitted : myUncommittedDocuments) { + if (uncommitted != document) { + commitDocument(uncommitted); + break; + } } } } - if (myUnitTestMode) { - myStopTrackingDocuments = true; - try { - //noinspection TestOnlyProblems - Logger.getInstance(getClass()).error( - "Too many uncommitted documents for " + myProject + "(" +myUncommittedDocuments.size()+")"+ - ":\n" + StringUtil.join(myUncommittedDocuments, "\n") + - myProject); - } - finally { - //noinspection TestOnlyProblems - clearUncommittedDocuments(); - } + finally { + //noinspection TestOnlyProblems + clearUncommittedDocuments(); } } } diff --git a/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiDocumentManagerImplTest.java b/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiDocumentManagerImplTest.java index d01a6f7680de..da482063e526 100644 --- a/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiDocumentManagerImplTest.java +++ b/platform/platform-tests/testSrc/com/intellij/psi/impl/PsiDocumentManagerImplTest.java @@ -41,10 +41,7 @@ import com.intellij.openapi.vfs.encoding.EncodingProjectManager; import com.intellij.openapi.vfs.limits.FileSizeLimit; import com.intellij.psi.*; import com.intellij.psi.impl.source.PsiFileImpl; -import com.intellij.testFramework.HeavyPlatformTestCase; -import com.intellij.testFramework.LeakHunter; -import com.intellij.testFramework.LightVirtualFile; -import com.intellij.testFramework.PlatformTestUtil; +import com.intellij.testFramework.*; import com.intellij.util.ArrayUtilRt; import com.intellij.util.IncorrectOperationException; import com.intellij.util.TestTimeOut; @@ -66,6 +63,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; @@ -994,4 +992,35 @@ public class PsiDocumentManagerImplTest extends HeavyPlatformTestCase { } }); } + + public void testHugeAmountOfChangedDocumentsMustNotCauseSynchronousCommitLeadingToUIFreeze() { + LoggedErrorProcessor.executeWith(new LoggedErrorProcessor() { + @Override + public @NotNull Set processError(@NotNull String category, @NotNull String message, String @NotNull [] details, Throwable t) { + if (message.contains("Too many uncommitted documents")) { + return Action.NONE; + } + return super.processError(category, message, details, t); + } + }, () -> { + PsiDocumentManagerImpl psiDocumentManager = (PsiDocumentManagerImpl)PsiDocumentManager.getInstance(getProject()); + psiDocumentManager.myUnitTestMode = false; // test the prod behaviour + try { + for (int i=0;i<5_000;i++) { + PsiFile mainFile = findFile(createFile()); + Document doc = getDocument(mainFile); + psiDocumentManager.addRunOnCommit(doc, d ->{ + ApplicationManager.getApplication().assertIsNonDispatchThread(); + }); + WriteCommandAction.runWriteCommandAction(getProject(), () -> { + doc.insertString(0," "); + }); + } + } + finally { + psiDocumentManager.myUnitTestMode = true; + psiDocumentManager.clearUncommittedDocuments(); + } + }); + } } \ No newline at end of file