[LL API] Clean invalidated sessions in 'KtFirAnalysisSessionProvider'

This commit is contained in:
Yan Zhulanow
2023-02-20 20:13:21 +09:00
committed by Space Team
parent c33bedb417
commit 1584fe77ba
3 changed files with 61 additions and 39 deletions
@@ -8,64 +8,65 @@ package org.jetbrains.kotlin.analysis.api.fir
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootModificationTracker
import com.intellij.openapi.util.LowMemoryWatcher
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.annotations.TestOnly
import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.util.CachedValueBase
import com.intellij.util.containers.CollectionFactory
import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeToken
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenFactory
import org.jetbrains.kotlin.analysis.api.session.KtAnalysisSessionProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getFirResolveSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.FirSessionValidityStamp
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.getKtModule
import org.jetbrains.kotlin.analysis.providers.createProjectWideOutOfBlockModificationTracker
import org.jetbrains.kotlin.analysis.utils.caches.SoftCachedMap
import org.jetbrains.kotlin.psi.KtElement
import java.util.concurrent.ConcurrentMap
import kotlin.reflect.KClass
@OptIn(KtAnalysisApiInternals::class)
class KtFirAnalysisSessionProvider(project: Project) : KtAnalysisSessionProvider(project) {
private val cache = KtAnalysisSessionCache(project)
private val cache: ConcurrentMap<Pair<KtModule, KClass<out KtLifetimeToken>>, CachedValue<KtAnalysisSession>> =
CollectionFactory.createConcurrentWeakValueMap()
init {
LowMemoryWatcher.register(::clearCaches, project)
}
override fun getAnalysisSession(useSiteKtElement: KtElement, factory: KtLifetimeTokenFactory): KtAnalysisSession {
return getAnalysisSessionByUseSiteKtModule(useSiteKtElement.getKtModule(project), factory)
}
override fun getAnalysisSessionByUseSiteKtModule(useSiteKtModule: KtModule, factory: KtLifetimeTokenFactory): KtAnalysisSession {
return cache.getAnalysisSession(useSiteKtModule to factory.identifier) {
val firResolveSession = useSiteKtModule.getFirResolveSession(project)
val validityToken = factory.create(project)
KtFirAnalysisSession.createAnalysisSessionByFirResolveSession(firResolveSession, validityToken)
val key = Pair(useSiteKtModule, factory.identifier)
return cache.computeIfAbsent(key) {
CachedValuesManager.getManager(project).createCachedValue {
val firResolveSession = useSiteKtModule.getFirResolveSession(project)
val validityToken = factory.create(project)
val validityStamp = FirSessionValidityStamp(firResolveSession.useSiteFirSession)
require(validityStamp.isValid) { "Got an invalid session for module $useSiteKtModule" }
CachedValueProvider.Result(
KtFirAnalysisSession.createAnalysisSessionByFirResolveSession(firResolveSession, validityToken),
validityStamp,
ProjectRootModificationTracker.getInstance(project),
project.createProjectWideOutOfBlockModificationTracker()
)
}
}.value
}
override fun clearCaches() {
for (cachedValue in cache.values) {
check(cachedValue is CachedValueBase<*>) {
"Unsupported 'CachedValue' of type ${cachedValue.javaClass}'"
}
cachedValue.clear()
}
}
@TestOnly
override fun clearCaches() {
cache.clear()
}
}
private class KtAnalysisSessionCache(project: Project) {
private val cache = SoftCachedMap.create<Pair<KtModule, KClass<out KtLifetimeToken>>, KtAnalysisSession>(
project,
SoftCachedMap.Kind.STRONG_KEYS_SOFT_VALUES,
listOf(
PsiModificationTracker.MODIFICATION_COUNT,
ProjectRootModificationTracker.getInstance(project),
project.createProjectWideOutOfBlockModificationTracker()
)
)
init {
LowMemoryWatcher.register({ cache.clearCachedValues() }, project)
}
@TestOnly
fun clear() {
cache.clear()
}
fun getAnalysisSession(key: Pair<KtModule, KClass<out KtLifetimeToken>>, create: () -> KtAnalysisSession): KtAnalysisSession {
return cache.getOrPut(key) { create() }
}
}
@@ -27,7 +27,7 @@ import org.jetbrains.kotlin.psi.KtFile
abstract class LLFirResolveSession {
abstract val project: Project
abstract val useSiteFirSession: FirSession
abstract val useSiteFirSession: LLFirSession
abstract val useSiteKtModule: KtModule
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.low.level.api.fir.sessions
import com.intellij.openapi.util.ModificationTracker
import java.lang.ref.WeakReference
class FirSessionValidityStamp(session: LLFirSession) : ModificationTracker {
private val sessionRef = WeakReference(session)
val isValid: Boolean
get() = modificationCount == 0L
override fun getModificationCount(): Long {
val session = sessionRef.get() ?: return 1
return if (session.isValid) 0 else 2
}
}