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:
+48
-44
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -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() {
|
||||||
|
|||||||
+1
-13
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user