mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
don't let PCE during free-threaded document change lead to leaks and eternal semi-committedness (IDEA-182419)
This commit is contained in:
@@ -705,17 +705,45 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
|
||||
assertNotNestedModification();
|
||||
myChangeInProgress = true;
|
||||
DelayedExceptions exceptions = new DelayedExceptions();
|
||||
try {
|
||||
DocumentEvent event = new DocumentEventImpl(this, offset, oldString, newString, myModificationStamp, wholeTextReplaced, initialStartOffset, initialOldLength);
|
||||
beforeChangedUpdate(event);
|
||||
beforeChangedUpdate(event, exceptions);
|
||||
myTextString = null;
|
||||
ImmutableCharSequence prevText = myText;
|
||||
myText = newText;
|
||||
sequence.incrementAndGet(); // increment sequence before firing events so that modification sequence on commit will match this sequence now
|
||||
changedUpdate(event, newModificationStamp, prevText);
|
||||
changedUpdate(event, newModificationStamp, prevText, exceptions);
|
||||
}
|
||||
finally {
|
||||
myChangeInProgress = false;
|
||||
exceptions.rethrowPCE();
|
||||
}
|
||||
}
|
||||
|
||||
private class DelayedExceptions {
|
||||
Throwable myException = null;
|
||||
|
||||
void register(Throwable e) {
|
||||
if (myException == null) {
|
||||
myException = e;
|
||||
} else {
|
||||
myException.addSuppressed(e);
|
||||
}
|
||||
|
||||
if (!(e instanceof ProcessCanceledException)) {
|
||||
LOG.error(e);
|
||||
}
|
||||
else if (myAssertThreading) {
|
||||
LOG.error("ProcessCanceledException must not be thrown from document listeners for real document", new Throwable(e));
|
||||
}
|
||||
}
|
||||
|
||||
void rethrowPCE() {
|
||||
if (myException instanceof ProcessCanceledException) {
|
||||
// the case of some wise inspection modifying non-physical document during highlighting to be interrupted
|
||||
throw (ProcessCanceledException)myException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,7 +752,7 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
return sequence.get();
|
||||
}
|
||||
|
||||
private void beforeChangedUpdate(DocumentEvent event) {
|
||||
private void beforeChangedUpdate(DocumentEvent event, DelayedExceptions exceptions) {
|
||||
Application app = ApplicationManager.getApplication();
|
||||
if (app != null) {
|
||||
FileDocumentManager manager = FileDocumentManager.getInstance();
|
||||
@@ -743,11 +771,8 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
try {
|
||||
listeners[i].beforeDocumentChange(event);
|
||||
}
|
||||
catch (ProcessCanceledException e) {
|
||||
throw e; // the case of some wise inspection modifying non-physical document during highlighting to be interrupted
|
||||
}
|
||||
catch (Throwable e) {
|
||||
LOG.error(e);
|
||||
exceptions.register(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -764,7 +789,7 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
}
|
||||
}
|
||||
|
||||
private void changedUpdate(@NotNull DocumentEvent event, long newModificationStamp, @NotNull CharSequence prevText) {
|
||||
private void changedUpdate(@NotNull DocumentEvent event, long newModificationStamp, @NotNull CharSequence prevText, DelayedExceptions exceptions) {
|
||||
try {
|
||||
if (LOG.isDebugEnabled()) LOG.debug(event.toString());
|
||||
|
||||
@@ -784,16 +809,8 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
try {
|
||||
listener.documentChanged(event);
|
||||
}
|
||||
catch (ProcessCanceledException e) {
|
||||
if (!myAssertThreading) {
|
||||
throw e;
|
||||
}
|
||||
else {
|
||||
LOG.error("ProcessCanceledException must not be thrown from document listeners for real document", new Throwable(e));
|
||||
}
|
||||
}
|
||||
catch (Throwable e) {
|
||||
LOG.error(e);
|
||||
exceptions.register(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-3
@@ -26,8 +26,10 @@ import com.intellij.openapi.application.impl.LaterInvocator;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import com.intellij.openapi.editor.ex.DocumentEx;
|
||||
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
|
||||
import com.intellij.openapi.editor.ex.PrioritizedDocumentListener;
|
||||
import com.intellij.openapi.editor.impl.DocumentImpl;
|
||||
import com.intellij.openapi.editor.impl.EditorImpl;
|
||||
import com.intellij.openapi.editor.impl.TrailingSpacesStripper;
|
||||
@@ -35,8 +37,10 @@ import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
|
||||
import com.intellij.openapi.fileTypes.PlainTextFileType;
|
||||
import com.intellij.openapi.fileTypes.PlainTextLanguage;
|
||||
import com.intellij.openapi.fileTypes.StdFileTypes;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
@@ -807,7 +811,7 @@ public class PsiDocumentManagerImplTest extends PlatformTestCase {
|
||||
Document document = file.getViewProvider().getDocument();
|
||||
|
||||
Semaphore semaphore = new Semaphore(1);
|
||||
TransactionGuard.submitTransaction(getTestRootDisposable(), () -> {
|
||||
TransactionGuard.submitTransaction(getTestRootDisposable(), () ->
|
||||
WriteCommandAction.runWriteCommandAction(myProject, () -> {
|
||||
document.insertString(0, "x");
|
||||
|
||||
@@ -817,8 +821,7 @@ public class PsiDocumentManagerImplTest extends PlatformTestCase {
|
||||
getPsiDocumentManager().commitAndRunReadAction(() -> semaphore.up());
|
||||
}
|
||||
}, new ProgressWindow(false, myProject));
|
||||
});
|
||||
});
|
||||
}));
|
||||
int iteration = 0;
|
||||
while (!semaphore.waitFor(10)) {
|
||||
UIUtil.dispatchAllInvocationEvents();
|
||||
@@ -829,6 +832,30 @@ public class PsiDocumentManagerImplTest extends PlatformTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testNoLeaksAfterPCEInListener() {
|
||||
PsiFile file = PsiFileFactory.getInstance(myProject).createFileFromText("a.txt", PlainTextFileType.INSTANCE, "");
|
||||
Document document = file.getViewProvider().getDocument();
|
||||
document.addDocumentListener(new PrioritizedDocumentListener() {
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDocumentChange(DocumentEvent event) {
|
||||
throw new ProcessCanceledException();
|
||||
}
|
||||
});
|
||||
try {
|
||||
document.insertString(0, "a");
|
||||
fail("PCE expected");
|
||||
}
|
||||
catch (ProcessCanceledException ignored) {
|
||||
}
|
||||
waitTenSecondsForCommit(document);
|
||||
LeakHunter.checkLeak(getPsiDocumentManager(), Document.class, d -> d == document);
|
||||
}
|
||||
|
||||
private static void assertLargeFileContentLimited(@NotNull String content, @NotNull VirtualFile vFile, @NotNull Document document) {
|
||||
Charset charset = EncodingManager.getInstance().getEncoding(vFile, false);
|
||||
float bytesPerChar = charset == null ? 2 : charset.newEncoder().averageBytesPerChar();
|
||||
|
||||
Reference in New Issue
Block a user