Scripts refactoring: move all scripts caches to ScriptDependenciesCache

Introduce base class for caches based on SLRUMap (with write lock) to avoid exceptions with concurrent map modifications
This commit is contained in:
Natalia Selezneva
2019-04-26 14:03:28 +03:00
parent 1ef4d987d1
commit 2f5deefd0a
4 changed files with 53 additions and 59 deletions
@@ -27,10 +27,7 @@ import com.intellij.util.containers.SLRUMap
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jetbrains.kotlin.idea.core.util.EDT import org.jetbrains.kotlin.idea.core.util.EDT
import org.jetbrains.kotlin.utils.addIfNotNull
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write import kotlin.concurrent.write
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
@@ -45,12 +42,18 @@ class ScriptDependenciesCache(private val project: Project) {
} }
private val cacheLock = ReentrantReadWriteLock() private val cacheLock = ReentrantReadWriteLock()
private val cache = SLRUMap<VirtualFile, ScriptDependencies>(MAX_SCRIPTS_CACHED, MAX_SCRIPTS_CACHED)
operator fun get(virtualFile: VirtualFile): ScriptDependencies? = cacheLock.write { cache[virtualFile] } private val scriptDependenciesCache = SLRUCacheWithLock<ScriptDependencies>()
private val scriptsModificationStampsCache = SLRUCacheWithLock<Long>()
operator fun get(virtualFile: VirtualFile): ScriptDependencies? = scriptDependenciesCache.get(virtualFile)
fun shouldRunDependenciesUpdate(file: VirtualFile): Boolean {
return scriptsModificationStampsCache.replace(file, file.modificationStamp) != file.modificationStamp
}
val allScriptsClasspath by ClearableLazyValue(cacheLock) { val allScriptsClasspath by ClearableLazyValue(cacheLock) {
val files = cache.entrySet().flatMap { it.value.classpath }.distinct() val files = scriptDependenciesCache.getAll().flatMap { it.value.classpath }.distinct()
ScriptDependenciesManager.toVfsRoots(files) ScriptDependenciesManager.toVfsRoots(files)
} }
@@ -59,7 +62,7 @@ class ScriptDependenciesCache(private val project: Project) {
} }
val allLibrarySources by ClearableLazyValue(cacheLock) { val allLibrarySources by ClearableLazyValue(cacheLock) {
ScriptDependenciesManager.toVfsRoots(cache.entrySet().flatMap { it.value.sources }.distinct()) ScriptDependenciesManager.toVfsRoots(scriptDependenciesCache.getAll().flatMap { it.value.sources }.distinct())
} }
val allLibrarySourcesScope by ClearableLazyValue(cacheLock) { val allLibrarySourcesScope by ClearableLazyValue(cacheLock) {
@@ -99,21 +102,15 @@ class ScriptDependenciesCache(private val project: Project) {
} }
fun clear() { fun clear() {
val keys = cacheLock.read { val keys = scriptDependenciesCache.getAll().map { it.key }.toList()
val keys = mutableListOf<VirtualFile>()
cache.iterateKeys { keys.addIfNotNull(it) } scriptDependenciesCache.clear()
cacheLock.write(cache::clear)
keys
}
onChange(keys) onChange(keys)
} }
fun save(virtualFile: VirtualFile, new: ScriptDependencies): Boolean { fun save(virtualFile: VirtualFile, new: ScriptDependencies): Boolean {
val old = cacheLock.write { val old = scriptDependenciesCache.replace(virtualFile, new)
val old = cache[virtualFile]
cache.put(virtualFile, new)
old
}
val changed = new != old val changed = new != old
if (changed) { if (changed) {
onChange(listOf(virtualFile)) onChange(listOf(virtualFile))
@@ -123,37 +120,12 @@ class ScriptDependenciesCache(private val project: Project) {
} }
fun delete(virtualFile: VirtualFile): Boolean { fun delete(virtualFile: VirtualFile): Boolean {
val changed = cacheLock.write { val changed = scriptDependenciesCache.remove(virtualFile)
cache.remove(virtualFile)
}
if (changed) { if (changed) {
onChange(listOf(virtualFile)) onChange(listOf(virtualFile))
} }
return changed 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() { private fun <R> KProperty0<R>.clearValue() {
@@ -184,3 +156,35 @@ private class ClearableLazyValue<in R, out T : Any>(
} }
} }
private class SLRUCacheWithLock<T> {
private val lock = ReentrantReadWriteLock()
val cache = SLRUMap<VirtualFile, T>(
ScriptDependenciesCache.MAX_SCRIPTS_CACHED,
ScriptDependenciesCache.MAX_SCRIPTS_CACHED
)
fun get(value: VirtualFile): T? = lock.write {
cache[value]
}
fun remove(file: VirtualFile) = lock.write {
cache.remove(file)
}
fun getAll(): Collection<Map.Entry<VirtualFile, T>> = lock.write {
cache.entrySet()
}
fun clear() = lock.write {
cache.clear()
}
fun replace(file: VirtualFile, value: T): T? = lock.write {
val old = get(file)
cache.put(file, value)
old
}
}
@@ -77,7 +77,7 @@ class ScriptDependenciesManager internal constructor(
@TestOnly @TestOnly
fun updateScriptDependenciesSynchronously(virtualFile: VirtualFile, project: Project) { fun updateScriptDependenciesSynchronously(virtualFile: VirtualFile, project: Project) {
val loader = SyncScriptDependenciesLoader(project) val loader = SyncScriptDependenciesLoader(project)
loader.updateDependencies(virtualFile) loader.loadDependencies(virtualFile)
loader.notifyRootsChanged() loader.notifyRootsChanged()
} }
} }
@@ -92,7 +92,9 @@ class ScriptDependenciesUpdater(
} }
private fun updateDependencies(file: VirtualFile) { private fun updateDependencies(file: VirtualFile) {
loaders.filter { it.isApplicable(file) }.forEach { it.updateDependencies(file) } if (!cache.shouldRunDependenciesUpdate(file)) return
loaders.filter { it.isApplicable(file) }.forEach { it.loadDependencies(file) }
} }
private fun makeRootsChangeIfNeeded() { private fun makeRootsChangeIfNeeded() {
@@ -13,9 +13,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ex.ProjectRootManagerEx import com.intellij.openapi.roots.ex.ProjectRootManagerEx
import com.intellij.openapi.util.EmptyRunnable import com.intellij.openapi.util.EmptyRunnable
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.SLRUMap
import org.jetbrains.kotlin.idea.core.script.* import org.jetbrains.kotlin.idea.core.script.*
import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesCache.Companion.MAX_SCRIPTS_CACHED
import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.ScriptContentLoader import org.jetbrains.kotlin.scripting.resolve.ScriptContentLoader
@@ -26,19 +24,9 @@ import kotlin.script.experimental.dependencies.ScriptDependencies
abstract class ScriptDependenciesLoader(protected val project: Project) { abstract class ScriptDependenciesLoader(protected val project: Project) {
fun updateDependencies(file: VirtualFile) {
if (fileModificationStamps[file.path] != file.modificationStamp) {
fileModificationStamps.put(file.path, file.modificationStamp)
loadDependencies(file)
}
}
private val fileModificationStamps: SLRUMap<String, Long> = SLRUMap(MAX_SCRIPTS_CACHED, MAX_SCRIPTS_CACHED)
abstract fun isApplicable(file: VirtualFile): Boolean abstract fun isApplicable(file: VirtualFile): Boolean
abstract fun loadDependencies(file: VirtualFile)
protected abstract fun loadDependencies(file: VirtualFile)
protected abstract fun shouldShowNotification(): Boolean protected abstract fun shouldShowNotification(): Boolean
protected var shouldNotifyRootsChanged = false protected var shouldNotifyRootsChanged = false