KTIJ-29329 Failed to save settings: "Cannot get KotlinCompilationErrorFileTimeStamps component state"

GitOrigin-RevId: af1eafe4088ff6b033c00b55999b5e940b85de15
This commit is contained in:
Vladimir Krivosheev
2024-04-09 15:37:36 +00:00
committed by intellij-monorepo-bot
parent 812fe3f28a
commit a710fe7b9d
4 changed files with 75 additions and 59 deletions
@@ -1,5 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="5" platform="JVM 17" allPlatforms="JVM [17]" useProjectSettings="false">
<compilerSettings>
<option name="additionalArguments" value="-Xjvm-default=all" />
</compilerSettings>
<compilerArguments>
<stringArguments>
<stringArg name="jvmTarget" arg="17" />
<stringArg name="apiVersion" arg="1.9" />
<stringArg name="languageVersion" arg="1.9" />
</stringArguments>
<arrayArguments>
<arrayArg name="pluginClasspaths">
<args>$KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar</args>
</arrayArg>
<arrayArg name="pluginOptions" />
</arrayArguments>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
@@ -19,5 +41,6 @@
<orderEntry type="module" module-name="intellij.java.impl" />
<orderEntry type="module" module-name="intellij.gradle.common" />
<orderEntry type="module" module-name="kotlin.base.compiler-configuration" />
<orderEntry type="library" name="kotlinx-serialization-core" level="project" />
</component>
</module>
@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.statistics
import com.intellij.internal.statistic.eventLog.EventLogGroup
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.idea.compiler.configuration.KotlinIdePlugin
import org.jetbrains.kotlin.idea.configuration.BuildSystemType
import org.jetbrains.kotlin.idea.configuration.buildSystemType
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.utils.addToStdlib.UnsafeCastFunction
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import kotlin.math.absoluteValue
import kotlin.random.Random
@@ -299,6 +300,7 @@ class KotlinOnboardingJ2KSessionService(private val project: Project, private va
/**
* Caches if the project contains a Kotlin file.
*/
@OptIn(UnsafeCastFunction::class)
internal suspend fun hasKotlinFiles(): Boolean {
hasKotlinFile?.let { return it }
return readAction {
@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.statistics
import com.intellij.internal.statistic.libraryUsage.LibraryUsageImportProcessor
@@ -8,9 +8,11 @@ import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtImportDirective
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
import org.jetbrains.kotlin.utils.addToStdlib.UnsafeCastFunction
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class KotlinLibraryUsageImportProcessor : LibraryUsageImportProcessor<KtImportDirective> {
@OptIn(UnsafeCastFunction::class)
override fun imports(file: PsiFile): List<KtImportDirective> = file.safeAs<KtFile>()?.importDirectives.orEmpty()
override fun isSingleElementImport(import: KtImportDirective): Boolean = !import.isAllUnder
@@ -1,19 +1,13 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.statistics.compilationError
import com.intellij.openapi.components.*
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.SimpleModificationTracker
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.io.DigestUtil
import com.intellij.util.io.hashToHexString
import com.intellij.util.xmlb.annotations.Tag
import com.intellij.util.xmlb.annotations.XMap
import org.jetbrains.annotations.ApiStatus
import kotlinx.serialization.Serializable
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
@Service(Service.Level.PROJECT)
@State(
@@ -22,75 +16,70 @@ import kotlin.concurrent.write
reportStatistic = false,
reloadable = false,
)
@ApiStatus.Internal
class KotlinCompilationErrorProcessedFilesTimeStampRecorder :
PersistentStateComponentWithModificationTracker<KotlinCompilationErrorProcessedFilesTimeStampRecorder.MyState> {
internal class KotlinCompilationErrorProcessedFilesTimeStampRecorder :
SerializablePersistentStateComponent<KotlinCompilationErrorProcessedFilesTimeStampRecorder.MyState>(EMPTY_STATE) {
override fun initializeComponent() = lock.write {
if (dropOutdatedTimestamps()) {
tracker.incModificationCount()
override fun initializeComponent() {
val currentTime = System.currentTimeMillis()
if (state.timestamps.values.any { currentTime.isHourPassedSince(it) }) {
updateState { state ->
val timestamps = HashMap(state.timestamps)
dropOutdatedTimestamps(currentTime = currentTime, timestamps = timestamps)
if (timestamps.isEmpty()) EMPTY_STATE else MyState(timestamps = timestamps)
}
}
}
private val lock = ReentrantReadWriteLock()
private var state = MyState()
private val tracker = SimpleModificationTracker()
override fun getState(): MyState = lock.read { MyState(HashMap(state.timestamps)) }
override fun getStateModificationCount(): Long = tracker.modificationCount
override fun loadState(state: MyState) = lock.write {
this.state = state
}
@Serializable
class MyState(
@XMap
@JvmField
val timestamps: MutableMap<Key, Long> = HashMap()
) : BaseState() {
data class Key(
// Use md5 instead of the actual file path to make local storage smaller. Users' paths can be huge
@Tag("filePathMd5")
val filePathMd5: String,
@Tag("compilationErrorId")
val compilationErrorId: String
)
}
@JvmField val timestamps: Map<String, Long> = emptyMap()
)
fun keepOnlyIfHourPassedAndRecordTimestamps(vFile: VirtualFile, compilationErrorIds: List<String>): List<String> {
if (compilationErrorIds.isEmpty()) return emptyList()
val hash = pathMd5Hash(vFile)
lock.write {
val result = mutableListOf<String>()
updateState { state ->
val timestamps = HashMap(state.timestamps)
val currentTime = System.currentTimeMillis()
val result = compilationErrorIds.asSequence()
result.clear()
compilationErrorIds.asSequence()
.distinct() /* `isHourPassed` + `recordTimestamp` don't allow the compilation error to happen for the
second time in this pipeline, so `distinct` is just a small optimization */
.filter { isHourPassed(hash, currentTime, it) }
.onEach { recordTimestamp(hash, currentTime, it) }
.toList()
if (dropOutdatedTimestamps(currentTime) || result.isNotEmpty()) {
tracker.incModificationCount()
}
return result
.filter { isHourPassed(md5Hash = hash, currentTime = currentTime, compilationErrorId = it, timestamps = timestamps) }
.onEach { recordTimestamp(md5Hash = hash, currentTime = currentTime, compilationErrorId = it, timestamps = timestamps) }
.toCollection(result)
dropOutdatedTimestamps(currentTime, timestamps)
if (timestamps.isEmpty()) EMPTY_STATE else MyState(timestamps = timestamps)
}
return result
}
private fun isHourPassed(md5Hash: String, currentTime: Long, compilationErrorId: String): Boolean {
val fileTime = state.timestamps[MyState.Key(md5Hash, compilationErrorId)] ?: return true
return currentTime.isHourPassedSince(fileTime)
}
private fun recordTimestamp(md5Hash: String, currentTime: Long, compilationErrorId: String) {
state.timestamps[MyState.Key(md5Hash, compilationErrorId)] = currentTime
}
private fun dropOutdatedTimestamps(currentTime: Long = System.currentTimeMillis()): Boolean =
state.timestamps.values.removeIf { currentTime.isHourPassedSince(it) }
companion object {
fun getInstance(project: Project): KotlinCompilationErrorProcessedFilesTimeStampRecorder = project.service()
}
}
private val EMPTY_STATE = KotlinCompilationErrorProcessedFilesTimeStampRecorder.MyState()
private fun isHourPassed(md5Hash: String, currentTime: Long, compilationErrorId: String, timestamps: Map<String, Long>): Boolean {
val fileTime = timestamps[createCompositeKey(md5Hash, compilationErrorId)] ?: return true
return currentTime.isHourPassedSince(fileTime)
}
// Use md5 instead of the actual file path to make local storage smaller.
// Users' paths can be huge.
// Key - combination of filePathMd5 + compilationErrorId, to avoid non-primitive key.
private fun createCompositeKey(md5Hash: String, compilationErrorId: String) = "$md5Hash-$compilationErrorId"
private fun recordTimestamp(md5Hash: String, currentTime: Long, compilationErrorId: String, timestamps: MutableMap<String, Long>) {
timestamps[createCompositeKey(md5Hash, compilationErrorId)] = currentTime
}
private fun dropOutdatedTimestamps(currentTime: Long, timestamps: MutableMap<String, Long>): Boolean {
return timestamps.values.removeIf { currentTime.isHourPassedSince(it) }
}
private fun pathMd5Hash(virtualFile: VirtualFile): String = hashToHexString(virtualFile.path, DigestUtil.md5())
private fun Long.isHourPassedSince(lastTime: Long): Boolean = TimeUnit.MILLISECONDS.toHours(this - lastTime) >= 1