[Analysis API FIR] fix invalid usages of CachedValue

it should not cache map as values are stored on soft references
This commit is contained in:
Ilya Kirillov
2022-09-30 15:51:10 +02:00
committed by teamcity
parent 457069090b
commit fceb3425c8
6 changed files with 116 additions and 58 deletions
@@ -7,21 +7,18 @@ package org.jetbrains.kotlin.analysis.api.impl.base
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootModificationTracker
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
import org.jetbrains.kotlin.analysis.providers.createProjectWideOutOfBlockModificationTracker
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.session.KtAnalysisSessionProvider
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.getKtModule
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.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.ConcurrentHashMap
import kotlin.reflect.KClass
@KtAnalysisApiInternals
@@ -47,10 +44,6 @@ abstract class CachingKtAnalysisSessionProvider<State : Any>(project: Project) :
}
}
private fun getCachedAnalysisSession(firResolveSession: State, token: KtLifetimeToken): KtAnalysisSession? {
return cache.getCachedAnalysisSession(firResolveSession to token::class)
}
@TestOnly
final override fun clearCaches() {
cache.clear()
@@ -58,23 +51,24 @@ abstract class CachingKtAnalysisSessionProvider<State : Any>(project: Project) :
}
private class KtAnalysisSessionCache(project: Project) {
private val cache = CachedValuesManager.getManager(project).createCachedValue {
CachedValueProvider.Result(
ConcurrentHashMap<Pair<KtModule, KClass<out KtLifetimeToken>>, KtAnalysisSession>(),
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()
)
}
)
@TestOnly
fun clear() {
cache.value.clear()
cache.clear()
}
inline fun getAnalysisSession(key: Pair<KtModule, KClass<out KtLifetimeToken>>, create: () -> KtAnalysisSession): KtAnalysisSession =
cache.value.getOrPut(key) { create() }
fun getCachedAnalysisSession(key: KEY): KtAnalysisSession? =
cache.value[key]
fun getAnalysisSession(
key: Pair<KtModule, KClass<out KtLifetimeToken>>,
create: () -> KtAnalysisSession
): KtAnalysisSession =
cache.getOrPut(key) { create() }
}
@@ -0,0 +1,75 @@
/*
* Copyright 2010-2022 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.utils.caches
import com.intellij.openapi.project.Project
import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.annotations.TestOnly
import java.util.concurrent.ConcurrentHashMap
public abstract class SoftCachedMap<K : Any, V : Any> {
public abstract fun getOrPut(key: K, create: () -> V): V
@TestOnly
public abstract fun clear()
public companion object {
public fun <K : Any, V : Any> create(
project: Project,
kind: Kind,
trackers: List<Any>
): SoftCachedMap<K, V> = when {
trackers.isEmpty() -> SoftCachedMapWithoutTrackers(kind)
else -> SoftCachedMapWithTrackers(project, kind, trackers.toTypedArray())
}
}
public enum class Kind {
SOFT_KEYS_SOFT_VALUES,
STRONG_KEYS_SOFT_VALUES
}
}
private class SoftCachedMapWithTrackers<K : Any, V : Any>(
private val project: Project,
kind: Kind,
private val trackers: Array<Any>
) : SoftCachedMap<K, V>() {
private val cache = when (kind) {
Kind.SOFT_KEYS_SOFT_VALUES -> ContainerUtil.createConcurrentSoftMap<K, CachedValue<V>>()
Kind.STRONG_KEYS_SOFT_VALUES -> ConcurrentHashMap<K, CachedValue<V>>()
}
override fun clear() {
cache.clear()
}
override fun getOrPut(key: K, create: () -> V): V {
return cache.getOrPut(key) {
CachedValuesManager.getManager(project).createCachedValue {
CachedValueProvider.Result(create(), *trackers)
}
}.value
}
}
private class SoftCachedMapWithoutTrackers<K : Any, V : Any>(kind: Kind) : SoftCachedMap<K, V>() {
private val cache = when (kind) {
Kind.SOFT_KEYS_SOFT_VALUES -> ContainerUtil.createConcurrentSoftKeySoftValueMap<K, V>()
Kind.STRONG_KEYS_SOFT_VALUES -> ContainerUtil.createSoftValueMap<K, V>()
}
override fun clear() {
cache.clear()
}
override fun getOrPut(key: K, create: () -> V): V {
return cache.getOrPut(key, create)
}
}
@@ -41,13 +41,14 @@ internal class LLFirResolveSessionDepended(
override val useSiteKtModule: KtModule get() = originalFirResolveSession.useSiteKtModule
override val useSiteFirSession get() = originalFirResolveSession.useSiteFirSession
private val scopeSessionProviderCache by softCachedValue(
private val scopeSessionProviderCache = SoftCachedMap.create<FirSession, LLFirScopeSessionProvider>(
project,
PsiModificationTracker.MODIFICATION_COUNT,
ProjectRootModificationTracker.getInstance(project),
) {
ConcurrentHashMap<FirSession, LLFirScopeSessionProvider>()
}
SoftCachedMap.Kind.SOFT_KEYS_SOFT_VALUES,
listOf(
PsiModificationTracker.MODIFICATION_COUNT,
ProjectRootModificationTracker.getInstance(project)
)
)
override fun getScopeSessionFor(firSession: FirSession): ScopeSession {
return scopeSessionProviderCache
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootModificationTracker
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSession
@@ -19,33 +20,23 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.state.LLFirSourceResolveS
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.errorWithFirSpecificEntries
import org.jetbrains.kotlin.analysis.project.structure.*
import org.jetbrains.kotlin.analysis.providers.createProjectWideOutOfBlockModificationTracker
import org.jetbrains.kotlin.analysis.utils.caches.getValue
import org.jetbrains.kotlin.analysis.utils.caches.softCachedValue
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.withLock
import org.jetbrains.kotlin.analysis.utils.caches.SoftCachedMap
internal class LLFirResolveSessionService(project: Project) {
private val sessionProviderStorage = LLFirSessionProviderStorage(project)
private val stateCache by softCachedValue(
private val cache = SoftCachedMap.create<KtModule, LLFirResolvableResolveSession>(
project,
project.createProjectWideOutOfBlockModificationTracker(),
ProjectRootModificationTracker.getInstance(project),
) {
mutableMapOf<KtModule, LLFirResolvableResolveSession>()
}
private val cacheLock = ReentrantReadWriteLock()
SoftCachedMap.Kind.STRONG_KEYS_SOFT_VALUES,
listOf(
ProjectRootModificationTracker.getInstance(project),
project.createProjectWideOutOfBlockModificationTracker(),
)
)
fun getFirResolveSession(module: KtModule): LLFirResolvableResolveSession {
cacheLock.readLock().withLock {
stateCache[module]?.let { return it }
}
cacheLock.writeLock().withLock {
stateCache[module]?.let { return it }
val session = createFirResolveSessionFor(module, sessionProviderStorage)
stateCache[module] = session
return session
return cache.getOrPut(module) {
createFirResolveSessionFor(module, sessionProviderStorage)
}
}
@@ -9,6 +9,7 @@ import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getFirResolveSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.resolveToFirSymbol
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSourcesSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.getContainingFile
import org.jetbrains.kotlin.analysis.providers.KotlinAnnotationsResolver
import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProvider
@@ -40,7 +41,7 @@ import org.jetbrains.kotlin.psi.*
* PSI index based implementation of [FirPredicateBasedProvider].
*/
internal class LLFirIdePredicateBasedProvider(
private val session: FirSession,
private val session: LLFirSourcesSession,
private val annotationsResolver: KotlinAnnotationsResolver,
private val declarationProvider: KotlinDeclarationProvider,
) : FirPredicateBasedProvider() {
@@ -6,10 +6,7 @@
package org.jetbrains.kotlin.analysis.low.level.api.fir.util
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootModificationTracker
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.analysis.utils.caches.getValue
import org.jetbrains.kotlin.analysis.utils.caches.softCachedValue
import org.jetbrains.kotlin.analysis.utils.caches.SoftCachedMap
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import java.util.concurrent.ConcurrentHashMap
@@ -28,12 +25,11 @@ abstract class LLFirScopeSessionProvider {
private class LLFirInvalidatableScopeSessionProvider(project: Project, invalidationTrackers: List<Any>) : LLFirScopeSessionProvider() {
// ScopeSession is thread-local, so we use Thread id as a key
// We cannot use thread locals here as it may lead to memory leaks
private val cache by softCachedValue(
private val cache = SoftCachedMap.create<Long, ScopeSession>(
project,
*invalidationTrackers.toTypedArray(),
) {
ConcurrentHashMap<Long, ScopeSession>()
}
SoftCachedMap.Kind.STRONG_KEYS_SOFT_VALUES,
invalidationTrackers
)
override fun getScopeSession(): ScopeSession {
return cache.getOrPut(Thread.currentThread().id) { ScopeSession() }