KTIJ-24983 [FIR] Ensure safe publication of configured plugin factories in FirExtensionRegistrar

Previous approach prevented double registration of the plugins, but
might have caused a race condition: atomic flag was set before the
plugins initialization, not after, so another thread could see garbage
data even if it could read the flag set to "true".

This might or might not be a root cause for the KTIJ-24983 exception,
so we'll have to wait for some time before actually closing the issue.
This commit is contained in:
Roman Golyshev
2023-05-31 16:06:10 +02:00
committed by Space Team
parent 3f22044275
commit b11447ea6b
@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.fir.SessionConfiguration
import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension
import org.jetbrains.kotlin.fir.builder.FirScriptConfiguratorExtension
import org.jetbrains.kotlin.fir.resolve.FirSamConversionTransformerExtension
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.reflect.KClass
abstract class FirExtensionRegistrar : FirExtensionRegistrarAdapter() {
@@ -184,25 +183,35 @@ abstract class FirExtensionRegistrar : FirExtensionRegistrarAdapter() {
@OptIn(PluginServicesInitialization::class)
fun configure(): BunchOfRegisteredExtensions {
if (isInitialized.compareAndSet(false, true)) {
// Extension registrars can survive FirSession recreation in IDE mode, but we don't want to
// call `configurePlugin` more than once, because it will lead to registering all plugins twice.
// Please see KT-51444 for the details.
return BunchOfRegisteredExtensions(configuredExtensionFactories)
}
ExtensionRegistrarContext().configurePlugin()
private val extensionFactories: Map<KClass<out FirExtension>, MutableList<FirExtension.Factory<FirExtension>>> =
AVAILABLE_EXTENSIONS.associateWith {
mutableListOf()
}
return BunchOfRegisteredExtensions(map)
}
/**
* A lazy property which returns the [extensionFactories] map, but calls
* [configurePlugin] to make sure that it's correctly configured.
*
* Extension registrars can survive FirSession recreation in IDE mode, but we don't want to
* call [configurePlugin] more than once, because it will lead to registering all plugins twice.
* That's why we don't want to call [configurePlugin] directly from the [configure].
*
* Instead, we use [lazy] to ensure that initialization happens only once, and that the
* resulting [extensionFactories] map is visible to all possible callers, so no races occur.
*/
private val configuredExtensionFactories: Map<KClass<out FirExtension>, List<FirExtension.Factory<FirExtension>>> by lazy(
LazyThreadSafetyMode.SYNCHRONIZED
) {
ExtensionRegistrarContext().configurePlugin()
private val map: Map<KClass<out FirExtension>, MutableList<FirExtension.Factory<FirExtension>>> = AVAILABLE_EXTENSIONS.associateWith {
mutableListOf()
extensionFactories
}
private var isInitialized: AtomicBoolean = AtomicBoolean(false)
private fun <P : FirExtension> registerExtension(kClass: KClass<out P>, factory: FirExtension.Factory<P>) {
val registeredExtensions = map.getValue(kClass)
val registeredExtensions = extensionFactories.getValue(kClass)
registeredExtensions += factory
}
}