From 4d1677be06112a9ab2dda1a5db2dccaf14996643 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Sun, 12 Jan 2020 01:55:59 +0300 Subject: [PATCH] Invalidate PerModulePackageCache on content change in file without view provider (KT-35907) Investigation was started because of flaky behaviour in NewJavaToKotlinConverterSingleFileTestGenerated tests. Approximately 1 of 3 all tests executions caused failure in one of the tests. Analyze showed that unlike the successful run, failure test was missing PSI event with PerModulePackageCache index invalidation. This was caused by null from FileManagerImpl.findCachedViewProvider() and null from FileDocumentManagerImpl.getDocumentFromCache(). Both methods use weak references maps to operate: FileManagerImpl.myVFileToViewProviderMap and FileDocumentManagerImpl.myDocumentCache. #KT-35907 Fixed --- .../caches/PerModulePackageCacheService.kt | 51 +++++++++++++++---- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt index a510a4187cb..173d13202cf 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/PerModulePackageCacheService.kt @@ -10,7 +10,6 @@ import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.fileTypes.FileTypeRegistry import com.intellij.openapi.module.Module import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.project.Project @@ -25,6 +24,7 @@ import com.intellij.openapi.vfs.newvfs.BulkFileListener import com.intellij.openapi.vfs.newvfs.events.* import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager +import com.intellij.psi.impl.PsiManagerEx import com.intellij.psi.impl.PsiTreeChangeEventImpl import com.intellij.psi.impl.PsiTreeChangePreprocessor import com.intellij.psi.search.GlobalSearchScope @@ -50,26 +50,53 @@ import java.util.concurrent.ConcurrentMap class KotlinPackageContentModificationListener(private val project: Project) : Disposable { val connection = project.messageBus.connect() + companion object { + val LOG = Logger.getInstance(this::class.java) + } + init { connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener { - override fun before(events: MutableList) = onEvents(events) - override fun after(events: List) = onEvents(events) + override fun before(events: MutableList) = onEvents(events, false) + override fun after(events: List) = onEvents(events, true) - private fun isRelevant(it: VFileEvent): Boolean = - it is VFileMoveEvent || it is VFileCreateEvent || it is VFileCopyEvent || it is VFileDeleteEvent + private fun isRelevant(event: VFileEvent): Boolean = when (event) { + is VFilePropertyChangeEvent -> false + is VFileCreateEvent -> true + is VFileMoveEvent -> true + is VFileDeleteEvent -> true + is VFileContentChangeEvent -> true + is VFileCopyEvent -> true + else -> { + LOG.warn("Unknown vfs event: ${event.javaClass}") + false + } + } - fun onEvents(events: List) { + fun onEvents(events: List, isAfter: Boolean) { val service = PerModulePackageCacheService.getInstance(project) + val fileManager = PsiManagerEx.getInstanceEx(project).fileManager if (events.size >= FULL_DROP_THRESHOLD) { service.onTooComplexChange() } else { events.asSequence() .filter(::isRelevant) - .filter { (it.isValid || it !is VFileCreateEvent) && it.file != null } + .filter { + (it.isValid || it !is VFileCreateEvent) && it.file != null + } .filter { val vFile = it.file!! vFile.isDirectory || vFile.fileType == KotlinFileType.INSTANCE } + .filter { + // It expected that content change events will be duplicated with more precise PSI events and processed + // in KotlinPackageStatementPsiTreeChangePreprocessor, but events might have been missing if PSI view provider + // is absent. + if (it is VFileContentChangeEvent) { + isAfter && fileManager.findCachedViewProvider(it.file) == null + } else { + true + } + } .filter { when (val origin = it.requestor) { is Project -> origin == project @@ -97,7 +124,9 @@ class KotlinPackageContentModificationListener(private val project: Project) : D class KotlinPackageStatementPsiTreeChangePreprocessor(private val project: Project) : PsiTreeChangePreprocessor { override fun treeChanged(event: PsiTreeChangeEventImpl) { val eFile = event.file ?: event.child as? PsiFile - if (eFile == null) LOG.debugIfEnabled(project, true) { "Got PsiEvent: $event without file" } + if (eFile == null) { + LOG.debugIfEnabled(project, true) { "Got PsiEvent: $event without file" } + } val file = eFile as? KtFile ?: return when (event.code) { @@ -118,10 +147,12 @@ class KotlinPackageStatementPsiTreeChangePreprocessor(private val project: Proje return } val childrenOfType = parent.getChildrenOfType() - if ((!event.isGenericChange && (childrenOfType.any() || parent is KtPackageDirective)) || + if ( + (!event.isGenericChange && (childrenOfType.any() || parent is KtPackageDirective)) || (childrenOfType.any { it.name.isEmpty() } && parent is KtFile) - ) + ) { ServiceManager.getService(project, PerModulePackageCacheService::class.java).notifyPackageChange(file) + } } else -> { }