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:
+34
-10
@@ -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() {
|
||||
|
||||
+8
-5
@@ -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
|
||||
|
||||
+2
-19
@@ -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 }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user