Psi/Document inconsistency


We're developing plugin for our language, and I've got some problem, I cannot explain. I have the code that generates some other code and inserts it into document :

ApplicationManager.getApplication().invokeLater(new Runnable() { public void run() {     ApplicationManager.getApplication().runWriteAction(new Runnable() {         public void run() {             if(metaUsage.isValid()) { // can become not valid                 generating = true;                 LSFMetaCodeBody body = metaUsage.getMetaCodeBody();                 if(fMetaText==null) {                     if(body!=null)                         body.delete();                 } else {                     LSFMetaCodeBody genBody = LSFElementGenerator.createMetaBodyFromText(myProject, fMetaText);                     if(body != null) {                         body.replace(genBody);                     } else {                         metaUsage.getNode().addChild(genBody.getNode(), metaUsage.getMetaCodeIdList().getNode().getTreeNext().getTreeNext());                     }                 }                 generating = false;             }         }     }); } });

And when running body.replace(genBody), or adding child, at first I get the following error:

[ 252914]  ERROR - impl.PsiToDocumentSynchronizer - Attempt to modify PSI for non-committed Document! java.lang.Throwable      at com.intellij.openapi.diagnostic.Logger.error(      at com.intellij.psi.impl.PsiToDocumentSynchronizer.a(      at com.intellij.psi.impl.PsiToDocumentSynchronizer.childReplaced(      at com.intellij.psi.impl.PsiManagerImpl.a(      at com.intellij.psi.impl.PsiManagerImpl.childReplaced(      at com.intellij.pom.wrappers.PsiEventWrapperAspect.a(      at com.intellij.pom.wrappers.PsiEventWrapperAspect.update(      at com.intellij.pom.core.impl.PomModelImpl.runTransaction(      at com.intellij.psi.impl.source.tree.ChangeUtil.prepareAndRunChangeAction(      at com.intellij.psi.impl.source.tree.CompositeElement.replaceChild(      at com.intellij.psi.impl.source.codeStyle.CodeEditUtil.replaceChild(      at com.intellij.extapi.psi.ASTDelegatePsiElement.replaceChildInternal(      at com.intellij.extapi.psi.ASTDelegatePsiElement.replace(      at com.simpleplugin.psi.MetaChangeDetector$MetaUsageProcessing$1$1$      at com.intellij.openapi.application.impl.ApplicationImpl.runWriteAction(      at com.simpleplugin.psi.MetaChangeDetector$MetaUsageProcessing$1$      at com.intellij.openapi.application.impl.LaterInvocator$      at java.awt.event.InvocationEvent.dispatch(      at java.awt.EventQueue.dispatchEventImpl(      at java.awt.EventQueue.access$000(      at java.awt.EventQueue$      at java.awt.EventQueue$      at Method)      at$1.doIntersectionPrivilege(      at java.awt.EventQueue.dispatchEvent(      at com.intellij.ide.IdeEventQueue.d(      at com.intellij.ide.IdeEventQueue._dispatchEvent(      at com.intellij.ide.IdeEventQueue.dispatchEvent(      at java.awt.EventDispatchThread.pumpOneEventForFilters(      at java.awt.EventDispatchThread.pumpEventsForFilter(      at java.awt.EventDispatchThread.pumpEventsForHierarchy(      at java.awt.EventDispatchThread.pumpEvents(      at java.awt.EventDispatchThread.pumpEvents(      at

And then after that I am constantly getting:

[ 319811]  ERROR - .psi.impl.DocumentCommitThread - PSI/document inconsistency before reparse: file=Lsf File java.lang.AssertionError: PSI/document inconsistency before reparse: file=Lsf File      at com.intellij.psi.impl.DocumentCommitThread.a(      at com.intellij.psi.impl.DocumentCommitThread.a(      at com.intellij.psi.impl.DocumentCommitThread.access$600(      at com.intellij.psi.impl.DocumentCommitThread$      at com.intellij.openapi.application.impl.ApplicationImpl.tryRunReadAction(      at com.intellij.psi.impl.DocumentCommitThread.a(      at com.intellij.psi.impl.DocumentCommitThread.access$500(      at com.intellij.psi.impl.DocumentCommitThread$      at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(      at com.intellij.psi.impl.DocumentCommitThread.b(      at      at

What I don't get is how document can become inconsistent, when the whole process of changing PSI is wrapped into runWriteAction, and it acquires global lock, and after its completion, commit document synchronously. Or am I wrong, and the last process is asynchronous?

PS: This exception i get, only when I run described code very very often (for example press the button and don't realease it, what triggers described event "continously").


Wrapping everything into performForCommittedDocument, or commit document before helps (and commiting after doesn't), but giving to that such behaviour is really not safe, because the other processes may work with incorrect psi without knowing it (isValid will be true). Or I am missing something?


Committing the document means building an up-to-date PSI structure from the document contents. If you don't commit the document before you start modifying the PSI, you're working with an out-of-date PSI structure that doesn't correspond to the actual contents of the document, which is what the exception is telling you. Committing the document after you modify the PSI makes no sense, because you've already changed the PSI structure.

PSI modifications can be performed only in the event dispatch thread while holding a write action. A write action is exclusive, which means that there can be no other processes running under a read action in parallel to your code. And if someone holds a PSI element without holding a read action, then this element can become invalid at any point in time, which is expected behavior.


So the only safe case for modification action is the following:

UIUtil.invokeLaterIfNeeded - because we can run PSI modification only in main UI thread
DocumentManage.performForCommitedDocument - because the document may be not-commited, and we'll get the described exception
Application.runWriteAction - we need to block read actions (to prevent them from getting dirty data). Write actions may only be executed in current thread, so their blocking is a sort of "side effect".

Eliminating any of these steps in common case will lead to exceptions. Am I right?


Committing the document after you modify the PSI makes no sense, because you've already changed the PSI structure.

And what about text offsets and so on...

It was discussed here:


I'm not sure that using performForCommittedDocument() would be correct in your case. It adds your runnable to the list of runnables which will be eventually executed when the document is committed. Most likely you just need to call commitDocument() and then perform your operation.


What about text offsets? This only matters if you need to perform some modifications through the document immediately after some PSI modifications. Since you're only working with the PSI, this shouldn't matter to you at all.


Please sign in to leave a comment.