ScriptDependenciesCache: limit simultaneously cached script count

Do not schedule updates for dependencies of scripts that were not requested
   (opened in the editor or such) for some time
This fixes a problem when vcs updates changing scripts can trigger
   undesired script dependencies updates
   (probable cause of script files never highlighted correctly in some cases)
Refactor: use VirtualFile instead of String and move code from GradleScriptTemplateProvider
This commit is contained in:
Pavel V. Talanov
2017-10-19 15:29:54 +03:00
parent f67e01db1c
commit df74fb23f8
3 changed files with 44 additions and 34 deletions
@@ -24,8 +24,10 @@ import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElementFinder
import com.intellij.psi.PsiManager
import com.intellij.psi.search.NonClasspathDirectoriesScope
import com.intellij.util.containers.SLRUMap
import kotlinx.coroutines.experimental.launch
import org.jetbrains.kotlin.idea.core.util.EDT
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
@@ -35,14 +37,16 @@ import kotlin.reflect.KProperty0
import kotlin.reflect.jvm.isAccessible
import kotlin.script.experimental.dependencies.ScriptDependencies
private const val MAX_SCRIPTS_CACHED = 15
class ScriptDependenciesCache(private val project: Project) {
private val cacheLock = ReentrantReadWriteLock()
private val cache = hashMapOf<String, ScriptDependencies>()
private val cache = SLRUMap<VirtualFile, ScriptDependencies>(0, MAX_SCRIPTS_CACHED)
operator fun get(virtualFile: VirtualFile): ScriptDependencies? = cacheLock.read { cache[virtualFile.path] }
operator fun get(virtualFile: VirtualFile): ScriptDependencies? = cacheLock.read { cache[virtualFile] }
val allScriptsClasspath by ClearableLazyValue(cacheLock) {
val files = cache.values.flatMap { it.classpath }.distinct()
val files = cache.entrySet().flatMap { it.value.classpath }.distinct()
ScriptDependenciesManager.toVfsRoots(files)
}
@@ -51,15 +55,13 @@ class ScriptDependenciesCache(private val project: Project) {
}
val allLibrarySources by ClearableLazyValue(cacheLock) {
ScriptDependenciesManager.toVfsRoots(cache.values.flatMap { it.sources }.distinct())
ScriptDependenciesManager.toVfsRoots(cache.entrySet().flatMap { it.value.sources }.distinct())
}
val allLibrarySourcesScope by ClearableLazyValue(cacheLock) {
NonClasspathDirectoriesScope(allLibrarySources)
}
fun <T> inspectCache(body: (Map<String, ScriptDependencies>) -> T): T = cacheLock.read { body(cache) }
private fun onChange(file: VirtualFile?) {
this::allScriptsClasspath.clearValue()
this::allScriptsClasspathScope.clearValue()
@@ -102,10 +104,9 @@ class ScriptDependenciesCache(private val project: Project) {
}
fun save(virtualFile: VirtualFile, new: ScriptDependencies): Boolean {
val path = virtualFile.path
val old = cacheLock.write {
val old = cache[path]
cache[path] = new
val old = cache[virtualFile]
cache.put(virtualFile, new)
old
}
val changed = new != old
@@ -118,13 +119,36 @@ class ScriptDependenciesCache(private val project: Project) {
fun delete(virtualFile: VirtualFile): Boolean {
val changed = cacheLock.write {
cache.remove(virtualFile.path) != null
cache.remove(virtualFile) != null
}
if (changed) {
onChange(virtualFile)
}
return changed
}
fun combineDependencies(filePredicate: (VirtualFile) -> Boolean): ScriptDependencies = cacheLock.read {
val sources = mutableListOf<File>()
val binaries = mutableListOf<File>()
val imports = mutableListOf<String>()
val scripts = mutableListOf<File>()
val relevantEntries = cache.entrySet().filter { filePredicate(it.key) }.map { it.value }
relevantEntries.forEach {
sources += it.sources
binaries += it.classpath
imports += it.imports
scripts += it.scripts
}
return ScriptDependencies(
classpath = binaries.distinct(),
sources = sources.distinct(),
imports = imports.distinct(),
scripts = scripts.distinct(),
javaHome = relevantEntries.map { it.javaHome }.firstOrNull()
)
}
}
private fun <R> KProperty0<R>.clearValue() {
@@ -84,7 +84,7 @@ internal class ScriptDependenciesUpdater(
tryUsingDefault(file)
}
updateCache(listOf(file))
performUpdate(file)
return cache[file] ?: ScriptDependencies.Empty
}
@@ -108,17 +108,20 @@ internal class ScriptDependenciesUpdater(
}
}
private fun updateCache(files: Iterable<VirtualFile>) =
private fun requestUpdate(files: Iterable<VirtualFile>) =
files.map { file ->
if (!file.isValid) {
return cache.delete(file)
}
else if (cache[file] != null) { // only update dependencies for scripts that were touched recently
performUpdate(file)
}
else {
updateForFile(file)
false
}
}.contains(true)
private fun updateForFile(file: VirtualFile): Boolean {
private fun performUpdate(file: VirtualFile): Boolean {
val scriptDef = scriptDefinitionProvider.findScriptDefinition(file) ?: return false
return when (scriptDef.dependencyResolver) {
@@ -263,7 +266,7 @@ internal class ScriptDependenciesUpdater(
return
}
if (updateCache(events.mapNotNull {
if (requestUpdate(events.mapNotNull {
// The check is partly taken from the BuildManager.java
it.file?.takeIf {
// the isUnitTestMode check fixes ScriptConfigurationHighlighting & Navigation tests, since they are not trigger proper update mechanims
@@ -181,23 +181,6 @@ class GradleScriptDefaultDependenciesProvider(
return previouslyAnalyzedScriptsCombinedDependencies().takeUnless { it.classpath.isEmpty() }
}
private fun previouslyAnalyzedScriptsCombinedDependencies(): ScriptDependencies {
val sources = mutableListOf<File>()
val binaries = mutableListOf<File>()
val imports = mutableListOf<String>()
scriptDependenciesCache.inspectCache { cache ->
cache.entries.filter { it.key.endsWith(KOTLIN_BUILD_FILE_NAME) }
.map { it.value }.forEach {
sources += it.sources
binaries += it.classpath
imports += it.imports
}
}
return ScriptDependencies(
classpath = binaries.distinct(),
sources = sources.distinct(),
imports = imports.distinct()
)
}
private fun previouslyAnalyzedScriptsCombinedDependencies() =
scriptDependenciesCache.combineDependencies { it.name == KOTLIN_BUILD_FILE_NAME }
}