IDEA-331839 do not use CountDownLatch, introduce ideStartupWizard EP

GitOrigin-RevId: f554674027a55a36256b456add65f5820a9bab80
This commit is contained in:
Vladimir Krivosheev
2023-09-28 09:50:06 +02:00
committed by intellij-monorepo-bot
parent 2ab5336e83
commit 0d943b69fa
12 changed files with 115 additions and 111 deletions

View File

@@ -26,6 +26,5 @@
</orderEntry>
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.idea.customization.base" />
<orderEntry type="module" module-name="intellij.ide.startup.importSettings" />
</component>
</module>

View File

@@ -3,16 +3,12 @@ package com.intellij.openapi.application.ex;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.util.indexing.impl.IndexDebugProperties;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.util.concurrent.CountDownLatch;
public final class ApplicationManagerEx extends ApplicationManager {
public static final String IS_INTERNAL_PROPERTY = "idea.is.internal";
private static volatile boolean inStressTest;
private static volatile CountDownLatch isInitialStart;
public static ApplicationEx getApplicationEx() {
return (ApplicationEx)ourApplication;
@@ -31,19 +27,4 @@ public final class ApplicationManagerEx extends ApplicationManager {
inStressTest = value;
IndexDebugProperties.IS_IN_STRESS_TESTS = value;
}
public static void setInitialStart() {
isInitialStart = new CountDownLatch(1);
}
public static boolean isInitialStart() {
return getInitialStartState() != null;
}
@Nullable
public static CountDownLatch getInitialStartState() {
return isInitialStart;
}
}

View File

@@ -146,7 +146,7 @@ public final class ConfigImportHelper {
log.error("Couldn't backup current config or delete current config directory", e);
}
}
else if (!IdeStartupWizardKt.isStartupWizardEnabled()) {
else if (!Boolean.getBoolean("intellij.startup.wizard")) {
if (shouldAskForConfig()) {
oldConfigDirAndOldIdePath = showDialogAndGetOldConfigPath(guessedOldConfigDirs.getPaths());
importScenarioStatistics = SHOW_DIALOG_REQUESTED_BY_PROPERTY;

View File

@@ -1,47 +0,0 @@
// 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.openapi.application
import com.intellij.openapi.application.ex.ApplicationManagerEx
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.logger
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withContext
private const val INTELLIJ_STARTUP_WIZARD_CLASS_PROPERTY = "intellij.startup-wizard.class"
private val LOG: Logger
get() = logger<IdeStartupWizard>()
internal val isStartupWizardEnabled: Boolean =
!System.getProperty(INTELLIJ_STARTUP_WIZARD_CLASS_PROPERTY).isNullOrBlank()
internal suspend fun runStartupWizard() {
if (!isStartupWizardEnabled) return
if (!ConfigImportHelper.isNewUser()) return
LOG.info("Entering startup wizard workflow.")
waitForAppManagerInitialState()
val className = System.getProperty(INTELLIJ_STARTUP_WIZARD_CLASS_PROPERTY)
LOG.info("Passing execution control to $className.")
val wizardClass = Class.forName(className)
val instance = wizardClass.getDeclaredConstructor().newInstance() as IdeStartupWizard
instance.run()
}
private suspend fun waitForAppManagerInitialState() {
val latch = ApplicationManagerEx.getInitialStartState()
if (latch == null) error("Cannot get initial startup state")
LOG.info("Waiting for app manager initial state.")
withContext(Dispatchers.IO) {
runInterruptible {
latch.await()
}
}
}
interface IdeStartupWizard {
suspend fun run()
}

View File

@@ -17,7 +17,10 @@ import com.intellij.ide.ui.IconMapLoader
import com.intellij.ide.ui.LafManager
import com.intellij.ide.ui.UISettings
import com.intellij.ide.ui.laf.LafManagerImpl
import com.intellij.idea.*
import com.intellij.idea.AppExitCodes
import com.intellij.idea.AppMode
import com.intellij.idea.IdeStarter
import com.intellij.idea.StartupErrorReporter
import com.intellij.internal.statistic.collectors.fus.actions.persistence.ActionsEventLogGroup
import com.intellij.openapi.application.*
import com.intellij.openapi.application.ex.ApplicationEx
@@ -160,6 +163,7 @@ internal suspend fun loadApp(app: ApplicationImpl,
appRegisteredJob.join()
initConfigurationStoreJob.join()
val appInitializedListenerJob = launch {
val appInitializedListeners = appInitListeners.await()
span("app initialized callback") {
@@ -167,6 +171,7 @@ internal suspend fun loadApp(app: ApplicationImpl,
callAppInitialized(listeners = appInitializedListeners, asyncScope = app.coroutineScope)
}
}
asyncScope.launch {
launch(CoroutineName("checkThirdPartyPluginsAllowed")) {
checkThirdPartyPluginsAllowed()

View File

@@ -0,0 +1,47 @@
// 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.ide.bootstrap
import com.intellij.diagnostic.PluginException
import com.intellij.openapi.application.Application
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.extensions.impl.ExtensionPointImpl
import com.intellij.platform.diagnostic.telemetry.impl.span
import kotlinx.coroutines.Job
import org.jetbrains.annotations.ApiStatus.Internal
internal suspend fun runStartupWizard(isInitialStart: Job, app: Application) {
val log = logger<IdeStartupWizard>()
log.info("Entering startup wizard workflow.")
span("app manager initial state waiting") {
isInitialStart.join()
}
val point = app.extensionArea
.getExtensionPoint<IdeStartupWizard>("com.intellij.ideStartupWizard") as ExtensionPointImpl<IdeStartupWizard>
for (adapter in point.sortedAdapters) {
val pluginDescriptor = adapter.pluginDescriptor
if (!pluginDescriptor.isBundled) {
log.error(PluginException("ideStartupWizard extension can be implemented only by a bundled plugin", pluginDescriptor.pluginId))
continue
}
try {
log.info("Passing execution control to $adapter.")
span("${adapter.assignableToClassName}.run") {
adapter.createInstance<IdeStartupWizard>(app)?.run()
}
break
}
catch (e: Throwable) {
log.error(PluginException(e, pluginDescriptor.pluginId))
}
}
point.reset()
}
@Internal
interface IdeStartupWizard {
suspend fun run()
}

View File

@@ -22,7 +22,6 @@ import com.intellij.openapi.application.impl.*
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.getOrLogException
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.diagnostic.runAndLogException
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.IconLoader
import com.intellij.openapi.util.ShutDownTracker
@@ -64,6 +63,7 @@ import java.util.function.BiFunction
import java.util.logging.ConsoleHandler
import java.util.logging.Level
import javax.swing.*
import kotlin.concurrent.Volatile
import kotlin.system.exitProcess
internal const val IDE_STARTED: String = "------------------------------------------------------ IDE STARTED ------------------------------------------------------"
@@ -187,34 +187,41 @@ fun CoroutineScope.startApplication(args: List<String>,
val euaDocumentDeferred = async { loadEuaDocument(appInfoDeferred) }
val configImportDeferred = async {
if (!isHeadless && configImportNeededDeferred.await()) {
initLafJob.join()
val log = logDeferred.await()
importConfig(
args = args,
targetDirectoryToImportConfig = targetDirectoryToImportConfig ?: PathManager.getConfigDir(),
log = log,
appStarter = appStarterDeferred.await(),
euaDocumentDeferred = euaDocumentDeferred,
)
val configImportDeferred: Deferred<Job?> = async {
if (isHeadless || !configImportNeededDeferred.await()) {
return@async null
}
if (ConfigImportHelper.isNewUser() && System.getProperty("ide.experimental.ui") == null) {
initLafJob.join()
val log = logDeferred.await()
importConfig(
args = args,
targetDirectoryToImportConfig = targetDirectoryToImportConfig ?: PathManager.getConfigDir(),
log = log,
appStarter = appStarterDeferred.await(),
euaDocumentDeferred = euaDocumentDeferred,
)
if (ConfigImportHelper.isNewUser()) {
if (System.getProperty("ide.experimental.ui") == null) {
runCatching {
EarlyAccessRegistryManager.setAndFlush(mapOf("ide.experimental.ui" to "true"))
}.getOrLogException(log)
}
if (isStartupWizardEnabled && ConfigImportHelper.isNewUser()) {
log.info("Will enter initial app wizard flow.")
ApplicationManagerEx.setInitialStart()
}
log.info("Will enter initial app wizard flow.")
val result = CompletableDeferred<Boolean>()
isInitialStart = result
result
}
else {
null
}
}
val pluginSetDeferred = async {
// plugins cannot be loaded when a config import is needed, because plugins may be added after importing
configImportDeferred.await()
configImportDeferred.join()
PluginManagerCore.scheduleDescriptorLoading(coroutineScope = this@startApplication,
zipFilePoolDeferred = zipFilePoolDeferred,
@@ -245,7 +252,7 @@ fun CoroutineScope.startApplication(args: List<String>,
Class.forName(OpenTelemetrySdkBuilder::class.java.name, true, classLoader)
}
val appLoaded = async {
val appLoaded = launch {
checkSystemDirJob.join()
val classBeforeAppProperty = System.getProperty(IDEA_CLASS_BEFORE_APPLICATION_PROPERTY)
@@ -262,30 +269,26 @@ fun CoroutineScope.startApplication(args: List<String>,
val starter = loadApp(app = app,
initAwtToolkitAndEventQueueJob = initEventQueueJob,
pluginSetDeferred = pluginSetDeferred,
appInfoDeferred = appInfoDeferred,
appInfoDeferred = appInfoDeferred,
euaDocumentDeferred = euaDocumentDeferred,
asyncScope = this@startApplication,
initLafJob = initLafJob,
logDeferred = logDeferred,
appRegisteredJob = appRegisteredJob,
args = args.filterNot { CommandLineArgs.isKnownArgument(it) })
if (isStartupWizardEnabled) {
// Initial startup wizard relies on config import being performed before this async ends.
configImportDeferred.await()
}
else {
// In case of startup wizard, this will be executed at a stage separated from app loading.
executeApplicationStarter(starter, args)
}
return@async starter
}
if (isStartupWizardEnabled) {
launch {
val starter = appLoaded.await()
val log = logDeferred.await()
log.runAndLogException { runStartupWizard() }
executeApplicationStarter(starter, args)
// out of appLoaded scope
this@startApplication.launch {
val isInitialStart = configImportDeferred.await()
// appLoaded not only provides starter, but also loads app, that's why it is here
if (isInitialStart != null) {
val log = logDeferred.await()
runCatching {
span("startup wizard run") {
runStartupWizard(isInitialStart = isInitialStart, app = ApplicationManager.getApplication())
}
}.getOrLogException(log)
}
executeApplicationStarter(starter = starter, args = args)
}
}
@@ -311,6 +314,10 @@ fun CoroutineScope.startApplication(args: List<String>,
}
}
@Volatile
@JvmField
internal var isInitialStart: CompletableDeferred<Boolean>? = null
private fun CoroutineScope.scheduleEnableCoroutineDumpAndJstack() {
if (!System.getProperty("idea.enable.coroutine.dump", "true").toBoolean()) {
return

View File

@@ -141,6 +141,7 @@
<extensionPoint name="projectTemplate"
beanClass="com.intellij.platform.ProjectTemplateEP" dynamic="true"/>
<extensionPoint name="ideStartupWizard" interface="com.intellij.platform.ide.bootstrap.IdeStartupWizard" dynamic="false"/>
<extensionPoint name="ApplicationLoadListener" interface="com.intellij.ide.ApplicationLoadListener" dynamic="false"/>
<extensionPoint name="ideEventQueueDispatcher" interface="com.intellij.ide.IdeEventQueue$EventDispatcher" dynamic="true"/>

View File

@@ -3,12 +3,12 @@
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
</component>
</module>

View File

@@ -0,0 +1,6 @@
<idea-plugin package="com.intellij.ide.startup.importSettings">
<extensions defaultExtensionNs="com.intellij">
<!--suppress PluginXmlDynamicPlugin -->
<ideStartupWizard implementation="com.intellij.ide.startup.importSettings.IdeStartupWizardImpl"/>
</extensions>
</idea-plugin>

View File

@@ -1,14 +1,19 @@
package intellij.ide.startup.importSettings
package com.intellij.ide.startup.importSettings
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.IdeStartupWizard
import com.intellij.openapi.extensions.ExtensionNotApplicableException
import com.intellij.platform.ide.bootstrap.IdeStartupWizard
import com.intellij.ui.components.dialog
import com.intellij.ui.dsl.builder.panel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Suppress("unused") // instantiated by reflection
class IdeStartupWizardImpl : IdeStartupWizard {
private class IdeStartupWizardImpl : IdeStartupWizard {
init {
if (!System.getProperty("intellij.startup.wizard", "false").toBoolean()) {
throw ExtensionNotApplicableException.create()
}
}
@Suppress("HardCodedStringLiteral") // temporary
override suspend fun run() {