fix "IDEA-221944 Deadlock on opening second project" and support preloading for proje...
[idea/community.git] / platform / service-container / src / com / intellij / serviceContainer / PlatformComponentManagerImpl.kt
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.serviceContainer
3
4 import com.intellij.diagnostic.*
5 import com.intellij.ide.plugins.*
6 import com.intellij.ide.plugins.cl.PluginClassLoader
7 import com.intellij.openapi.Disposable
8 import com.intellij.openapi.application.Application
9 import com.intellij.openapi.application.ApplicationManager
10 import com.intellij.openapi.components.*
11 import com.intellij.openapi.components.impl.ComponentManagerImpl
12 import com.intellij.openapi.components.impl.stores.IComponentStore
13 import com.intellij.openapi.diagnostic.ControlFlowException
14 import com.intellij.openapi.diagnostic.logger
15 import com.intellij.openapi.extensions.*
16 import com.intellij.openapi.extensions.impl.ExtensionsAreaImpl
17 import com.intellij.openapi.progress.ProcessCanceledException
18 import com.intellij.openapi.progress.ProgressIndicator
19 import com.intellij.openapi.progress.ProgressIndicatorProvider
20 import com.intellij.openapi.progress.ProgressManager
21 import com.intellij.openapi.util.Disposer
22 import com.intellij.util.IncorrectOperationException
23 import com.intellij.util.SmartList
24 import com.intellij.util.concurrency.AppExecutorUtil
25 import com.intellij.util.containers.ContainerUtil
26 import com.intellij.util.io.storage.HeavyProcessLatch
27 import com.intellij.util.messages.*
28 import com.intellij.util.messages.impl.MessageBusImpl
29 import com.intellij.util.pico.DefaultPicoContainer
30 import org.jetbrains.annotations.ApiStatus
31 import org.jetbrains.annotations.ApiStatus.Internal
32 import org.jetbrains.annotations.TestOnly
33 import java.lang.reflect.Constructor
34 import java.lang.reflect.InvocationTargetException
35 import java.lang.reflect.Modifier
36 import java.util.*
37 import java.util.concurrent.CompletableFuture
38 import java.util.concurrent.ConcurrentMap
39
40 abstract class PlatformComponentManagerImpl @JvmOverloads constructor(internal val parent: ComponentManager?, setExtensionsRootArea: Boolean = parent == null) : ComponentManagerImpl(parent), LazyListenerCreator {
41   companion object {
42     private val LOG = logger<PlatformComponentManagerImpl>()
43
44     private val constructorParameterResolver = PlatformConstructorParameterResolver(isExtensionSupported = false)
45     private val heavyConstructorParameterResolver = PlatformConstructorParameterResolver(isExtensionSupported = true)
46
47     private class PlatformConstructorParameterResolver(private val isExtensionSupported: Boolean) : ConstructorParameterResolver() {
48       override fun isResolvable(componentManager: PlatformComponentManagerImpl, requestorKey: Any, expectedType: Class<*>): Boolean {
49         if (isLightService(expectedType) || super.isResolvable(componentManager, requestorKey, expectedType)) {
50           return true
51         }
52         return isExtensionSupported && componentManager.extensionArea.findExtensionByClass(expectedType) != null
53       }
54
55       override fun resolveInstance(componentManager: PlatformComponentManagerImpl, requestorKey: Any, expectedType: Class<*>): Any? {
56         if (isLightService(expectedType)) {
57           return componentManager.getLightService(componentManager.lightServices!!, expectedType, true)
58         }
59
60         val result = super.resolveInstance(componentManager, requestorKey, expectedType)
61         if (result == null && isExtensionSupported) {
62           val extension = componentManager.extensionArea.findExtensionByClass(expectedType)
63           if (extension != null) {
64             LOG.warn("Do not use constructor injection to get extension instance (requestorKey=$requestorKey, extensionClass=${expectedType.name})")
65           }
66           return extension
67         }
68         return result
69       }
70     }
71
72     @JvmStatic
73     protected val fakeCorePluginDescriptor = object : PluginDescriptor {
74       override fun getPluginClassLoader() = null
75
76       override fun getPluginId() = PluginId.getId(PluginManagerCore.CORE_PLUGIN_ID)
77     }
78   }
79
80   @Suppress("LeakingThis")
81   private val extensionArea = ExtensionsAreaImpl(this)
82
83   private var handlingInitComponentError = false
84
85   var componentCreated = false
86     private set
87
88   private val lightServices: ConcurrentMap<Class<*>, Any>? = when {
89     parent == null || parent.picoContainer.parent == null -> ContainerUtil.newConcurrentMap()
90     else -> null
91   }
92
93   protected open val componentStore: IComponentStore
94     get() = getService(IComponentStore::class.java)
95
96   init {
97     if (setExtensionsRootArea) {
98       Extensions.setRootArea(extensionArea)
99     }
100   }
101
102   final override fun getExtensionArea(): ExtensionsAreaImpl = extensionArea
103
104   @Internal
105   open fun registerComponents(plugins: List<IdeaPluginDescriptor>) {
106     @Suppress("UNCHECKED_CAST")
107     plugins as List<IdeaPluginDescriptorImpl>
108     val activityNamePrefix = activityNamePrefix()
109     val parallelActivity = if (activityNamePrefix == null) null else ParallelActivity.PREPARE_APP_INIT
110
111     val app = getApplication()
112     val headless = app == null || app.isHeadlessEnvironment
113     var componentConfigCount = 0
114     var map: ConcurrentMap<String, MutableList<ListenerDescriptor>>? = null
115     val isHeadlessMode = app?.isHeadlessEnvironment == true
116     val isUnitTestMode = app?.isUnitTestMode == true
117
118     parallelActivity.run("${activityNamePrefix}service and ep registration") {
119       // register services before registering extensions because plugins can access services in their
120       // extensions which can be invoked right away if the plugin is loaded dynamically
121       for (plugin in plugins) {
122         val containerDescriptor = getContainerDescriptor(plugin)
123         registerServices(containerDescriptor.services, plugin)
124
125         for (descriptor in containerDescriptor.components) {
126           if (!descriptor.prepareClasses(headless) || !isComponentSuitable(descriptor)) {
127             continue
128           }
129
130           try {
131             registerComponent(descriptor, plugin)
132             componentConfigCount++
133           }
134           catch (e: Throwable) {
135             handleInitComponentError(e, null, plugin.pluginId)
136           }
137         }
138
139         val listeners = getContainerDescriptor(plugin).listeners
140         if (listeners.isNotEmpty()) {
141           if (map == null) {
142             map = ContainerUtil.newConcurrentMap()
143           }
144
145           for (listener in listeners) {
146             if ((isUnitTestMode && !listener.activeInTestMode) || (isHeadlessMode && !listener.activeInHeadlessMode)) {
147               continue
148             }
149
150             map!!.getOrPut(listener.topicClassName) { SmartList() }.add(listener)
151           }
152         }
153
154         containerDescriptor.extensionPoints?.let {
155           extensionArea.registerExtensionPoints(plugin, it, this)
156         }
157       }
158     }
159
160     parallelActivity.run("${activityNamePrefix}extension registration") {
161       val notifyListeners = LoadingPhase.isStartupComplete()
162       for (descriptor in plugins) {
163         descriptor.registerExtensions(extensionArea, this, notifyListeners)
164       }
165     }
166
167     if (myComponentConfigCount <= 0) {
168       myComponentConfigCount = componentConfigCount
169     }
170
171     // app - phase must be set before getMessageBus()
172     if (picoContainer.parent == null && !LoadingPhase.PROJECT_OPENED.isComplete /* loading plugin on the fly */) {
173       LoadingPhase.setCurrentPhase(LoadingPhase.COMPONENT_REGISTERED)
174     }
175
176     // todo support lazy listeners for dynamically loaded plugins
177     // ensure that messageBus is created, regardless of lazy listeners map state
178     val messageBus = messageBus as MessageBusImpl
179     if (map != null) {
180       messageBus.setLazyListeners(map!!)
181     }
182   }
183
184   protected fun createComponents(indicator: ProgressIndicator?) {
185     LOG.assertTrue(!componentCreated)
186
187     if (indicator != null) {
188       indicator.isIndeterminate = false
189     }
190
191     val activityNamePrefix = activityNamePrefix()
192     val activity = when (activityNamePrefix) {
193       null -> null
194       else -> StartUpMeasurer.start("$activityNamePrefix${StartUpMeasurer.Phases.CREATE_COMPONENTS_SUFFIX}")
195     }
196
197     for (componentAdapter in myPicoContainer.componentAdapters) {
198       if (componentAdapter is MyComponentAdapter) {
199         componentAdapter.getInstance<Any>(this, indicator = indicator)
200       }
201     }
202
203     activity?.end()
204
205     componentCreated = true
206   }
207
208   @TestOnly
209   fun registerComponentImplementation(componentKey: Class<*>, componentImplementation: Class<*>, shouldBeRegistered: Boolean) {
210     val picoContainer = picoContainer
211     val adapter = picoContainer.unregisterComponent(componentKey) as MyComponentAdapter?
212     if (shouldBeRegistered) {
213       LOG.assertTrue(adapter != null)
214     }
215     picoContainer.registerComponent(MyComponentAdapter(componentKey, componentImplementation.name, DefaultPluginDescriptor("test registerComponentImplementation"), this, componentImplementation))
216   }
217
218   @TestOnly
219   fun <T : Any> replaceComponentInstance(componentKey: Class<T>, componentImplementation: T, parentDisposable: Disposable?): T? {
220     val adapter = myPicoContainer.getComponentAdapter(componentKey) as MyComponentAdapter
221     return adapter.replaceInstance(componentImplementation, parentDisposable)
222   }
223
224   private fun registerComponent(config: ComponentConfig, pluginDescriptor: PluginDescriptor) {
225     val interfaceClass = Class.forName(config.interfaceClass, true, pluginDescriptor.pluginClassLoader)
226
227     if (config.options != null && java.lang.Boolean.parseBoolean(config.options!!.get("overrides"))) {
228       myPicoContainer.unregisterComponent(interfaceClass) ?: throw PluginException("$config does not override anything", pluginDescriptor.pluginId)
229     }
230
231     val implementationClass = when {
232       config.interfaceClass == config.implementationClass -> interfaceClass.name
233       else -> config.implementationClass
234     }
235
236     // implementationClass == null means we want to unregister this component
237     if (!implementationClass.isNullOrEmpty()) {
238       val ws = config.options != null && java.lang.Boolean.parseBoolean(config.options!!.get("workspace"))
239       myPicoContainer.registerComponent(MyComponentAdapter(interfaceClass, implementationClass, pluginDescriptor, this, null, ws))
240     }
241   }
242
243   internal fun getApplication(): Application? = if (this is Application) this else ApplicationManager.getApplication()
244
245   private fun registerServices(services: List<ServiceDescriptor>, pluginDescriptor: IdeaPluginDescriptor) {
246     val picoContainer = myPicoContainer
247     for (descriptor in services) {
248       // Allow to re-define service implementations in plugins.
249       // Empty serviceImplementation means we want to unregister service.
250       if (descriptor.overrides && picoContainer.unregisterComponent(descriptor.getInterface()) == null) {
251         throw PluginException("Service: ${descriptor.getInterface()} doesn't override anything", pluginDescriptor.pluginId)
252       }
253
254       // empty serviceImplementation means we want to unregister service
255       if (descriptor.implementation != null) {
256         picoContainer.registerComponent(ServiceComponentAdapter(descriptor, pluginDescriptor, this))
257       }
258     }
259   }
260
261   @Internal
262   fun handleInitComponentError(t: Throwable, componentClassName: String?, pluginId: PluginId) {
263     if (handlingInitComponentError) {
264       return
265     }
266
267     handlingInitComponentError = true
268     try {
269       PluginManager.handleComponentError(t, componentClassName, pluginId)
270     }
271     finally {
272       handlingInitComponentError = false
273     }
274   }
275
276   @Internal
277   fun initializeComponent(component: Any, serviceDescriptor: ServiceDescriptor?) {
278     if (serviceDescriptor == null || !(component is PathMacroManager || component is IComponentStore || component is MessageBusFactory)) {
279       LoadingPhase.CONFIGURATION_STORE_INITIALIZED.assertAtLeast()
280       componentStore.initComponent(component, serviceDescriptor)
281     }
282   }
283
284   protected abstract fun getContainerDescriptor(pluginDescriptor: IdeaPluginDescriptorImpl): ContainerDescriptor
285
286   final override fun <T : Any> getComponent(interfaceClass: Class<T>): T? {
287     val picoContainer = picoContainer
288     val adapter = picoContainer.getComponentAdapter(interfaceClass) ?: return null
289     @Suppress("UNCHECKED_CAST")
290     return when (adapter) {
291       is BaseComponentAdapter -> {
292         if (parent != null && adapter.componentManager !== this) {
293           LOG.error("getComponent must be called on appropriate container (current: $this, expected: ${adapter.componentManager})")
294         }
295         adapter.getInstance(adapter.componentManager)
296       }
297       else -> adapter.getComponentInstance(picoContainer) as T
298     }
299   }
300
301   final override fun <T : Any> getService(serviceClass: Class<T>, createIfNeeded: Boolean): T? {
302     val lightServices = lightServices
303     if (lightServices != null && isLightService(serviceClass)) {
304       return getLightService(lightServices, serviceClass, createIfNeeded)
305     }
306
307     val key = serviceClass.name
308     val adapter = picoContainer.getServiceAdapter(key) as? ServiceComponentAdapter
309     if (adapter != null) {
310       return adapter.getInstance(this, createIfNeeded)
311     }
312
313     ProgressManager.checkCanceled()
314
315     if (parent != null) {
316       val result = parent.getService(serviceClass, createIfNeeded)
317       if (result != null) {
318         LOG.error("$key is registered as application service, but requested as project one")
319         return result
320       }
321     }
322
323     val result = getComponent(serviceClass) ?: return null
324     PluginException.logPluginError(LOG, "$key requested as a service, but it is a component - convert it to a service or " +
325                                         "change call to ${if (parent == null) "ApplicationManager.getApplication().getComponent()" else "project.getComponent()"}",
326                                    null, serviceClass)
327     return result
328   }
329
330   private fun <T : Any> getLightService(lightServices: ConcurrentMap<Class<*>, Any>, serviceClass: Class<T>, createIfNeeded: Boolean): T? {
331     @Suppress("UNCHECKED_CAST")
332     val result = lightServices.get(serviceClass) as T?
333     if (result != null || !createIfNeeded) {
334       return result
335     }
336     else {
337       synchronized(serviceClass) {
338         return getOrCreateLightService(serviceClass, lightServices)
339       }
340     }
341   }
342
343   @Internal
344   fun getServiceImplementationClassNames(prefix: String): List<String> {
345     val result = ArrayList<String>()
346     ServiceManagerImpl.processAllDescriptors(this) { serviceDescriptor ->
347       val implementation = serviceDescriptor.implementation ?: return@processAllDescriptors
348       if (implementation.startsWith(prefix)) {
349         result.add(implementation)
350       }
351     }
352     return result
353   }
354
355   private fun <T : Any> getOrCreateLightService(serviceClass: Class<T>, cache: ConcurrentMap<Class<*>, Any>): T {
356     LoadingPhase.COMPONENT_REGISTERED.assertAtLeast()
357
358     @Suppress("UNCHECKED_CAST")
359     var result = cache.get(serviceClass) as T?
360     if (result != null) {
361       return result
362     }
363
364     HeavyProcessLatch.INSTANCE.processStarted("Creating service '${serviceClass.name}'").use {
365       if (ProgressIndicatorProvider.getGlobalProgressIndicator() == null) {
366         result = createLightService(serviceClass)
367       }
368       else {
369         ProgressManager.getInstance().executeNonCancelableSection {
370           result = createLightService(serviceClass)
371         }
372       }
373     }
374
375     val prevValue = cache.put(serviceClass, result)
376     LOG.assertTrue(prevValue == null)
377     return result!!
378   }
379
380   final override fun createMessageBus(): MessageBus {
381     val messageBus: MessageBus = MessageBusFactory.newMessageBus(this, parent?.messageBus)
382     if (messageBus is MessageBusImpl && StartUpMeasurer.isMeasuringPluginStartupCosts()) {
383       messageBus.setMessageDeliveryListener { topic, messageName, handler, duration ->
384         if (!StartUpMeasurer.isMeasuringPluginStartupCosts()) {
385           messageBus.setMessageDeliveryListener(null)
386           return@setMessageDeliveryListener
387         }
388
389         logMessageBusDelivery(topic, messageName, handler, duration)
390       }
391     }
392
393     registerServiceInstance(MessageBus::class.java, messageBus, fakeCorePluginDescriptor)
394     return messageBus
395   }
396
397   protected open fun logMessageBusDelivery(topic: Topic<*>, messageName: String?, handler: Any, durationInNano: Long) {
398     val loader = handler.javaClass.classLoader
399     val pluginId = if (loader is PluginClassLoader) loader.pluginIdString else PluginManagerCore.CORE_PLUGIN_ID
400     StartUpMeasurer.addPluginCost(pluginId, "MessageBus", durationInNano)
401   }
402
403   /**
404    * Use only if approved by core team.
405    */
406   @Internal
407   fun registerComponent(key: Class<*>, implementation: Class<*>, pluginDescriptor: PluginDescriptor, override: Boolean) {
408     val picoContainer = picoContainer
409     if (override && picoContainer.unregisterComponent(key) == null) {
410       throw PluginException("Component $key doesn't override anything", pluginDescriptor.pluginId)
411     }
412     picoContainer.registerComponent(MyComponentAdapter(key, implementation.name, pluginDescriptor, this, implementation))
413   }
414
415   /**
416    * Use only if approved by core team.
417    */
418   @Internal
419   fun registerService(serviceInterface: Class<*>, implementation: Class<*>, pluginDescriptor: PluginDescriptor, override: Boolean) {
420     val serviceKey = serviceInterface.name
421     if (override && myPicoContainer.unregisterComponent(serviceKey) == null) {
422       throw PluginException("Service $serviceKey doesn't override anything", pluginDescriptor.pluginId)
423     }
424
425     val descriptor = ServiceDescriptor()
426     descriptor.serviceInterface = serviceInterface.name
427     descriptor.serviceImplementation = implementation.name
428     myPicoContainer.registerComponent(ServiceComponentAdapter(descriptor, pluginDescriptor, this, implementation))
429   }
430
431   /**
432    * Use only if approved by core team.
433    */
434   @Internal
435   fun <T : Any> registerServiceInstance(serviceInterface: Class<T>, instance: T, pluginDescriptor: PluginDescriptor) {
436     val serviceKey = serviceInterface.name
437     myPicoContainer.unregisterComponent(serviceKey)
438
439     val descriptor = ServiceDescriptor()
440     descriptor.serviceInterface = serviceKey
441     descriptor.serviceImplementation = instance.javaClass.name
442     picoContainer.registerComponent(ServiceComponentAdapter(descriptor, pluginDescriptor, this, instance.javaClass, instance))
443   }
444
445   @TestOnly
446   @Internal
447   fun <T : Any> replaceServiceInstance(serviceInterface: Class<T>, instance: T, parentDisposable: Disposable) {
448     val adapter = myPicoContainer.getServiceAdapter(serviceInterface.name) as ServiceComponentAdapter
449     adapter.replaceInstance(instance, parentDisposable)
450   }
451
452   private fun <T : Any> createLightService(serviceClass: Class<T>): T {
453     val startTime = StartUpMeasurer.getCurrentTime()
454
455     val result = instantiateClass(serviceClass, null)
456     if (result is Disposable) {
457       Disposer.register(this, result)
458     }
459
460     initializeComponent(result, null)
461     ParallelActivity.SERVICE.record(startTime, serviceClass, getActivityLevel())
462     return result
463   }
464
465   final override fun <T : Any> instantiateClass(aClass: Class<T>, pluginId: PluginId?): T {
466     try {
467       ProgressManager.checkCanceled()
468     }
469     catch (e: ProcessCanceledException) {
470       // otherwise ExceptionInInitializerError happens and the class is screwed forever
471       @Suppress("SpellCheckingInspection")
472       if (!e.stackTrace.any { it.methodName == "<clinit>" }) {
473         throw e
474       }
475     }
476
477     try {
478       if (parent == null) {
479         val constructor = aClass.getDeclaredConstructor()
480         constructor.isAccessible = true
481         return constructor.newInstance()
482       }
483       else {
484         val constructors = aClass.declaredConstructors
485
486         var constructor: Constructor<*>? = if (constructors.size > 1) {
487           // see ConfigurableEP - prefer constructor that accepts our instance
488           constructors.firstOrNull { it.parameterCount == 1 && it.parameterTypes[0].isAssignableFrom(javaClass) }
489         }
490         else {
491           null
492         }
493
494         if (constructor == null) {
495           constructors.sortBy { it.parameterCount }
496           constructor = constructors.first()!!
497         }
498
499         constructor.isAccessible = true
500         @Suppress("UNCHECKED_CAST")
501         return when (constructor.parameterCount) {
502           1 -> constructor.newInstance(this)
503           else -> constructor.newInstance()
504         } as T
505       }
506     }
507     catch (e: Throwable) {
508       if (e is InvocationTargetException) {
509         val targetException = e.targetException
510         if (targetException is ControlFlowException) {
511           throw targetException
512         }
513       }
514       else if (e is ControlFlowException) {
515         throw e
516       }
517
518       val message = "Cannot create class ${aClass.name}"
519       if (pluginId == null) {
520         throw PluginException.createByClass(message, e, aClass)
521       }
522       else {
523         throw PluginException(message, e, pluginId)
524       }
525     }
526   }
527
528   final override fun <T : Any> instantiateClassWithConstructorInjection(aClass: Class<T>, key: Any, pluginId: PluginId?): T {
529     // constructorParameterResolver is very expensive, because pico container behaviour is to find greediest satisfiable constructor,
530     // so, if class has constructors (Project) and (Project, Foo, Bar), then Foo and Bar unrelated classes will be searched for.
531     // To avoid this expensive nearly linear search of extension, first resolve without our logic to resolve extensions, and in case of error try expensive.
532     try {
533       return instantiateUsingPicoContainer(aClass, key, this, constructorParameterResolver)
534     }
535     catch (e: ProcessCanceledException) {
536       throw e
537     }
538     catch (e: ExtensionNotApplicableException) {
539       throw e
540     }
541     catch (e: Exception) {
542       if (lightServices == null || e is IncorrectOperationException) {
543         throw e
544       }
545       else {
546         assertExtensionInjection(pluginId, e)
547         return instantiateUsingPicoContainer(aClass, key, this, heavyConstructorParameterResolver)
548       }
549     }
550   }
551
552   protected open fun assertExtensionInjection(pluginId: PluginId?, e: Exception) {
553     val app = getApplication()
554     @Suppress("SpellCheckingInspection")
555     if (app != null && app.isUnitTestMode && pluginId?.idString != "org.jetbrains.kotlin" && pluginId?.idString != "Lombook Plugin") {
556       throw UnsupportedOperationException("In tests, extension classes are not resolved for constructor injection, to enforce removing such deprecated references.", e)
557     }
558   }
559
560   final override fun <T : Any> instantiateExtensionWithPicoContainerOnlyIfNeeded(className: String?, pluginDescriptor: PluginDescriptor?): T {
561     val pluginId = pluginDescriptor?.pluginId ?: PluginId.getId("unknown")
562     if (className == null) {
563       throw PluginException("implementation class is not specified", pluginId)
564     }
565
566     val aClass = try {
567       @Suppress("UNCHECKED_CAST")
568       Class.forName(className, true, pluginDescriptor?.pluginClassLoader ?: javaClass.classLoader) as Class<T>
569     }
570     catch (e: Throwable) {
571       throw PluginException(e, pluginId)
572     }
573
574     try {
575       return instantiateClass(aClass, pluginId)
576     }
577     catch (e: ProcessCanceledException) {
578       throw e
579     }
580     catch (e: ExtensionNotApplicableException) {
581       throw e
582     }
583     catch (e: Throwable) {
584       when {
585         e.cause is NoSuchMethodException || e.cause is IllegalArgumentException -> {
586           val exception = PluginException("Bean extension class constructor must not have parameters: $className", pluginId)
587           if ((pluginDescriptor?.isBundled == true) || getApplication()?.isUnitTestMode == true) {
588             LOG.error(exception)
589           }
590           else {
591             LOG.warn(exception)
592           }
593         }
594         e is PluginException -> throw e
595         else -> throw if (pluginDescriptor == null) PluginException.createByClass(e, aClass) else PluginException(e, pluginDescriptor.pluginId)
596       }
597     }
598
599     return instantiateClassWithConstructorInjection(aClass, aClass, pluginId)
600   }
601
602   final override fun createListener(descriptor: ListenerDescriptor): Any {
603     val pluginDescriptor = descriptor.pluginDescriptor
604     val aClass = try {
605       Class.forName(descriptor.listenerClassName, true, pluginDescriptor.pluginClassLoader)
606     }
607     catch (e: Throwable) {
608       throw PluginException("Cannot create listener ${descriptor.listenerClassName}", e, pluginDescriptor.pluginId)
609     }
610     return instantiateClass(aClass, pluginDescriptor.pluginId)
611   }
612
613   final override fun logError(error: Throwable, pluginId: PluginId) {
614     if (error is ProcessCanceledException || error is ExtensionNotApplicableException) {
615       throw error
616     }
617
618     LOG.error(createPluginExceptionIfNeeded(error, pluginId))
619   }
620
621   final override fun createError(error: Throwable, pluginId: PluginId): RuntimeException {
622     return when (error) {
623       is ProcessCanceledException, is ExtensionNotApplicableException -> error as RuntimeException
624       else -> createPluginExceptionIfNeeded(error, pluginId)
625     }
626   }
627
628   final override fun createError(message: String, pluginId: PluginId) = PluginException(message, pluginId)
629
630   @Internal
631   fun getActivityLevel(): StartUpMeasurer.Level = DefaultPicoContainer.getActivityLevel(myPicoContainer)
632
633   @Internal
634   fun unloadServices(containerDescriptor: ContainerDescriptor): List<Any> {
635     val unloadedInstances = ArrayList<Any>()
636     for (service in containerDescriptor.services) {
637       val adapter = myPicoContainer.unregisterComponent(service.getInterface()) as? ServiceComponentAdapter ?: continue
638       val instance = adapter.getInstance<Any>(this, createIfNeeded = false) ?: continue
639       if (instance is Disposable) {
640         Disposer.dispose(instance)
641       }
642       unloadedInstances.add(instance)
643     }
644     return unloadedInstances
645   }
646
647   @Internal
648   open fun activityNamePrefix(): String? = null
649
650   @ApiStatus.Internal
651   fun preloadServices(plugins: List<IdeaPluginDescriptor>): CompletableFuture<*> {
652     @Suppress("UNCHECKED_CAST")
653     plugins as List<IdeaPluginDescriptorImpl>
654
655     val futures = mutableListOf<CompletableFuture<Void>>()
656     val executor = AppExecutorUtil.createBoundedApplicationPoolExecutor("preload services", Runtime.getRuntime().availableProcessors(), false)
657     for (plugin in plugins) {
658       for (service in getContainerDescriptor(plugin).services) {
659         if (service.preload) {
660           futures.add(CompletableFuture.runAsync(Runnable {
661             (myPicoContainer.getServiceAdapter(service.getInterface()) as ServiceComponentAdapter?)?.getInstance<Any>(this)
662           }, executor))
663         }
664       }
665     }
666     executor.shutdown()
667     return CompletableFuture.allOf(*futures.toTypedArray())
668   }
669 }
670
671 private fun createPluginExceptionIfNeeded(error: Throwable, pluginId: PluginId): RuntimeException {
672   return when (error) {
673     is PluginException, is ExtensionInstantiationException -> error as RuntimeException
674     else -> PluginException(error, pluginId)
675   }
676 }
677
678 private fun <T> isLightService(serviceClass: Class<T>): Boolean {
679   return Modifier.isFinal(serviceClass.modifiers) && serviceClass.isAnnotationPresent(Service::class.java)
680 }