[startup] speed up bootstrap when new modular loading approach is used (IJPL-128)

Before, ModularMain loaded information about all dependencies of the root modules to configure classpath. Now, it takes the pre-computed classpath of 'intellij.platform.bootstrap' module from MANIFEST.MF, adds it to the classpath of the system classloader (which initially contains platform-loader.jar only), load and calls com.intellij.idea.Main.main. Other product-specific modules from the main module group are added by calling ProductLoadingStrategy::addMainModuleGroupToClassPath function. In order to postpone this computation, it's done via 'mainClassLoaderDeferred' async task which is explicitly passed to functions which need to have the fully configured main class loader.

When the old approach is used and all platform JARs are added to JVM classpath via command line argument, 'addMainModuleGroupToClassPath' does nothing.

GitOrigin-RevId: a8590c71dc9f7ac351c0628bcb9b3e15a92b704b
This commit is contained in:
Nikolay Chashnikov
2023-08-31 15:10:41 +02:00
committed by intellij-monorepo-bot
parent 47d65ace9e
commit a55827a69c
8 changed files with 97 additions and 43 deletions

View File

@@ -8,6 +8,7 @@ import com.intellij.concurrency.IdeaForkJoinWorkerThreadFactory
import com.intellij.diagnostic.StartUpMeasurer
import com.intellij.ide.BootstrapBundle
import com.intellij.ide.BytecodeTransformer
import com.intellij.ide.plugins.ProductLoadingStrategy
import com.intellij.ide.plugins.StartupAbortedException
import com.intellij.ide.startup.StartupActionScriptManager
import com.intellij.openapi.application.ApplicationNamesInfo
@@ -56,9 +57,15 @@ fun main(rawArgs: Array<String>) {
addBootstrapTiming("init scope creating", startupTimings)
StartUpMeasurer.addTimings(startupTimings, "bootstrap", startTimeUnixNano)
span("startApplication") {
val mainClassLoaderDeferred = async(CoroutineName("main class loader initializing")) {
val classLoader = AppStarter::class.java.classLoader
ProductLoadingStrategy.strategy.addMainModuleGroupToClassPath(classLoader)
return@async classLoader
}
// not IO-, but CPU-bound due to descrambling, don't use here IO dispatcher
val appStarterDeferred = async(CoroutineName("main class loading")) {
val aClass = AppStarter::class.java.classLoader.loadClass("com.intellij.idea.MainImpl")
val aClass = mainClassLoaderDeferred.await().loadClass("com.intellij.idea.MainImpl")
MethodHandles.lookup().findConstructor(aClass, MethodType.methodType(Void.TYPE)).invoke() as AppStarter
}
@@ -72,7 +79,7 @@ fun main(rawArgs: Array<String>) {
}
}
startApplication(args = args, appStarterDeferred = appStarterDeferred, mainScope = this@runBlocking, busyThread = busyThread)
startApplication(args = args, mainClassLoaderDeferred = mainClassLoaderDeferred, appStarterDeferred = appStarterDeferred, mainScope = this@runBlocking, busyThread = busyThread)
}
}

View File

@@ -3,7 +3,6 @@ package com.intellij.platform.bootstrap;
import com.intellij.ide.plugins.ProductLoadingStrategy;
import com.intellij.idea.Main;
import com.intellij.platform.runtime.repository.ProductModules;
import com.intellij.platform.runtime.repository.RuntimeModuleRepository;
import org.jetbrains.annotations.NotNull;
@@ -14,9 +13,9 @@ import org.jetbrains.annotations.NotNull;
@SuppressWarnings("unused")
public final class ModularMain {
@SuppressWarnings("ConfusingMainMethod")
public static void main(@NotNull RuntimeModuleRepository moduleRepository, @NotNull ProductModules productModules, String @NotNull [] args) {
public static void main(@NotNull RuntimeModuleRepository moduleRepository, String @NotNull [] args) {
//when this new way to load the platform will become default, strategy instance may be passed explicitly instead
ProductLoadingStrategy.setStrategy(new ModuleBasedProductLoadingStrategy(productModules));
ProductLoadingStrategy.setStrategy(new ModuleBasedProductLoadingStrategy(moduleRepository));
Main.main(args);
}
}

View File

@@ -2,8 +2,12 @@
package com.intellij.platform.bootstrap
import com.intellij.ide.plugins.*
import com.intellij.platform.runtime.repository.ProductModules
import com.intellij.openapi.diagnostic.logger
import com.intellij.platform.runtime.repository.RuntimeModuleGroup
import com.intellij.platform.runtime.repository.RuntimeModuleId
import com.intellij.platform.runtime.repository.RuntimeModuleRepository
import com.intellij.platform.runtime.repository.serialization.RuntimeModuleRepositorySerialization
import com.intellij.util.lang.PathClassLoader
import com.intellij.util.lang.ZipFilePool
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
@@ -11,7 +15,30 @@ import kotlinx.coroutines.async
import java.nio.file.Files
import java.nio.file.Path
class ModuleBasedProductLoadingStrategy(private val productModules: ProductModules) : ProductLoadingStrategy() {
class ModuleBasedProductLoadingStrategy(private val moduleRepository: RuntimeModuleRepository) : ProductLoadingStrategy() {
private val productModules by lazy {
val rootModuleName = System.getProperty(PLATFORM_ROOT_MODULE_PROPERTY)
if (rootModuleName == null) {
error("'$PLATFORM_ROOT_MODULE_PROPERTY' system property is not specified")
}
val rootModule = moduleRepository.getModule(RuntimeModuleId.module(rootModuleName))
val productModulesPath = "META-INF/$rootModuleName/product-modules.xml"
val moduleGroupStream = rootModule.readFile(productModulesPath)
if (moduleGroupStream == null) {
error("$productModulesPath is not found in '$rootModuleName' module")
}
RuntimeModuleRepositorySerialization.loadProductModules(moduleGroupStream, productModulesPath, moduleRepository)
}
override fun addMainModuleGroupToClassPath(bootstrapClassLoader: ClassLoader) {
val mainGroupClassPath = productModules.mainModuleGroup.includedModules.flatMapTo(LinkedHashSet()) {
it.moduleDescriptor.resourceRootPaths
}
val classPath = (bootstrapClassLoader as PathClassLoader).classPath
logger<ModuleBasedProductLoadingStrategy>().info("New classpath roots:\n${(mainGroupClassPath - classPath.baseUrls.toSet()).joinToString("\n")}")
classPath.addFiles(mainGroupClassPath)
}
override fun loadBundledPluginDescriptors(scope: CoroutineScope,
bundledPluginDir: Path?,
isUnitTestMode: Boolean,
@@ -58,3 +85,5 @@ class ModuleBasedProductLoadingStrategy(private val productModules: ProductModul
}
}
}
private const val PLATFORM_ROOT_MODULE_PROPERTY = "intellij.platform.root.module"

View File

@@ -382,6 +382,7 @@ private fun CoroutineScope.loadDescriptorsFromProperty(context: DescriptorListLo
@Suppress("DeferredIsResult")
internal fun CoroutineScope.scheduleLoading(zipFilePoolDeferred: Deferred<ZipFilePool>?,
mainClassLoaderDeferred: Deferred<ClassLoader>,
logDeferred: Deferred<Logger>?): Deferred<PluginSet> {
val resultDeferred = async(CoroutineName("plugin descriptor loading")) {
val isUnitTestMode = PluginManagerCore.isUnitTestMode
@@ -396,6 +397,7 @@ internal fun CoroutineScope.scheduleLoading(zipFilePoolDeferred: Deferred<ZipFil
isUnitTestMode = isUnitTestMode,
isRunningFromSources = isRunningFromSources,
zipFilePoolDeferred = zipFilePoolDeferred,
mainClassLoaderDeferred = mainClassLoaderDeferred
)
}
result
@@ -490,7 +492,8 @@ fun loadDescriptorsForDeprecatedWizard(): PluginLoadingResult {
isMissingIncludeIgnored = isUnitTestMode,
checkOptionalConfigFileUniqueness = isUnitTestMode || isRunningFromSources,
).use { context ->
loadDescriptors(context = context, isUnitTestMode = isUnitTestMode, isRunningFromSources = isRunningFromSources)
loadDescriptors(context = context, isUnitTestMode = isUnitTestMode, isRunningFromSources = isRunningFromSources,
mainClassLoaderDeferred = CompletableDeferred(DescriptorListLoadingContext::class.java.classLoader))
}
}
}
@@ -507,10 +510,12 @@ suspend fun loadDescriptors(
isUnitTestMode: Boolean = PluginManagerCore.isUnitTestMode,
isRunningFromSources: Boolean,
zipFilePoolDeferred: Deferred<ZipFilePool>? = null,
mainClassLoaderDeferred: Deferred<ClassLoader>,
): PluginLoadingResult {
val listDeferred: List<Deferred<IdeaPluginDescriptorImpl?>>
val extraListDeferred: List<Deferred<IdeaPluginDescriptorImpl?>>
val zipFilePool = if (context.transient) null else zipFilePoolDeferred?.await()
val mainClassLoader = mainClassLoaderDeferred.await()
withContext(Dispatchers.IO) {
listDeferred = loadDescriptorsFromDirs(
context = context,
@@ -518,6 +523,7 @@ suspend fun loadDescriptors(
isUnitTestMode = isUnitTestMode,
isRunningFromSources = isRunningFromSources,
zipFilePool = zipFilePool,
mainClassLoader = mainClassLoader
)
extraListDeferred = loadDescriptorsFromProperty(context, zipFilePool)
}
@@ -577,6 +583,7 @@ private fun CoroutineScope.loadDescriptorsFromDirs(
isUnitTestMode: Boolean = PluginManagerCore.isUnitTestMode,
isRunningFromSources: Boolean = PluginManagerCore.isRunningFromSources(),
zipFilePool: ZipFilePool?,
mainClassLoader: ClassLoader,
): List<Deferred<IdeaPluginDescriptorImpl?>> {
val platformPrefixProperty = PlatformUtils.getPlatformPrefix()
val platformPrefix = if (platformPrefixProperty == PlatformUtils.QODANA_PREFIX) {
@@ -591,7 +598,8 @@ private fun CoroutineScope.loadDescriptorsFromDirs(
isUnitTestMode = isUnitTestMode,
isInDevServerMode = AppMode.isDevServer(),
isRunningFromSources = isRunningFromSources,
pool = zipFilePool)
pool = zipFilePool,
classLoader = mainClassLoader)
val custom = loadDescriptorsFromDir(dir = customPluginDir, context = context, isBundled = false, pool = zipFilePool)
@@ -605,8 +613,8 @@ private fun CoroutineScope.loadCoreModules(context: DescriptorListLoadingContext
isUnitTestMode: Boolean,
isInDevServerMode: Boolean,
isRunningFromSources: Boolean,
pool: ZipFilePool?): List<Deferred<IdeaPluginDescriptorImpl?>> {
val classLoader = DescriptorListLoadingContext::class.java.classLoader
pool: ZipFilePool?,
classLoader: ClassLoader): List<Deferred<IdeaPluginDescriptorImpl?>> {
val pathResolver = ClassPathXmlPathResolver(classLoader = classLoader, isRunningFromSources = isRunningFromSources && !isInDevServerMode)
val useCoreClassLoader = pathResolver.isRunningFromSources ||
platformPrefix.startsWith("CodeServer") ||
@@ -795,6 +803,7 @@ fun loadDescriptorsFromOtherIde(
customPluginDir = customPluginDir,
bundledPluginDir = bundledPluginDir,
zipFilePool = null,
mainClassLoader = DescriptorListLoadingContext::class.java.classLoader,
)
}, isMainProcess()),
overrideUseIfCompatible = false,

View File

@@ -27,6 +27,7 @@ import com.intellij.util.Java11Shim
import com.intellij.util.PlatformUtils
import com.intellij.util.lang.UrlClassLoader
import com.intellij.util.lang.ZipFilePool
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.future.asCompletableFuture
@@ -361,17 +362,21 @@ object PluginManagerCore {
@Internal
fun scheduleDescriptorLoading(coroutineScope: CoroutineScope) {
scheduleDescriptorLoading(coroutineScope = coroutineScope, zipFilePoolDeferred = null, logDeferred = null)
scheduleDescriptorLoading(coroutineScope = coroutineScope,
zipFilePoolDeferred = null,
mainClassLoaderDeferred = CompletableDeferred(PluginManagerCore::class.java.classLoader),
logDeferred = null)
}
@Internal
@Synchronized
fun scheduleDescriptorLoading(coroutineScope: CoroutineScope,
zipFilePoolDeferred: Deferred<ZipFilePool>?,
mainClassLoaderDeferred: Deferred<ClassLoader>,
logDeferred: Deferred<Logger>?): Deferred<PluginSet> {
var result = initFuture
if (result == null) {
result = coroutineScope.scheduleLoading(zipFilePoolDeferred, logDeferred)
result = coroutineScope.scheduleLoading(zipFilePoolDeferred, mainClassLoaderDeferred, logDeferred)
initFuture = result
}
return result

View File

@@ -34,6 +34,11 @@ abstract class ProductLoadingStrategy {
}
}
/**
* Adds roots of all modules from the main module group and their dependencies to the classpath of [bootstrapClassLoader].
*/
abstract fun addMainModuleGroupToClassPath(bootstrapClassLoader: ClassLoader)
abstract fun loadBundledPluginDescriptors(scope: CoroutineScope,
bundledPluginDir: Path?,
isUnitTestMode: Boolean,
@@ -42,6 +47,9 @@ abstract class ProductLoadingStrategy {
}
private class PathBasedProductLoadingStrategy : ProductLoadingStrategy() {
override fun addMainModuleGroupToClassPath(bootstrapClassLoader: ClassLoader) {
}
override fun loadBundledPluginDescriptors(scope: CoroutineScope,
bundledPluginDir: Path?,
isUnitTestMode: Boolean,

View File

@@ -98,8 +98,9 @@ private val commandProcessor: AtomicReference<(List<String>) -> Deferred<CliResu
internal var shellEnvDeferred: Deferred<Boolean?>? = null
private set
// the main thread's dispatcher is sequential - use it with care
// the main thread's dispatcher is sequential - use it with care
fun CoroutineScope.startApplication(args: List<String>,
mainClassLoaderDeferred: Deferred<ClassLoader>,
appStarterDeferred: Deferred<AppStarter>,
mainScope: CoroutineScope,
busyThread: Thread) {
@@ -113,6 +114,7 @@ fun CoroutineScope.startApplication(args: List<String>,
}
val appInfoDeferred = async(CoroutineName("app info")) {
mainClassLoaderDeferred.await()
// required for DisabledPluginsState and EUA
ApplicationInfoImpl.getShadowInstance()
}
@@ -241,6 +243,7 @@ fun CoroutineScope.startApplication(args: List<String>,
PluginManagerCore.scheduleDescriptorLoading(coroutineScope = asyncScope,
zipFilePoolDeferred = zipFilePoolDeferred,
mainClassLoaderDeferred = mainClassLoaderDeferred,
logDeferred = logDeferred)
}
@@ -746,4 +749,13 @@ fun getServer(): BuiltInServer? {
val candidate = instance.serverDisposable
return if (candidate is BuiltInServer) candidate else null
}
@Deprecated("Use 'startApplication' with 'mainClassLoaderDeferred' parameter instead",
ReplaceWith(
"startApplication(args, CompletableDeferred(AppStarter::class.java.classLoader), appStarterDeferred, mainScope, busyThread)",
"kotlinx.coroutines.CompletableDeferred"))
fun CoroutineScope.startApplication(args: List<String>, appStarterDeferred: Deferred<AppStarter>, mainScope: CoroutineScope,
busyThread: Thread) {
startApplication(args, CompletableDeferred(AppStarter::class.java.classLoader), appStarterDeferred, mainScope, busyThread)
}
//</editor-fold>

View File

@@ -1,19 +1,16 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.runtime.loader;
import com.intellij.platform.runtime.repository.*;
import com.intellij.platform.runtime.repository.serialization.RuntimeModuleRepositorySerialization;
import com.intellij.platform.runtime.repository.ProductModules;
import com.intellij.platform.runtime.repository.RuntimeModuleRepository;
import com.intellij.util.lang.PathClassLoader;
import com.intellij.util.lang.UrlClassLoader;
import org.jetbrains.annotations.Contract;
import java.io.InputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.List;
/**
* Initiates loading of a product based on IntelliJ platform. It loads information about the product modules from {@link RuntimeModuleRepository}
@@ -22,7 +19,6 @@ import java.util.Set;
*/
public final class IntellijLoader {
private static final String RUNTIME_REPOSITORY_PATH_PROPERTY = "intellij.platform.runtime.repository.path";
private static final String PLATFORM_ROOT_MODULE_PROPERTY = "intellij.platform.root.module";
public static void main(String[] args) throws Throwable {
String repositoryPathString = System.getProperty(RUNTIME_REPOSITORY_PATH_PROPERTY);
@@ -30,31 +26,20 @@ public final class IntellijLoader {
reportError(RUNTIME_REPOSITORY_PATH_PROPERTY + " is not specified");
}
String rootModuleName = System.getProperty(PLATFORM_ROOT_MODULE_PROPERTY);
if (rootModuleName == null) {
reportError(PLATFORM_ROOT_MODULE_PROPERTY + " is not specified");
}
RuntimeModuleRepository repository = RuntimeModuleRepository.create(Path.of(repositoryPathString));
RuntimeModuleDescriptor rootModule = repository.getModule(RuntimeModuleId.module(rootModuleName));
String productModulesPath = "META-INF/" + rootModuleName + "/product-modules.xml";
InputStream moduleGroupStream = rootModule.readFile(productModulesPath);
if (moduleGroupStream == null) {
reportError(productModulesPath + " is not found in " + rootModuleName + " module");
List<Path> bootstrapClasspath = repository.getBootstrapClasspath("intellij.platform.bootstrap");
ClassLoader appClassLoader = IntellijLoader.class.getClassLoader();
if (!(appClassLoader instanceof PathClassLoader)) {
reportError("JVM for IntelliJ must be started with -Djava.system.class.loader=com.intellij.util.lang.PathClassLoader parameter");
}
ProductModules productModules = RuntimeModuleRepositorySerialization.loadProductModules(moduleGroupStream, productModulesPath, repository);
String bootstrapModuleName = System.getProperty("intellij.platform.bootstrap.module", "intellij.platform.bootstrap");
Set<Path> classpath = new LinkedHashSet<>(repository.getModule(RuntimeModuleId.module(bootstrapModuleName)).getModuleClasspath());
for (IncludedRuntimeModule item : productModules.getMainModuleGroup().getIncludedModules()) {
classpath.addAll(item.getModuleDescriptor().getResourceRootPaths());
}
PathClassLoader classLoader = new PathClassLoader(UrlClassLoader.build().files(new ArrayList<>(classpath)).parent(IntellijLoader.class.getClassLoader()));
((PathClassLoader)appClassLoader).getClassPath().addFiles(bootstrapClasspath);
String bootstrapClassName = "com.intellij.platform.bootstrap.ModularMain";
Class<?> bootstrapClass = Class.forName(bootstrapClassName, true, classLoader);
MethodHandles.publicLookup()
.findStatic(bootstrapClass, "main", MethodType.methodType(void.class, RuntimeModuleRepository.class, ProductModules.class, String[].class))
.invokeExact(repository, productModules, args);
Class<?> bootstrapClass = Class.forName(bootstrapClassName, true, appClassLoader);
MethodHandle methodHandle = MethodHandles.publicLookup()
.findStatic(bootstrapClass, "main",
MethodType.methodType(void.class, RuntimeModuleRepository.class, String[].class));
methodHandle.invokeExact(repository, args);
}
@Contract("_ -> fail")