ScriptConfigurationManager: rebuild cache synchronously from default loader

This commit is contained in:
Sergey Rostov
2020-05-01 15:47:37 +03:00
parent ab51292da8
commit 751d167f66
5 changed files with 73 additions and 33 deletions
@@ -6,6 +6,8 @@
package org.jetbrains.kotlin.idea.core.script.configuration package org.jetbrains.kotlin.idea.core.script.configuration
import com.intellij.ProjectTopics import com.intellij.ProjectTopics
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModuleRootEvent import com.intellij.openapi.roots.ModuleRootEvent
@@ -111,13 +113,16 @@ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigur
* Loads script configuration if classpath roots don't contain [file] yet * Loads script configuration if classpath roots don't contain [file] yet
*/ */
private fun getActualClasspathRoots(file: VirtualFile): ScriptClassRootsCache { private fun getActualClasspathRoots(file: VirtualFile): ScriptClassRootsCache {
val classpathRoots = classpathRoots try {
if (classpathRoots.contains(file)) { // we should run default loader if this [file] is not cached in [classpathRoots]
return classpathRoots // and it is not supported by any of [plugins]
// getOrLoadConfiguration will do this
// (despite that it's result becomes unused, it still may populate [classpathRoots])
getOrLoadConfiguration(file, null)
} catch (cancelled: ProcessCanceledException) {
// read actions may be cancelled if we are called by impatient reader
} }
getOrLoadConfiguration(file)
return this.classpathRoots return this.classpathRoots
} }
@@ -223,7 +223,7 @@ class DefaultScriptingSupport(manager: CompositeScriptConfigurationManager) : De
file.removeScriptDependenciesNotificationPanel(project) file.removeScriptDependenciesNotificationPanel(project)
} }
saveReports(file, newResult.reports) saveReports(file, newResult.reports)
setAppliedConfiguration(file, newResult) setAppliedConfiguration(file, newResult, syncUpdate = true)
} else { } else {
debug(file) { debug(file) {
"configuration changed, notification is shown: old = $oldConfiguration, new = $newConfiguration" "configuration changed, notification is shown: old = $oldConfiguration, new = $newConfiguration"
@@ -377,15 +377,21 @@ abstract class DefaultScriptingSupportBase(val manager: CompositeScriptConfigura
protected open fun setAppliedConfiguration( protected open fun setAppliedConfiguration(
file: VirtualFile, file: VirtualFile,
newConfigurationSnapshot: ScriptConfigurationSnapshot? newConfigurationSnapshot: ScriptConfigurationSnapshot?,
syncUpdate: Boolean = false
) { ) {
manager.updater.checkInTransaction() manager.updater.checkInTransaction()
val newConfiguration = newConfigurationSnapshot?.configuration val newConfiguration = newConfigurationSnapshot?.configuration
debug(file) { "configuration changed = $newConfiguration" } debug(file) { "configuration changed = $newConfiguration" }
if (newConfiguration != null) { if (newConfiguration != null) {
manager.updater.invalidate(file)
cache.setApplied(file, newConfigurationSnapshot) cache.setApplied(file, newConfigurationSnapshot)
if (syncUpdate) {
manager.updater.updateSynchronously()
} else {
manager.updater.invalidate(file)
}
} }
} }
@@ -15,6 +15,7 @@ import com.intellij.util.containers.HashSetQueue
import org.jetbrains.kotlin.idea.core.script.debug import org.jetbrains.kotlin.idea.core.script.debug
import org.jetbrains.kotlin.idea.core.util.KotlinIdeaCoreBundle import org.jetbrains.kotlin.idea.core.util.KotlinIdeaCoreBundle
import java.util.* import java.util.*
import javax.swing.SwingUtilities
/** /**
* Sequentially loads script configuration in background. * Sequentially loads script configuration in background.
@@ -227,9 +228,12 @@ internal class DefaultBackgroundExecutor(
override fun start() { override fun start() {
super.start() super.start()
BackgroundTaskUtil.executeOnPooledThread(project, Runnable { // executeOnPooledThread requires read lock, and we may fail to acquire it
run() SwingUtilities.invokeLater {
}) BackgroundTaskUtil.executeOnPooledThread(project, {
run()
})
}
} }
override fun close() { override fun close() {
@@ -22,6 +22,8 @@ import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupportHelpe
import org.jetbrains.kotlin.idea.core.script.debug import org.jetbrains.kotlin.idea.core.script.debug
import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.application.runWriteAction
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
/** /**
* Utility for postponing indexing of new roots to the end of some bulk operation. * Utility for postponing indexing of new roots to the end of some bulk operation.
@@ -78,6 +80,7 @@ class ScriptClassRootsUpdater(
ensureUpdateScheduled() ensureUpdateScheduled()
} }
private val syncLock = ReentrantLock()
private var scheduledUpdate: ProgressIndicator? = null private var scheduledUpdate: ProgressIndicator? = null
@Synchronized @Synchronized
@@ -88,31 +91,48 @@ class ScriptClassRootsUpdater(
} }
} }
fun doUpdate() { /**
val updates = manager.collectRootsAndCheckNew() * Used from legacy FS cache only, don't use
*/
if (!updates.changed) return @Synchronized
fun updateSynchronously() {
ProgressManager.checkCanceled() syncLock.withLock {
scheduledUpdate?.cancel()
if (updates.hasNewRoots) { doUpdate(false)
notifyRootsChanged()
} }
}
PsiElementFinder.EP.findExtensionOrFail(KotlinScriptDependenciesClassFinder::class.java, project) fun doUpdate(underProgressManager: Boolean = true) {
.clearCache() syncLock.withLock {
val updates = manager.collectRootsAndCheckNew()
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount() if (!updates.changed) return
if (updates.updatedScripts.isNotEmpty()) { try {
ScriptingSupportHelper.updateHighlighting(project) { ProgressManager.checkCanceled()
it.path in updates.updatedScripts
if (updates.hasNewRoots) {
notifyRootsChanged()
}
PsiElementFinder.EP.findExtensionOrFail(KotlinScriptDependenciesClassFinder::class.java, project)
.clearCache()
ScriptDependenciesModificationTracker.getInstance(project).incModificationCount()
if (updates.updatedScripts.isNotEmpty()) {
ScriptingSupportHelper.updateHighlighting(project) {
it.path in updates.updatedScripts
}
}
} catch (cancel: ProcessCanceledException) {
if (underProgressManager) throw cancel
} finally {
synchronized(this) {
scheduledUpdate = null
}
} }
} }
synchronized(this) {
scheduledUpdate = null
}
} }
private fun notifyRootsChanged() { private fun notifyRootsChanged() {
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.idea.scripting.gradle package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.components.service import com.intellij.openapi.components.service
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationInputs import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationInputs
@@ -25,10 +26,14 @@ data class GradleKotlinScriptConfigurationInputs(
val inputsTS: Long val inputsTS: Long
) : CachedConfigurationInputs { ) : CachedConfigurationInputs {
override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean { override fun isUpToDate(project: Project, file: VirtualFile, ktFile: KtFile?): Boolean {
val actualStamp = getGradleScriptInputsStamp(project, file, ktFile) ?: return false try {
val actualStamp = getGradleScriptInputsStamp(project, file, ktFile) ?: return false
if (actualStamp.sections != this.sections) return false if (actualStamp.sections != this.sections) return false
return project.service<GradleScriptInputsWatcher>().areRelatedFilesUpToDate(file, inputsTS) return project.service<GradleScriptInputsWatcher>().areRelatedFilesUpToDate(file, inputsTS)
} catch (cancel: ProcessCanceledException) {
return false
}
} }
} }