diff --git a/java/java-tests/testSrc/com/intellij/index/ConcurrentIndexTest.groovy b/java/java-tests/testSrc/com/intellij/index/ConcurrentIndexTest.groovy index eb351137a346..126781a1ba23 100644 --- a/java/java-tests/testSrc/com/intellij/index/ConcurrentIndexTest.groovy +++ b/java/java-tests/testSrc/com/intellij/index/ConcurrentIndexTest.groovy @@ -14,21 +14,27 @@ * limitations under the License. */ package com.intellij.index + +import com.intellij.lang.FCTSBackedLighterAST import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ReadAction import com.intellij.openapi.command.WriteCommandAction import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile import com.intellij.psi.PsiJavaFile import com.intellij.psi.impl.PsiDocumentManagerBase +import com.intellij.psi.impl.search.JavaNullMethodArgumentUtil import com.intellij.psi.impl.source.PsiFileImpl import com.intellij.psi.search.GlobalSearchScope import com.intellij.testFramework.BombedProgressIndicator import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.SkipSlowTestLocally import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase +import com.intellij.util.GCUtil import java.util.concurrent.CountDownLatch +import java.util.concurrent.Future /** * @author peter */ @@ -170,4 +176,36 @@ class ConcurrentIndexTest extends JavaCodeInsightFixtureTestCase { for(future in futuresToWait) future.get() } } + + void "test concurrent light AST access during uncommitted document indexing"() { + def clazz = myFixture.addClass('class Bar { void foo(Object o) {}}') + + def text = " foo(null);"; + for (i in 0..20) { + text = "new Runnable() { void run() {\n " + text + "\n}}.run();" + } + text = "class Foo {{ " + text * 200 + "}}" + + def file = myFixture.addFileToProject('a.java', text) + def document = file.viewProvider.document + for (i in 1..5) { + WriteCommandAction.runWriteCommandAction project, { + document.insertString(document.text.indexOf('null') + 1, ' ') + document.insertString(document.text.indexOf('(null') + 1, ' ') + PsiDocumentManager.getInstance(project).commitAllDocuments() + } + GCUtil.tryGcSoftlyReachableObjects() + + assert file.node.lighterAST instanceof FCTSBackedLighterAST + List futures = [] + futures << ApplicationManager.application.executeOnPooledThread { ReadAction.run { + assert !JavaNullMethodArgumentUtil.hasNullArgument(clazz.methods[0], 0) + } } + futures << ApplicationManager.application.executeOnPooledThread { ReadAction.run { + assert JavaPsiFacade.getInstance(project).findClass('Foo', GlobalSearchScope.allScope(project)) + } } + futures.each { it.get() } + } + } + } diff --git a/platform/core-impl/src/com/intellij/util/indexing/FileContentImpl.java b/platform/core-impl/src/com/intellij/util/indexing/FileContentImpl.java index 69d75c85aad2..c3fd6fe7fb5c 100644 --- a/platform/core-impl/src/com/intellij/util/indexing/FileContentImpl.java +++ b/platform/core-impl/src/com/intellij/util/indexing/FileContentImpl.java @@ -15,6 +15,7 @@ */ package com.intellij.util.indexing; +import com.intellij.lang.FileASTNode; import com.intellij.lang.Language; import com.intellij.lang.LighterAST; import com.intellij.openapi.editor.Document; @@ -85,12 +86,26 @@ public class FileContentImpl extends UserDataHolderBase implements FileContent { LighterAST lighterAST = getUserData(IndexingDataKeys.LIGHTER_AST_NODE_KEY); if (lighterAST == null) { lighterAST = getPsiFileForPsiDependentIndex().getNode().getLighterAST(); - assert lighterAST != null; putUserData(IndexingDataKeys.LIGHTER_AST_NODE_KEY, lighterAST); } return lighterAST; } + /** + * Expand the AST to ensure {@link com.intellij.lang.FCTSBackedLighterAST} won't be used, because it's not thread-safe, + * but unsaved documents may be indexed in many concurrent threads + */ + void ensureThreadSafeLighterAST() { + ensureParsed(getPsiFile().getNode()); + ensureParsed(getPsiFileForPsiDependentIndex().getNode()); + } + + private static void ensureParsed(@Nullable FileASTNode node) { + if (node != null) { + node.getFirstChildNode(); + } + } + public PsiFile createFileFromText(@NotNull CharSequence text) { Project project = getProject(); if (project == null) { @@ -232,6 +247,7 @@ public class FileContentImpl extends UserDataHolderBase implements FileContent { myHash = hash; } + @NotNull public PsiFile getPsiFileForPsiDependentIndex() { Document document = FileDocumentManager.getInstance().getCachedDocument(getFile()); PsiFile psi = null; diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java index 34c01d0ca5d3..df5b30c5d3ef 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java @@ -1346,6 +1346,7 @@ public class FileBasedIndexImpl extends FileBasedIndex { } initFileContent(newFc, project, dominantContentFile); + newFc.ensureThreadSafeLighterAST(); if (content instanceof AuthenticContent) { newFc.putUserData(PlatformIdTableBuilding.EDITOR_HIGHLIGHTER,