From d16a1b14d888e4768c35a53885eb94d1697d94e6 Mon Sep 17 00:00:00 2001 From: Vladimir Dolzhenko Date: Thu, 24 Oct 2019 13:14:04 +0200 Subject: [PATCH] Acquire lock for several seconds to create LightClasses to workaround dead-lock over resolve Relates to #KT-34279 --- .../classes/KotlinClassInnerStuffCache.kt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KotlinClassInnerStuffCache.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KotlinClassInnerStuffCache.kt index c379f4e70b9..a8619b70c34 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KotlinClassInnerStuffCache.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KotlinClassInnerStuffCache.kt @@ -21,13 +21,15 @@ import com.intellij.psi.util.CachedValueProvider import com.intellij.util.ArrayUtil import com.intellij.util.containers.ContainerUtil import gnu.trove.THashMap +import java.util.concurrent.TimeUnit +import java.util.concurrent.locks.ReentrantLock class KotlinClassInnerStuffCache(val myClass: PsiExtensibleClass, externalDependencies: List) { private val myTracker = SimpleModificationTracker() private val dependencies: List = externalDependencies + myTracker fun get(initializer: () -> T) = object : Lazy { - // Note: holder is used as initialization monitor as well + private val lock = ReentrantLock() private val holder = lazyPub { PsiCachedValueImpl(PsiManager.getInstance(myClass.project), CachedValueProvider { @@ -49,20 +51,29 @@ class KotlinClassInnerStuffCache(val myClass: PsiExtensibleClass, externalDepend // Assumption 1: Lets say A calculation requires another value e.g. B to be calculated // Assumption 2: Thread T2 wants to calculate value for B - // to avoid dead-lock case we mark thread as doing calculation and acquire lock only once per thread + // to avoid dead-lock + // - we mark thread as doing calculation and acquire lock only once per thread // as a trade-off to prevent dependent value could be calculated several time // due to CAS (within putUserDataIfAbsent etc) the same instance of calculated value will be used - if (!initIsRunning.get()) { - synchronized(holder) { + // TODO: NOTE: acquire lock for a several seconds to avoid dead-lock via resolve is a WORKAROUND + + val notRunning = !initIsRunning.get() + if (notRunning && lock.tryLock(500, TimeUnit.MILLISECONDS)) { + try { initIsRunning.set(true) try { computeValue() } finally { initIsRunning.set(false) } + } finally { + lock.unlock() } } else { + if (notRunning) { // means unable to acquire lock within specified time limit + Logger.getInstance(KotlinClassInnerStuffCache::class.java).error("failed to acquire lock for $initializer") + } computeValue() } }