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
This commit is contained in:
Alexey Kudravtsev
2025-07-16 11:04:38 +00:00
committed by intellij-monorepo-bot
parent 6eb4c859ff
commit 26e13c9315
2 changed files with 55 additions and 31 deletions
@@ -78,8 +78,6 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
private final Map<Document, Throwable> myUncommittedDocumentTraces = CollectionFactory.createConcurrentWeakMap();
private /*non-static*/ final Key<UncommittedInfo> 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();
}
}
}
@@ -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<Action> 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();
}
});
}
}