gradle.kts: improve service initializtion

- prevent analyzing .gradle.kts files until all services are loaded
- remove services caches where it is not required
- replace cached services with cache only during vfs events batch processing
- prevent services loading in actions updating
This commit is contained in:
Sergey Rostov
2020-07-09 21:35:38 +03:00
parent a97d718de8
commit 1f52c0e0d4
13 changed files with 57 additions and 19 deletions
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.idea.core.script package org.jetbrains.kotlin.idea.core.script
import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.components.serviceIfCreated
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.util.Key import com.intellij.openapi.util.Key
@@ -108,6 +109,9 @@ interface ScriptConfigurationManager {
fun getAllScriptDependenciesSources(): List<VirtualFile> fun getAllScriptDependenciesSources(): List<VirtualFile>
companion object { companion object {
fun getServiceIfCreated(project: Project): ScriptConfigurationManager? =
ServiceManager.getServiceIfCreated(project, ScriptConfigurationManager::class.java)
@JvmStatic @JvmStatic
fun getInstance(project: Project): ScriptConfigurationManager = fun getInstance(project: Project): ScriptConfigurationManager =
ServiceManager.getService(project, ScriptConfigurationManager::class.java) ServiceManager.getService(project, ScriptConfigurationManager::class.java)
@@ -84,7 +84,9 @@ class ScriptDefinitionsManager(private val project: Project) : LazyScriptDefinit
private val scriptDefinitionsCacheLock = ReentrantLock() private val scriptDefinitionsCacheLock = ReentrantLock()
private val scriptDefinitionsCache = SLRUMap<String, ScriptDefinition>(10, 10) private val scriptDefinitionsCache = SLRUMap<String, ScriptDefinition>(10, 10)
val configurations = (ScriptConfigurationManager.getInstance(project) as CompositeScriptConfigurationManager) // cache service as it's getter is on the hot path
// it is safe, since both services are in same plugin
val configurations = ScriptConfigurationManager.getInstance(project) as CompositeScriptConfigurationManager
override fun findDefinition(script: SourceCode): ScriptDefinition? { override fun findDefinition(script: SourceCode): ScriptDefinition? {
val locationId = script.locationId ?: return null val locationId = script.locationId ?: return null
@@ -27,10 +27,16 @@ class ScriptTrafficLightRendererContributor : TrafficLightRendererContributor {
TrafficLightRenderer(project, document, file) { TrafficLightRenderer(project, document, file) {
override fun getDaemonCodeAnalyzerStatus(severityRegistrar: SeverityRegistrar): DaemonCodeAnalyzerStatus { override fun getDaemonCodeAnalyzerStatus(severityRegistrar: SeverityRegistrar): DaemonCodeAnalyzerStatus {
val status = super.getDaemonCodeAnalyzerStatus(severityRegistrar) val status = super.getDaemonCodeAnalyzerStatus(severityRegistrar)
if (!ScriptDefinitionsManager.getInstance(file.project).isReady()) {
val configurations = ScriptConfigurationManager.getServiceIfCreated(project)
if (configurations == null) {
// services not yet initialized (it should be initialized under the LoadScriptDefinitionsStartupActivity)
status.reasonWhySuspended = KotlinIdeaCoreBundle.message("text.loading.kotlin.script.configuration")
status.errorAnalyzingFinished = false
} else if (!ScriptDefinitionsManager.getInstance(file.project).isReady()) {
status.reasonWhySuspended = KotlinIdeaCoreBundle.message("text.loading.kotlin.script.definitions") status.reasonWhySuspended = KotlinIdeaCoreBundle.message("text.loading.kotlin.script.definitions")
status.errorAnalyzingFinished = false status.errorAnalyzingFinished = false
} else if (ScriptConfigurationManager.getInstance(project).isConfigurationLoadingInProgress(file)) { } else if (configurations.isConfigurationLoadingInProgress(file)) {
status.reasonWhySuspended = KotlinIdeaCoreBundle.message("text.loading.kotlin.script.configuration") status.reasonWhySuspended = KotlinIdeaCoreBundle.message("text.loading.kotlin.script.configuration")
status.errorAnalyzingFinished = false status.errorAnalyzingFinished = false
} }
@@ -49,7 +49,8 @@ class CompositeScriptConfigurationManager(val project: Project) : ScriptConfigur
private val classpathRoots: ScriptClassRootsCache private val classpathRoots: ScriptClassRootsCache
get() = updater.classpathRoots get() = updater.classpathRoots
private val plugins = ScriptingSupport.EPN.getPoint(project).extensionList private val plugins
get() = ScriptingSupport.EPN.getPoint(project).extensionList
val default = DefaultScriptingSupport(this) val default = DefaultScriptingSupport(this)
@@ -83,10 +83,11 @@ internal class ScriptChangesNotifier(
} }
private val defaultListener = DefaultScriptChangeListener(project) private val defaultListener = DefaultScriptChangeListener(project)
private val listeners: Collection<ScriptChangeListener> = mutableListOf<ScriptChangeListener>().apply { private val listeners: Collection<ScriptChangeListener>
addAll(LISTENER.getPoint(project).extensionList) get() = mutableListOf<ScriptChangeListener>().apply {
add(defaultListener) addAll(LISTENER.getPoint(project).extensionList)
} add(defaultListener)
}
private fun getListener(project: Project, file: VirtualFile): ScriptChangeListener? { private fun getListener(project: Project, file: VirtualFile): ScriptChangeListener? {
if (project.isDisposed || areListenersDisabled()) return null if (project.isDisposed || areListenersDisabled()) return null
@@ -33,9 +33,10 @@ fun addVfsListener(
} }
override fun apply() { override fun apply() {
val fileChangesProcessor = watcher.fileChangesProcessor
changedFiles.forEach { changedFiles.forEach {
LocalFileSystem.getInstance().findFileByPath(it)?.let { f -> LocalFileSystem.getInstance().findFileByPath(it)?.let { f ->
watcher.fileChanged(f.path, f.timeStamp) fileChangesProcessor(f.path, f.timeStamp)
} }
} }
} }
@@ -17,19 +17,22 @@ fun addVfsListener(
) { ) {
VirtualFileManager.getInstance().addAsyncFileListener( VirtualFileManager.getInstance().addAsyncFileListener(
object : AsyncFileChangeListenerBase() { object : AsyncFileChangeListenerBase() {
var fileChangesProcessor = watcher.fileChangesProcessor
override fun isRelevant(path: String): Boolean { override fun isRelevant(path: String): Boolean {
return buildRootsManager.maybeAffectedGradleProjectFile(path) return buildRootsManager.maybeAffectedGradleProjectFile(path)
} }
override fun updateFile(file: VirtualFile, event: VFileEvent) { override fun updateFile(file: VirtualFile, event: VFileEvent) {
watcher.fileChanged(event.path, file.timeStamp) fileChangesProcessor(event.path, file.timeStamp)
} }
// do nothing // do nothing
override fun prepareFileDeletion(file: VirtualFile) {} override fun prepareFileDeletion(file: VirtualFile) {}
override fun apply() {} override fun apply() {}
override fun reset() {} override fun reset() {
fileChangesProcessor = watcher.fileChangesProcessor
}
}, },
watcher.project watcher.project
) )
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModelResolver import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModelResolver
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRoot import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRoot
import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinitionProvider
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.plugins.gradle.service.project.GradlePartialResolverPolicy import org.jetbrains.plugins.gradle.service.project.GradlePartialResolverPolicy
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings import org.jetbrains.plugins.gradle.settings.GradleProjectSettings
@@ -98,10 +99,14 @@ class LoadConfigurationAction : AnAction(
private fun getNotificationVisibility(editor: Editor): Boolean { private fun getNotificationVisibility(editor: Editor): Boolean {
if (!scriptConfigurationsNeedToBeUpdatedBalloon) return false if (!scriptConfigurationsNeedToBeUpdatedBalloon) return false
if (DiffUtil.isDiffEditor(editor)) return false if (DiffUtil.isDiffEditor(editor)) return false
val project = editor.project ?: return false val project = editor.project ?: return false
// prevent services initializtion
// (all services actually initialized under the ScriptDefinitionProvider during startup activity)
if (ScriptDefinitionProvider.getServiceIfCreated(project) == null) return false
val file = getKotlinScriptFile(editor) ?: return false val file = getKotlinScriptFile(editor) ?: return false
if (autoReloadScriptConfigurations(project, file)) { if (autoReloadScriptConfigurations(project, file)) {
@@ -14,15 +14,26 @@ import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
class GradleScriptListener(project: Project) : ScriptChangeListener(project) { class GradleScriptListener(project: Project) : ScriptChangeListener(project) {
// todo(gradle6): remove // todo(gradle6): remove
private val legacy = GradleLegacyScriptListener(project) private val legacy = GradleLegacyScriptListener(project)
private val buildRootsManager = GradleBuildRootsManager.getInstance(project)
private val buildRootsManager
get() = GradleBuildRootsManager.getInstance(project)
init { init {
// listen changes using VFS events, including gradle-configuration related files // listen changes using VFS events, including gradle-configuration related files
addVfsListener(this, buildRootsManager) addVfsListener(this, buildRootsManager)
} }
// cache buildRootsManager service for hot path under vfs changes listener
val fileChangesProcessor: (filePath: String, ts: Long) -> Unit
get() {
val buildRootsManager = buildRootsManager
return { filePath, ts ->
buildRootsManager.fileChanged(filePath, ts)
}
}
fun fileChanged(filePath: String, ts: Long) = fun fileChanged(filePath: String, ts: Long) =
buildRootsManager.fileChanged(filePath, ts) fileChangesProcessor(filePath, ts)
override fun isApplicable(vFile: VirtualFile) = override fun isApplicable(vFile: VirtualFile) =
// todo(gradle6): replace with `isCustomScriptingSupport(vFile)` // todo(gradle6): replace with `isCustomScriptingSupport(vFile)`
@@ -27,7 +27,8 @@ import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
* TODO(gradle6): remove * TODO(gradle6): remove
*/ */
class GradleLegacyScriptConfigurationLoader(project: Project) : DefaultScriptConfigurationLoader(project) { class GradleLegacyScriptConfigurationLoader(project: Project) : DefaultScriptConfigurationLoader(project) {
private val buildRootsManager = GradleBuildRootsManager.getInstance(project) private val buildRootsManager
get() = GradleBuildRootsManager.getInstance(project)
override fun interceptBackgroundLoading(file: VirtualFile, isFirstLoad: Boolean, doLoad: () -> Unit): Boolean { override fun interceptBackgroundLoading(file: VirtualFile, isFirstLoad: Boolean, doLoad: () -> Unit): Boolean {
if (!isGradleKotlinScript(file)) return false if (!isGradleKotlinScript(file)) return false
@@ -14,7 +14,8 @@ import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager
// called from GradleScriptListener // called from GradleScriptListener
// todo(gradle6): remove // todo(gradle6): remove
class GradleLegacyScriptListener(project: Project) : ScriptChangeListener(project) { class GradleLegacyScriptListener(project: Project) : ScriptChangeListener(project) {
private val buildRootsManager = GradleBuildRootsManager.getInstance(project) private val buildRootsManager
get() = GradleBuildRootsManager.getInstance(project)
override fun isApplicable(vFile: VirtualFile) = override fun isApplicable(vFile: VirtualFile) =
isGradleKotlinScript(vFile) isGradleKotlinScript(vFile)
@@ -21,7 +21,6 @@ import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager
import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptingSupport
import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport
import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport.Companion.EPN
import org.jetbrains.kotlin.idea.core.script.ucache.ScriptClassRootsBuilder import org.jetbrains.kotlin.idea.core.script.ucache.ScriptClassRootsBuilder
import org.jetbrains.kotlin.idea.core.util.EDT import org.jetbrains.kotlin.idea.core.util.EDT
import org.jetbrains.kotlin.idea.scripting.gradle.* import org.jetbrains.kotlin.idea.scripting.gradle.*
@@ -456,6 +455,6 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(),
companion object { companion object {
fun getInstance(project: Project): GradleBuildRootsManager = fun getInstance(project: Project): GradleBuildRootsManager =
EPN.getPoint(project).extensionList.firstIsInstance() ScriptingSupport.EPN.findExtensionOrFail(GradleBuildRootsManager::class.java, project)
} }
} }
@@ -26,6 +26,9 @@ interface ScriptDefinitionProvider {
companion object { companion object {
fun getInstance(project: Project): ScriptDefinitionProvider? = fun getInstance(project: Project): ScriptDefinitionProvider? =
ServiceManager.getService(project, ScriptDefinitionProvider::class.java) ServiceManager.getService(project, ScriptDefinitionProvider::class.java)
fun getServiceIfCreated(project: Project): ScriptDefinitionProvider? =
ServiceManager.getServiceIfCreated(project, ScriptDefinitionProvider::class.java)
} }
} }