Split script configuration state into applied and loaded to make markUpToDate more clear

This commit is contained in:
Sergey Rostov
2019-11-04 13:35:12 +03:00
committed by Natalia Selezneva
parent fde1d3fdf7
commit fbb2b260dd
5 changed files with 65 additions and 47 deletions
@@ -20,9 +20,9 @@ import kotlinx.coroutines.launch
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.idea.core.script.* import org.jetbrains.kotlin.idea.core.script.*
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.toVfsRoots import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.toVfsRoots
import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationInputs
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationCache import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationCache
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationSnapshot import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationSnapshot
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationState
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptConfigurationUpdater
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsCache
import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsIndexer import org.jetbrains.kotlin.idea.core.script.configuration.utils.ScriptClassRootsIndexer
@@ -40,7 +40,7 @@ import kotlin.concurrent.withLock
* Among this two methods concrete implementation should provide script changes listening (by calling [updater] on some event). * Among this two methods concrete implementation should provide script changes listening (by calling [updater] on some event).
* *
* Basically all requests routed to [cache]. If there is no entry in [cache] or it is considered out-of-date, * Basically all requests routed to [cache]. If there is no entry in [cache] or it is considered out-of-date,
* then [reloadOutOfDateConfiguration] will be called, which, in turn, should call [saveChangedConfiguration] * then [reloadOutOfDateConfiguration] will be called, which, in turn, should call [setAppliedConfiguration]
* immediately or in some future (e.g. after user will click "apply context" or/and configuration will * immediately or in some future (e.g. after user will click "apply context" or/and configuration will
* be calculated by some background thread). * be calculated by some background thread).
* *
@@ -61,7 +61,7 @@ internal abstract class AbstractScriptConfigurationManager(
/** /**
* Will be called on [cache] miss or when [file] is changed. * Will be called on [cache] miss or when [file] is changed.
* Implementation should initiate loading of [file]'s script configuration and call [saveChangedConfiguration] * Implementation should initiate loading of [file]'s script configuration and call [setAppliedConfiguration]
* immediately or in some future * immediately or in some future
* (e.g. after user will click "apply context" or/and configuration will be calculated by some background thread). * (e.g. after user will click "apply context" or/and configuration will be calculated by some background thread).
* *
@@ -73,7 +73,7 @@ internal abstract class AbstractScriptConfigurationManager(
*/ */
protected abstract fun reloadOutOfDateConfiguration( protected abstract fun reloadOutOfDateConfiguration(
file: KtFile, file: KtFile,
isFirstLoad: Boolean = getCachedConfiguration(file.originalFile.virtualFile) == null, isFirstLoad: Boolean = getAppliedConfiguration(file.originalFile.virtualFile) == null,
loadEvenWillNotBeApplied: Boolean = false, loadEvenWillNotBeApplied: Boolean = false,
forceSync: Boolean = false forceSync: Boolean = false
) )
@@ -87,13 +87,16 @@ internal abstract class AbstractScriptConfigurationManager(
override fun getScriptClasspath(file: KtFile): List<VirtualFile> = override fun getScriptClasspath(file: KtFile): List<VirtualFile> =
toVfsRoots(getConfiguration(file)?.dependenciesClassPath.orEmpty()) toVfsRoots(getConfiguration(file)?.dependenciesClassPath.orEmpty())
fun getCachedConfiguration(file: VirtualFile?): ScriptConfigurationSnapshot? { fun getCachedConfigurationState(file: VirtualFile?): ScriptConfigurationState? {
if (file == null) return null if (file == null) return null
return cache[file] return cache[file]
} }
fun getAppliedConfiguration(file: VirtualFile?): ScriptConfigurationSnapshot? =
getCachedConfigurationState(file)?.applied
override fun hasConfiguration(file: KtFile): Boolean { override fun hasConfiguration(file: KtFile): Boolean {
return getCachedConfiguration(file.originalFile.virtualFile) != null return getAppliedConfiguration(file.originalFile.virtualFile) != null
} }
override fun getConfiguration(file: KtFile): ScriptCompilationConfigurationWrapper? { override fun getConfiguration(file: KtFile): ScriptCompilationConfigurationWrapper? {
@@ -104,7 +107,7 @@ internal abstract class AbstractScriptConfigurationManager(
virtualFile: VirtualFile, virtualFile: VirtualFile,
preloadedKtFile: KtFile? = null preloadedKtFile: KtFile? = null
): ScriptCompilationConfigurationWrapper? { ): ScriptCompilationConfigurationWrapper? {
val cached = getCachedConfiguration(virtualFile) val cached = getAppliedConfiguration(virtualFile)
if (cached != null) return cached.configuration if (cached != null) return cached.configuration
val ktFile = project.getKtFile(virtualFile, preloadedKtFile) ?: return null val ktFile = project.getKtFile(virtualFile, preloadedKtFile) ?: return null
@@ -112,7 +115,7 @@ internal abstract class AbstractScriptConfigurationManager(
reloadOutOfDateConfiguration(ktFile, isFirstLoad = true) reloadOutOfDateConfiguration(ktFile, isFirstLoad = true)
} }
return getCachedConfiguration(virtualFile)?.configuration return getAppliedConfiguration(virtualFile)?.configuration
} }
override val updater: ScriptConfigurationUpdater = object : ScriptConfigurationUpdater { override val updater: ScriptConfigurationUpdater = object : ScriptConfigurationUpdater {
@@ -143,7 +146,7 @@ internal abstract class AbstractScriptConfigurationManager(
val virtualFile = file.originalFile.virtualFile val virtualFile = file.originalFile.virtualFile
if (virtualFile != null) { if (virtualFile != null) {
val state = cache[virtualFile] val state = cache[virtualFile]
if (state == null || !state.inputs.isUpToDate(project, virtualFile, file)) { if (state == null || !state.isUpToDate(project, virtualFile, file)) {
upToDate = false upToDate = false
reloadOutOfDateConfiguration( reloadOutOfDateConfiguration(
file, file,
@@ -165,14 +168,14 @@ internal abstract class AbstractScriptConfigurationManager(
file as? KtFile ?: error("PsiFile $file should be a KtFile, otherwise script dependencies cannot be loaded") file as? KtFile ?: error("PsiFile $file should be a KtFile, otherwise script dependencies cannot be loaded")
val virtualFile = file.virtualFile val virtualFile = file.virtualFile
if (cache[virtualFile]?.inputs?.isUpToDate(project, virtualFile, file) == true) return if (cache[virtualFile]?.isUpToDate(project, virtualFile, file) == true) return
rootsIndexer.transaction { rootsIndexer.transaction {
reloadOutOfDateConfiguration(file, isFirstLoad = true, forceSync = true) reloadOutOfDateConfiguration(file, isFirstLoad = true, forceSync = true)
} }
} }
protected open fun saveChangedConfiguration( protected open fun setAppliedConfiguration(
file: VirtualFile, file: VirtualFile,
newConfigurationSnapshot: ScriptConfigurationSnapshot? newConfigurationSnapshot: ScriptConfigurationSnapshot?
) { ) {
@@ -185,7 +188,7 @@ internal abstract class AbstractScriptConfigurationManager(
rootsIndexer.markNewRoot(file, newConfiguration) rootsIndexer.markNewRoot(file, newConfiguration)
} }
cache[file] = newConfigurationSnapshot cache.setApplied(file, newConfigurationSnapshot)
clearClassRootsCaches() clearClassRootsCaches()
} }
@@ -193,11 +196,11 @@ internal abstract class AbstractScriptConfigurationManager(
updateHighlighting(listOf(file)) updateHighlighting(listOf(file))
} }
protected fun markUpToDate( protected fun setLoadedConfiguration(
file: VirtualFile, file: VirtualFile,
inputs: CachedConfigurationInputs configurationSnapshot: ScriptConfigurationSnapshot
) { ) {
cache.markUpToDate(file, inputs) cache.setLoaded(file, configurationSnapshot)
} }
private fun hasNotCachedRoots(configuration: ScriptCompilationConfigurationWrapper): Boolean { private fun hasNotCachedRoots(configuration: ScriptCompilationConfigurationWrapper): Boolean {
@@ -141,7 +141,7 @@ internal class DefaultScriptConfigurationManager(project: Project) :
* and file will be up-to-date checked again. This will happen after current loading, * and file will be up-to-date checked again. This will happen after current loading,
* because only `backgroundExecutor` execute tasks in one thread. * because only `backgroundExecutor` execute tasks in one thread.
* - `invalid, waiting for apply`: * - `invalid, waiting for apply`:
* Loading will not be queued, since we are marking file is up to date with * Loading will not be queued, since we are marking file as up-to-date with
* not yet applied configuration. * not yet applied configuration.
* *
* Sync: * Sync:
@@ -177,8 +177,8 @@ internal class DefaultScriptConfigurationManager(project: Project) :
backgroundExecutor.ensureScheduled(virtualFile) { backgroundExecutor.ensureScheduled(virtualFile) {
// don't start loading if nothing was changed // don't start loading if nothing was changed
// (in case we checking for up-to-date and loading concurrently) // (in case we checking for up-to-date and loading concurrently)
val cached = getCachedConfiguration(virtualFile) val cached = getCachedConfigurationState(virtualFile)
if (cached == null || !cached.inputs.isUpToDate(project, virtualFile)) { if (cached == null || !cached.isUpToDate(project, virtualFile)) {
val actualIsFirstLoad = cached == null val actualIsFirstLoad = cached == null
async.first { it.loadDependencies(actualIsFirstLoad, virtualFile, scriptDefinition, loadingContext) } async.first { it.loadDependencies(actualIsFirstLoad, virtualFile, scriptDefinition, loadingContext) }
} }
@@ -202,7 +202,7 @@ internal class DefaultScriptConfigurationManager(project: Project) :
private val loadingContext = object : ScriptConfigurationLoadingContext { private val loadingContext = object : ScriptConfigurationLoadingContext {
override fun getCachedConfiguration(file: VirtualFile): ScriptConfigurationSnapshot? = override fun getCachedConfiguration(file: VirtualFile): ScriptConfigurationSnapshot? =
this@DefaultScriptConfigurationManager.getCachedConfiguration(file) this@DefaultScriptConfigurationManager.getAppliedConfiguration(file)
override fun suggestNewConfiguration(file: VirtualFile, newResult: ScriptConfigurationSnapshot) { override fun suggestNewConfiguration(file: VirtualFile, newResult: ScriptConfigurationSnapshot) {
suggestOrSaveConfiguration(file, newResult, false) suggestOrSaveConfiguration(file, newResult, false)
@@ -221,13 +221,13 @@ internal class DefaultScriptConfigurationManager(project: Project) :
saveLock.withLock { saveLock.withLock {
debug(file) { "configuration received = $newResult" } debug(file) { "configuration received = $newResult" }
markUpToDate(file, newResult.inputs) setLoadedConfiguration(file, newResult)
val newConfiguration = newResult.configuration val newConfiguration = newResult.configuration
if (newConfiguration == null) { if (newConfiguration == null) {
saveReports(file, newResult.reports) saveReports(file, newResult.reports)
} else { } else {
val oldConfiguration = getCachedConfiguration(file)?.configuration val oldConfiguration = getAppliedConfiguration(file)?.configuration
if (oldConfiguration == newConfiguration) { if (oldConfiguration == newConfiguration) {
saveReports(file, newResult.reports) saveReports(file, newResult.reports)
file.removeScriptDependenciesNotificationPanel(project) file.removeScriptDependenciesNotificationPanel(project)
@@ -242,7 +242,7 @@ internal class DefaultScriptConfigurationManager(project: Project) :
file.removeScriptDependenciesNotificationPanel(project) file.removeScriptDependenciesNotificationPanel(project)
} }
saveReports(file, newResult.reports) saveReports(file, newResult.reports)
saveChangedConfiguration(file, newResult) setAppliedConfiguration(file, newResult)
} else { } else {
debug(file) { debug(file) {
"configuration changed, notification is shown: old = $oldConfiguration, new = $newConfiguration" "configuration changed, notification is shown: old = $oldConfiguration, new = $newConfiguration"
@@ -253,7 +253,7 @@ internal class DefaultScriptConfigurationManager(project: Project) :
saveReports(file, newResult.reports) saveReports(file, newResult.reports)
file.removeScriptDependenciesNotificationPanel(project) file.removeScriptDependenciesNotificationPanel(project)
rootsIndexer.transaction { rootsIndexer.transaction {
saveChangedConfiguration(file, newResult) setAppliedConfiguration(file, newResult)
} }
} }
) )
@@ -30,6 +30,14 @@ data class ScriptConfigurationSnapshot(
val configuration: ScriptCompilationConfigurationWrapper? val configuration: ScriptCompilationConfigurationWrapper?
) )
data class ScriptConfigurationState(
val applied: ScriptConfigurationSnapshot? = null,
val loaded: ScriptConfigurationSnapshot? = null
) {
fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile? = null): Boolean =
(loaded ?: applied)?.inputs?.isUpToDate(project, file, ktFile) ?: false
}
/** /**
* Cached configurations for file's specific snapshot state. * Cached configurations for file's specific snapshot state.
* *
@@ -37,11 +45,11 @@ data class ScriptConfigurationSnapshot(
* Reader may do up-to-date check for existed entry. * Reader may do up-to-date check for existed entry.
*/ */
interface ScriptConfigurationCache { interface ScriptConfigurationCache {
operator fun get(file: VirtualFile): ScriptConfigurationSnapshot? operator fun get(file: VirtualFile): ScriptConfigurationState?
operator fun set(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot)
fun markUpToDate(file: VirtualFile, inputs: CachedConfigurationInputs) fun setApplied(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot)
fun markOutOfDate(file: VirtualFile) = markUpToDate(file, CachedConfigurationInputs.OutOfDate) fun setLoaded(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot)
fun markOutOfDate(file: VirtualFile)
fun all(): Collection<Pair<VirtualFile, ScriptCompilationConfigurationWrapper>> fun allApplied(): Collection<Pair<VirtualFile, ScriptCompilationConfigurationWrapper>>
} }
@@ -17,34 +17,41 @@ class ScriptConfigurationMemoryCache(
const val MAX_SCRIPTS_CACHED = 50 const val MAX_SCRIPTS_CACHED = 50
} }
private val memoryCache = SLRUMap<VirtualFile, ScriptConfigurationSnapshot>(MAX_SCRIPTS_CACHED, MAX_SCRIPTS_CACHED) private val memoryCache = SLRUMap<VirtualFile, ScriptConfigurationState>(MAX_SCRIPTS_CACHED, MAX_SCRIPTS_CACHED)
@Synchronized @Synchronized
override operator fun get(file: VirtualFile): ScriptConfigurationSnapshot? { override operator fun get(file: VirtualFile): ScriptConfigurationState? {
return memoryCache.get(file) return memoryCache.get(file)
} }
@Synchronized @Synchronized
override operator fun set(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot) { override fun setApplied(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot) {
memoryCache.put( val old = memoryCache[file] ?: ScriptConfigurationState()
file, memoryCache.put(file, old.copy(applied = configurationSnapshot))
configurationSnapshot
)
} }
override fun markUpToDate(file: VirtualFile, inputs: CachedConfigurationInputs) { @Synchronized
val old = memoryCache[file] override fun setLoaded(file: VirtualFile, configurationSnapshot: ScriptConfigurationSnapshot) {
if (old != null) { val old = memoryCache[file] ?: ScriptConfigurationState()
memoryCache.put(file, old.copy(inputs = inputs)) memoryCache.put(file, old.copy(loaded = configurationSnapshot))
} }
@Synchronized
override fun markOutOfDate(file: VirtualFile) {
val old = memoryCache[file] ?: ScriptConfigurationState()
memoryCache.put(
file, old.copy(
applied = old.applied?.copy(inputs = CachedConfigurationInputs.OutOfDate),
loaded = old.loaded?.copy(inputs = CachedConfigurationInputs.OutOfDate)
)
)
} }
@Synchronized @Synchronized
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun all() = override fun allApplied() =
memoryCache.entrySet().map { memoryCache.entrySet().mapNotNull {
if (it.value.configuration == null) null if (it.value.applied?.configuration == null) null
else it.key to it.value.configuration else it.key to it.value.applied?.configuration
} as Collection<Pair<VirtualFile, ScriptCompilationConfigurationWrapper>> } as Collection<Pair<VirtualFile, ScriptCompilationConfigurationWrapper>>
} }
@@ -25,8 +25,8 @@ internal class ScriptClassRootsCache(
private val project: Project, private val project: Project,
private val cache: ScriptConfigurationCache private val cache: ScriptConfigurationCache
) { ) {
private val all: Collection<Pair<VirtualFile, ScriptCompilationConfigurationWrapper>> get() = cache.all() private val all: Collection<Pair<VirtualFile, ScriptCompilationConfigurationWrapper>> get() = cache.allApplied()
private fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? = cache[file]?.configuration private fun getConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? = cache[file]?.applied?.configuration
private fun getScriptSdk(compilationConfiguration: ScriptCompilationConfigurationWrapper?): Sdk? { private fun getScriptSdk(compilationConfiguration: ScriptCompilationConfigurationWrapper?): Sdk? {
// workaround for mismatched gradle wrapper and plugin version // workaround for mismatched gradle wrapper and plugin version