From 1dfa05fe4969c66fb35d55597160575079435e94 Mon Sep 17 00:00:00 2001 From: Simon Ogorodnik Date: Fri, 18 Aug 2017 20:43:53 +0300 Subject: [PATCH] KT-19769: Optimize PerModulePackageCacheService #KT-19769 fixed --- .../caches/PerModulePackageCacheService.kt | 83 +++++++++++++++---- 1 file changed, 66 insertions(+), 17 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 2fdc448796c..7bdfe756c70 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 @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.idea.caches import com.intellij.openapi.components.service +import com.intellij.openapi.fileTypes.FileTypeRegistry import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.openapi.util.Ref @@ -29,6 +30,8 @@ import com.intellij.psi.impl.PsiTreeChangePreprocessor import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.containers.ContainerUtil import org.jetbrains.kotlin.analyzer.ModuleInfo +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.caches.PerModulePackageCacheService.Companion.FULL_DROP_THRESHOLD import org.jetbrains.kotlin.idea.caches.resolve.ModuleSourceInfo import org.jetbrains.kotlin.idea.caches.resolve.getModuleInfoByVirtualFile import org.jetbrains.kotlin.idea.caches.resolve.getNullableModuleInfo @@ -46,20 +49,25 @@ class KotlinPackageContentModificationListener( init { val connection = project.messageBus.connect() - val scope = GlobalSearchScope.projectScope(project) - - connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener.Adapter() { + connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener { override fun before(events: MutableList) = onEvents(events) { it is VFileDeleteEvent || it is VFileMoveEvent } override fun after(events: List) = onEvents(events) { it is VFileMoveEvent || it is VFileCreateEvent || it is VFileCopyEvent } - fun onEvents(events: List, filter: (VFileEvent) -> Boolean) = events.asSequence() - .filter(filter) - .map { it.file } - .filterNotNull() - .filter { it in scope } - .forEach { file -> - project.service().notifyPackageChange(file) - } + fun onEvents(events: List, eventPredicate: (VFileEvent) -> Boolean) { + + val service = project.service() + if (events.size >= FULL_DROP_THRESHOLD) { + service.onTooComplexChange() + } + else { + events + .asSequence() + .filter(eventPredicate) + .mapNotNull { it.file } + .filter { FileTypeRegistry.getInstance().getFileTypeByFileName(it.name) == KotlinFileType.INSTANCE } + .forEach { file -> service.notifyPackageChange(file) } + } + } }) } } @@ -69,7 +77,10 @@ class KotlinPackageStatementPsiTreeChangePreprocessor(private val project: Proje val file = event.file as? KtFile ?: return when (event.code) { - PsiTreeChangeEventImpl.PsiEventType.CHILD_ADDED, PsiTreeChangeEventImpl.PsiEventType.CHILD_MOVED, PsiTreeChangeEventImpl.PsiEventType.CHILD_REPLACED, PsiTreeChangeEventImpl.PsiEventType.CHILD_REMOVED -> { + PsiTreeChangeEventImpl.PsiEventType.CHILD_ADDED, + PsiTreeChangeEventImpl.PsiEventType.CHILD_MOVED, + PsiTreeChangeEventImpl.PsiEventType.CHILD_REPLACED, + PsiTreeChangeEventImpl.PsiEventType.CHILD_REMOVED -> { val child = event.child ?: return if (child.getParentOfType(false) != null) project.service().notifyPackageChange(file) @@ -89,20 +100,57 @@ class PerModulePackageCacheService(private val project: Project) { private val cache = ContainerUtil.createConcurrentWeakMap>>() - internal fun notifyPackageChange(file: VirtualFile) { - (getModuleInfoByVirtualFile(project, file) as? ModuleSourceInfo)?.let { onChangeInModuleSource(it) } + private val pendingDirectoryChanges: MutableSet = mutableSetOf() + private val pendingKtFileChanges: MutableSet = mutableSetOf() + + private val projectScope = GlobalSearchScope.projectScope(project) + + internal fun onTooComplexChange() = synchronized(this) { + pendingDirectoryChanges.clear() + pendingKtFileChanges.clear() + cache.values.forEach { it.set(null) } } - internal fun notifyPackageChange(file: KtFile) { - (file.getNullableModuleInfo() as? ModuleSourceInfo)?.let { onChangeInModuleSource(it) } + internal fun notifyPackageChange(file: VirtualFile): Unit = synchronized(this) { + if (file.isDirectory) { + pendingDirectoryChanges += file + } + else if (file.parent != null && file.parent.isDirectory) { + notifyPackageChange(file.parent) + } } - internal fun onChangeInModuleSource(moduleSourceInfo: ModuleSourceInfo) { + internal fun notifyPackageChange(file: KtFile): Unit = synchronized(this) { + pendingKtFileChanges += file + } + + internal fun invalidateCacheForModule(moduleSourceInfo: ModuleSourceInfo) { cache[moduleSourceInfo]?.set(null) } + internal fun checkPendingChanges() = synchronized(this) { + if (pendingDirectoryChanges.size + pendingKtFileChanges.size >= FULL_DROP_THRESHOLD) { + onTooComplexChange() + } + else { + pendingDirectoryChanges.forEach { vfile -> + if (vfile !in projectScope) return@forEach + (getModuleInfoByVirtualFile(project, vfile) as? ModuleSourceInfo)?.let { invalidateCacheForModule(it) } + } + pendingDirectoryChanges.clear() + + pendingKtFileChanges.forEach { file -> + if (file.virtualFile != null && file.virtualFile !in projectScope) return@forEach + (file.getNullableModuleInfo() as? ModuleSourceInfo)?.let { invalidateCacheForModule(it) } + } + pendingKtFileChanges.clear() + } + } + + fun packageExists(packageFqName: FqName, moduleInfo: ModuleSourceInfo): Boolean { val module = moduleInfo.module + checkPendingChanges() // Module own cache is a view on global cache. Since global cache based on WeakReferences when module // gets disposed this soft map will be disposed too, leading to drop soft refs on ModuleInfo's, and then to @@ -123,5 +171,6 @@ class PerModulePackageCacheService(private val project: Project) { companion object { private val PER_MODULE_PACKAGE_CACHE = Key.create>>>("per_module_package_cache") + val FULL_DROP_THRESHOLD = 1000 } } \ No newline at end of file