Don't compute cached values under lock - this can cause a deadlock
Three threads needed to reproduce: - Thread 1. Doesn't hold RW lock, starts computation and locks the cached lock (L) - Thread 2. Holds R lock and want to get same computation result. It's now locked on (L) - Thread 3. Any thread that want to get W lock. It's now locked on because of Thread 2 If Thread 1. wants to get R lock it will be stacked because of Thread 3. It's sometimes not possible to get a read lock while some thread is already waiting on W.
This commit is contained in:
@@ -43,6 +43,7 @@ import org.jetbrains.kotlin.psi.KtFile
|
|||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
class KotlinDebuggerCaches(private val project: Project) {
|
class KotlinDebuggerCaches(private val project: Project) {
|
||||||
|
|
||||||
@@ -54,14 +55,16 @@ class KotlinDebuggerCaches(private val project: Project) {
|
|||||||
|
|
||||||
private val cachedClassNames = CachedValuesManager.getManager(project).createCachedValue(
|
private val cachedClassNames = CachedValuesManager.getManager(project).createCachedValue(
|
||||||
{
|
{
|
||||||
CachedValueProvider.Result<HashMap<PsiElement, List<String>>>(
|
CachedValueProvider.Result<MutableMap<PsiElement, List<String>>>(
|
||||||
hashMapOf(), PsiModificationTracker.MODIFICATION_COUNT)
|
ConcurrentHashMap<PsiElement, List<String>>(),
|
||||||
|
PsiModificationTracker.MODIFICATION_COUNT)
|
||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
private val cachedTypeMappers = CachedValuesManager.getManager(project).createCachedValue(
|
private val cachedTypeMappers = CachedValuesManager.getManager(project).createCachedValue(
|
||||||
{
|
{
|
||||||
CachedValueProvider.Result<HashMap<PsiElement, KotlinTypeMapper>>(
|
CachedValueProvider.Result<MutableMap<PsiElement, KotlinTypeMapper>>(
|
||||||
hashMapOf(), PsiModificationTracker.MODIFICATION_COUNT)
|
ConcurrentHashMap<PsiElement, KotlinTypeMapper>(),
|
||||||
|
PsiModificationTracker.MODIFICATION_COUNT)
|
||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -77,67 +80,75 @@ class KotlinDebuggerCaches(private val project: Project) {
|
|||||||
): CompiledDataDescriptor {
|
): CompiledDataDescriptor {
|
||||||
val evaluateExpressionCache = getInstance(codeFragment.project)
|
val evaluateExpressionCache = getInstance(codeFragment.project)
|
||||||
|
|
||||||
return synchronized<CompiledDataDescriptor>(evaluateExpressionCache.cachedCompiledData) {
|
val text = "${codeFragment.importsToString()}\n${codeFragment.text}"
|
||||||
|
|
||||||
|
val cached = synchronized<Collection<CompiledDataDescriptor>>(evaluateExpressionCache.cachedCompiledData) {
|
||||||
val cache = evaluateExpressionCache.cachedCompiledData.value!!
|
val cache = evaluateExpressionCache.cachedCompiledData.value!!
|
||||||
val text = "${codeFragment.importsToString()}\n${codeFragment.text}"
|
|
||||||
|
|
||||||
val answer = cache[text].firstOrNull {
|
cache[text]
|
||||||
it.sourcePosition == sourcePosition || evaluateExpressionCache.canBeEvaluatedInThisContext(it, evaluationContext)
|
|
||||||
}
|
|
||||||
if (answer != null) return@synchronized answer
|
|
||||||
|
|
||||||
val newCompiledData = create(codeFragment, sourcePosition)
|
|
||||||
LOG.debug("Compile bytecode for ${codeFragment.text}")
|
|
||||||
|
|
||||||
cache.putValue(text, newCompiledData)
|
|
||||||
return@synchronized newCompiledData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val answer = cached.firstOrNull {
|
||||||
|
it.sourcePosition == sourcePosition || evaluateExpressionCache.canBeEvaluatedInThisContext(it, evaluationContext)
|
||||||
|
}
|
||||||
|
if (answer != null) {
|
||||||
|
return answer
|
||||||
|
}
|
||||||
|
|
||||||
|
val newCompiledData = create(codeFragment, sourcePosition)
|
||||||
|
LOG.debug("Compile bytecode for ${codeFragment.text}")
|
||||||
|
|
||||||
|
synchronized(evaluateExpressionCache.cachedCompiledData) {
|
||||||
|
evaluateExpressionCache.cachedCompiledData.value.putValue(text, newCompiledData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newCompiledData
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T: PsiElement> getOrComputeClassNames(psiElement: T, create: (T) -> ComputedClassNames): List<String> {
|
fun <T: PsiElement> getOrComputeClassNames(psiElement: T, create: (T) -> ComputedClassNames): List<String> {
|
||||||
val cache = getInstance(runReadAction { psiElement.project })
|
val cache = getInstance(runReadAction { psiElement.project })
|
||||||
synchronized(cache.cachedClassNames) {
|
|
||||||
val classNamesCache = cache.cachedClassNames.value
|
|
||||||
|
|
||||||
val cachedValue = classNamesCache[psiElement]
|
val classNamesCache = cache.cachedClassNames.value
|
||||||
if (cachedValue != null) return cachedValue
|
|
||||||
|
|
||||||
val computedClassNames = create(psiElement)
|
val cachedValue = classNamesCache[psiElement]
|
||||||
|
if (cachedValue != null) return cachedValue
|
||||||
|
|
||||||
if (computedClassNames.shouldBeCached) {
|
val computedClassNames = create(psiElement)
|
||||||
classNamesCache[psiElement] = computedClassNames.classNames
|
|
||||||
}
|
if (computedClassNames.shouldBeCached) {
|
||||||
return computedClassNames.classNames
|
classNamesCache[psiElement] = computedClassNames.classNames
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return computedClassNames.classNames
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOrCreateTypeMapper(psiElement: PsiElement): KotlinTypeMapper {
|
fun getOrCreateTypeMapper(psiElement: PsiElement): KotlinTypeMapper {
|
||||||
val cache = getInstance(runReadAction { psiElement.project })
|
val cache = getInstance(runReadAction { psiElement.project })
|
||||||
synchronized(cache.cachedTypeMappers) {
|
|
||||||
val typeMappersCache = cache.cachedTypeMappers.value
|
|
||||||
|
|
||||||
val file = runReadAction { psiElement.containingFile as KtFile }
|
val file = runReadAction { psiElement.containingFile as KtFile }
|
||||||
val isInLibrary = LibraryUtil.findLibraryEntry(file.virtualFile, file.project) != null
|
val isInLibrary = LibraryUtil.findLibraryEntry(file.virtualFile, file.project) != null
|
||||||
|
|
||||||
if (!isInLibrary) {
|
val key = if (!isInLibrary) file else psiElement
|
||||||
// Key = file
|
|
||||||
val cachedValue = typeMappersCache[file]
|
|
||||||
if (cachedValue != null) return cachedValue
|
|
||||||
|
|
||||||
val newValue = createTypeMapperForSourceFile(file)
|
val typeMappersCache = cache.cachedTypeMappers.value
|
||||||
typeMappersCache[file] = newValue
|
|
||||||
return newValue
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// key = KtElement
|
|
||||||
val element = getElementToCreateTypeMapperForLibraryFile(psiElement)
|
|
||||||
val cachedValue = typeMappersCache[psiElement]
|
|
||||||
if (cachedValue != null) return cachedValue
|
|
||||||
|
|
||||||
val newValue = createTypeMapperForLibraryFile(element, file)
|
val cachedValue = typeMappersCache[key]
|
||||||
typeMappersCache[psiElement] = newValue
|
if (cachedValue != null) return cachedValue
|
||||||
return newValue
|
|
||||||
}
|
if (!isInLibrary) {
|
||||||
|
val newValue = createTypeMapperForSourceFile(file)
|
||||||
|
|
||||||
|
typeMappersCache[file] = newValue
|
||||||
|
|
||||||
|
return newValue
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val element = getElementToCreateTypeMapperForLibraryFile(psiElement)
|
||||||
|
val newValue = createTypeMapperForLibraryFile(element, file)
|
||||||
|
|
||||||
|
typeMappersCache[psiElement] = newValue
|
||||||
|
|
||||||
|
return newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user