IJPL-166 enable coroutine dump a little bit later, log error

GitOrigin-RevId: 214a83e6855634ecc47c6cf8b17f4920336f729a
This commit is contained in:
Vladimir Krivosheev
2024-01-25 15:23:24 +01:00
committed by intellij-monorepo-bot
parent 082d393a4b
commit b6bbebcdae
5 changed files with 255 additions and 191 deletions

View File

@@ -4,7 +4,10 @@
@file:Suppress("RAW_RUN_BLOCKING")
package com.intellij.platform.ide.bootstrap
import com.intellij.diagnostic.COROUTINE_DUMP_HEADER
import com.intellij.diagnostic.LoadingState
import com.intellij.diagnostic.dumpCoroutines
import com.intellij.diagnostic.enableCoroutineDump
import com.intellij.ide.*
import com.intellij.ide.bootstrap.InitAppContext
import com.intellij.ide.gdpr.EndUserAgreement
@@ -47,6 +50,7 @@ import com.intellij.util.PlatformUtils
import com.intellij.util.io.URLUtil
import com.intellij.util.io.createDirectories
import com.intellij.util.lang.ZipFilePool
import com.jetbrains.JBR
import kotlinx.coroutines.*
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.annotations.VisibleForTesting
@@ -178,13 +182,23 @@ internal suspend fun loadApp(app: ApplicationImpl,
}
}
val coroutineDebugJob = launch(CoroutineName("coroutine debug probes init")) {
enableCoroutineDump().onFailure { e ->
LOG.error("Cannot enable coroutine debug dump", e)
}
enableJstack()
}
asyncScope.launch {
launch(CoroutineName("checkThirdPartyPluginsAllowed")) {
// do not use launch here - don't overload CPU, let some room for JIT and other CPU-intensive tasks during start-up
coroutineDebugJob.join()
span("checkThirdPartyPluginsAllowed") {
checkThirdPartyPluginsAllowed()
}
// doesn't block app start-up
launch(CoroutineName("post app init tasks")) {
span("post app init tasks") {
runPostAppInitTasks()
}
@@ -197,6 +211,17 @@ internal suspend fun loadApp(app: ApplicationImpl,
}
}
private suspend fun enableJstack() {
span("coroutine jstack configuration") {
JBR.getJstack()?.includeInfoFrom {
"""
$COROUTINE_DUMP_HEADER
${dumpCoroutines(stripDump = false)}
"""
}
}
}
private suspend fun preInitApp(app: ApplicationImpl,
asyncScope: CoroutineScope,
initLafJob: Job,

View File

@@ -26,9 +26,9 @@ private val LOG: Logger
get() = logger<IdeStartupWizard>()
val isIdeStartupWizardEnabled: Boolean
get() = !ApplicationManagerEx.isInIntegrationTest()
&& System.getProperty ("intellij.startup.wizard", "true").toBoolean()
&& IdeStartupExperiment.isExperimentEnabled()
get() = !ApplicationManagerEx.isInIntegrationTest() &&
System.getProperty ("intellij.startup.wizard", "true").toBoolean() &&
IdeStartupExperiment.isExperimentEnabled()
@ExperimentalCoroutinesApi
internal suspend fun runStartupWizard(isInitialStart: Job, app: Application) {
@@ -174,9 +174,9 @@ private object IdeStartupExperiment {
@Suppress("DEPRECATION")
private fun getGroupKind(group: Int) = when {
PlatformUtils.isIdeaUltimate() || PlatformUtils.isPyCharmPro() -> when {
group in 0..7 -> GroupKind.Experimental
group == 8 || group == 9 -> GroupKind.Control
PlatformUtils.isIdeaUltimate() || PlatformUtils.isPyCharmPro() -> when (group) {
in 0..7 -> GroupKind.Experimental
8, 9 -> GroupKind.Control
else -> GroupKind.Undefined
}
else -> when (group) {
@@ -186,12 +186,13 @@ private object IdeStartupExperiment {
}
}
private fun String.asBucket() = MathUtil.nonNegativeAbs(this.hashCode()) % 256
private fun asBucket(s: String) = MathUtil.nonNegativeAbs(s.hashCode()) % 256
private fun getBucket(): Int {
val deviceId = LOG.runAndLogException {
DeviceIdManager.getOrGenerateId(object : DeviceIdManager.DeviceIdToken {}, "FUS")
} ?: return 0
return deviceId.asBucket()
return asBucket(deviceId)
}
val experimentGroup by lazy {
@@ -204,7 +205,7 @@ private object IdeStartupExperiment {
experimentGroup
}
val experimentGroupKind by lazy {
val experimentGroupKind: GroupKind by lazy {
getGroupKind(experimentGroup)
}

View File

@@ -0,0 +1,160 @@
// Copyright 2000-2024 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.accessibility.enableScreenReaderSupportIfNecessary
import com.intellij.ide.gdpr.EndUserAgreement
import com.intellij.idea.AppMode
import com.intellij.openapi.application.ConfigImportHelper
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.application.impl.RawSwingDispatcher
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.getOrLogException
import com.intellij.openapi.util.IconLoader
import com.intellij.openapi.util.registry.EarlyAccessRegistryManager
import com.intellij.platform.diagnostic.telemetry.impl.span
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.withContext
import java.nio.file.Path
import java.util.concurrent.CancellationException
internal suspend fun importConfigIfNeeded(isHeadless: Boolean,
configImportNeededDeferred: Deferred<Boolean>,
lockSystemDirsJob: Job,
logDeferred: Deferred<Logger>,
args: List<String>,
targetDirectoryToImportConfig: Path?,
appStarterDeferred: Deferred<AppStarter>,
euaDocumentDeferred: Deferred<EndUserAgreement.Document?>,
initLafJob: Job): Job? {
if (isHeadless) {
importConfigHeadless(configImportNeededDeferred = configImportNeededDeferred,
lockSystemDirsJob = lockSystemDirsJob,
logDeferred = logDeferred,
args = args,
targetDirectoryToImportConfig = targetDirectoryToImportConfig,
appStarterDeferred = appStarterDeferred,
euaDocumentDeferred = euaDocumentDeferred)
return null
}
if (AppMode.isRemoteDevHost() || !configImportNeededDeferred.await()) {
return null
}
initLafJob.join()
val log = logDeferred.await()
importConfig(
args = args,
targetDirectoryToImportConfig = targetDirectoryToImportConfig ?: PathManager.getConfigDir(),
log = log,
appStarter = appStarterDeferred.await(),
euaDocumentDeferred = euaDocumentDeferred,
)
if (ConfigImportHelper.isNewUser()) {
enableNewUi(logDeferred)
if (isIdeStartupWizardEnabled) {
log.info("Will enter initial app wizard flow.")
val result = CompletableDeferred<Boolean>()
isInitialStart = result
return result
}
else {
return null
}
}
else {
return null
}
}
private suspend fun importConfig(args: List<String>,
targetDirectoryToImportConfig: Path,
log: Logger,
appStarter: AppStarter,
euaDocumentDeferred: Deferred<EndUserAgreement.Document?>,
headlessAutoImport: Boolean = false) {
if (headlessAutoImport) {
// headless AppStarters are not notified about config import
val veryFirstStartOnThisComputer = euaDocumentDeferred.await() != null
withContext(RawSwingDispatcher) {
try {
ConfigImportHelper.importConfigsTo(veryFirstStartOnThisComputer, targetDirectoryToImportConfig, args, log, true)
log.info("Automatic config import completed")
}
catch (e: UnsupportedOperationException) {
log.info("Automatic config import is not possible", e)
}
}
EarlyAccessRegistryManager.invalidate()
IconLoader.clearCache()
return
}
span("screen reader checking") {
runCatching {
enableScreenReaderSupportIfNecessary()
}.getOrLogException(log)
}
span("config importing") {
appStarter.beforeImportConfigs()
val veryFirstStartOnThisComputer = euaDocumentDeferred.await() != null
withContext(RawSwingDispatcher) {
ConfigImportHelper.importConfigsTo(veryFirstStartOnThisComputer, targetDirectoryToImportConfig, args, log)
}
appStarter.importFinished(targetDirectoryToImportConfig)
EarlyAccessRegistryManager.invalidate()
IconLoader.clearCache()
}
}
private suspend fun enableNewUi(logDeferred: Deferred<Logger>) {
if (System.getProperty("ide.experimental.ui") == null) {
try {
EarlyAccessRegistryManager.setAndFlush(mapOf("ide.experimental.ui" to "true"))
}
catch (e: CancellationException) {
throw e
}
catch (e: Throwable) {
logDeferred.await().error(e)
}
}
}
private suspend fun importConfigHeadless(configImportNeededDeferred: Deferred<Boolean>,
lockSystemDirsJob: Job,
logDeferred: Deferred<Logger>,
args: List<String>,
targetDirectoryToImportConfig: Path?,
appStarterDeferred: Deferred<AppStarter>,
euaDocumentDeferred: Deferred<EndUserAgreement.Document?>) {
if (!configImportNeededDeferred.await()) {
return
}
// make sure we lock the dir before writing
lockSystemDirsJob.join()
if (!ConfigImportHelper.isHeadlessAutomaticConfigImportAllowed()) {
enableNewUi(logDeferred)
}
else {
val log = logDeferred.await()
importConfig(
args = args,
targetDirectoryToImportConfig = targetDirectoryToImportConfig ?: PathManager.getConfigDir(),
log = log,
appStarter = appStarterDeferred.await(),
euaDocumentDeferred = euaDocumentDeferred,
headlessAutoImport = true
)
if (ConfigImportHelper.isNewUser()) {
enableNewUi(logDeferred)
}
}
}

View File

@@ -5,11 +5,9 @@
package com.intellij.platform.ide.bootstrap
import com.intellij.BundleBase
import com.intellij.accessibility.enableScreenReaderSupportIfNecessary
import com.intellij.diagnostic.*
import com.intellij.ide.*
import com.intellij.ide.bootstrap.*
import com.intellij.ide.gdpr.EndUserAgreement
import com.intellij.ide.instrument.WriteIntentLockInstrumenter
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.idea.*
@@ -22,10 +20,8 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.getOrLogException
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.IconLoader
import com.intellij.openapi.util.ShutDownTracker
import com.intellij.openapi.util.SystemInfoRt
import com.intellij.openapi.util.registry.EarlyAccessRegistryManager
import com.intellij.platform.diagnostic.telemetry.impl.OpenTelemetryConfigurator
import com.intellij.platform.diagnostic.telemetry.impl.TelemetryManagerImpl
import com.intellij.platform.diagnostic.telemetry.impl.span
@@ -41,7 +37,6 @@ import com.intellij.util.lang.ZipFilePool
import com.jetbrains.JBR
import io.opentelemetry.sdk.OpenTelemetrySdkBuilder
import kotlinx.coroutines.*
import kotlinx.coroutines.CancellationException
import org.jetbrains.annotations.ApiStatus.Internal
import java.awt.Toolkit
import java.lang.invoke.MethodHandles
@@ -58,7 +53,6 @@ import java.util.function.BiConsumer
import java.util.function.BiFunction
import java.util.logging.ConsoleHandler
import java.util.logging.Level
import kotlin.concurrent.Volatile
import kotlin.system.exitProcess
internal const val IDE_STARTED: String = "------------------------------------------------------ IDE STARTED ------------------------------------------------------"
@@ -89,6 +83,10 @@ private val commandProcessor: AtomicReference<(List<String>) -> Deferred<CliResu
internal var shellEnvDeferred: Deferred<Boolean?>? = null
private set
@Volatile
@JvmField
internal var isInitialStart: CompletableDeferred<Boolean>? = null
// the main thread's dispatcher is sequential - use it with care
@OptIn(ExperimentalCoroutinesApi::class)
fun CoroutineScope.startApplication(args: List<String>,
@@ -133,6 +131,7 @@ fun CoroutineScope.startApplication(args: List<String>,
val initBaseLafJob = launch {
initUi(initAwtToolkitJob = initAwtToolkitJob, isHeadless = isHeadless, asyncScope = this@startApplication)
}
if (!isHeadless) {
val initUiScale = launch {
if (SystemInfoRt.isMac) {
@@ -147,7 +146,9 @@ fun CoroutineScope.startApplication(args: List<String>,
}
}
scheduleShowSplashIfNeeded(lockSystemDirsJob = lockSystemDirsJob, initUiScale = initUiScale, appInfoDeferred = appInfoDeferred,
scheduleShowSplashIfNeeded(lockSystemDirsJob = lockSystemDirsJob,
initUiScale = initUiScale,
appInfoDeferred = appInfoDeferred,
args = args)
scheduleUpdateFrameClassAndWindowIconAndPreloadSystemFonts(initAwtToolkitJob = initAwtToolkitJob,
initUiScale = initUiScale,
@@ -201,67 +202,24 @@ fun CoroutineScope.startApplication(args: List<String>,
}
}
scheduleLoadSystemLibsAndLogInfoAndInitMacApp(logDeferred, appInfoDeferred, initLafJob, args, mainScope)
scheduleLoadSystemLibsAndLogInfoAndInitMacApp(logDeferred = logDeferred,
appInfoDeferred = appInfoDeferred,
initUiDeferred = initLafJob,
args = args,
mainScope = mainScope)
val euaDocumentDeferred = async { loadEuaDocument(appInfoDeferred) }
val configImportDeferred: Deferred<Job?> = async {
if (isHeadless) {
if (!configImportNeededDeferred.await()) {
return@async null
}
// make sure we lock the dir before writing
lockSystemDirsJob.join()
if (!ConfigImportHelper.isHeadlessAutomaticConfigImportAllowed()) {
enableNewUi(logDeferred)
}
else {
val log = logDeferred.await()
importConfig(
importConfigIfNeeded(isHeadless = isHeadless,
configImportNeededDeferred = configImportNeededDeferred,
lockSystemDirsJob = lockSystemDirsJob,
logDeferred = logDeferred,
args = args,
targetDirectoryToImportConfig = targetDirectoryToImportConfig ?: PathManager.getConfigDir(),
log = log,
appStarter = appStarterDeferred.await(),
targetDirectoryToImportConfig = targetDirectoryToImportConfig,
appStarterDeferred = appStarterDeferred,
euaDocumentDeferred = euaDocumentDeferred,
headlessAutoImport = true
)
if (ConfigImportHelper.isNewUser()) {
enableNewUi(logDeferred)
}
}
return@async null
}
if (AppMode.isRemoteDevHost() || !configImportNeededDeferred.await()) {
return@async null
}
initLafJob.join()
val log = logDeferred.await()
importConfig(
args = args,
targetDirectoryToImportConfig = targetDirectoryToImportConfig ?: PathManager.getConfigDir(),
log = log,
appStarter = appStarterDeferred.await(),
euaDocumentDeferred = euaDocumentDeferred,
)
if (ConfigImportHelper.isNewUser()) {
enableNewUi(logDeferred)
if (isIdeStartupWizardEnabled) {
log.info("Will enter initial app wizard flow.")
val result = CompletableDeferred<Boolean>()
isInitialStart = result
result
}
else {
null
}
}
else {
null
}
initLafJob =initLafJob)
}
val pluginSetDeferred = async {
@@ -295,7 +253,7 @@ fun CoroutineScope.startApplication(args: List<String>,
Class.forName(OpenTelemetrySdkBuilder::class.java.name, true, classLoader)
}
val appLoaded = launch {
val appLoaded = async {
val initEventQueueJob = scheduleInitIdeEventQueue(initAwtToolkit = initAwtToolkitJob, isHeadless = isHeadless)
checkSystemDirJob.join()
@@ -312,7 +270,7 @@ fun CoroutineScope.startApplication(args: List<String>,
ApplicationImpl(CoroutineScope(mainScope.coroutineContext.job).namedChildScope("Application"), isInternal)
}
val starter = loadApp(app = app,
loadApp(app = app,
initAwtToolkitAndEventQueueJob = initEventQueueJob,
pluginSetDeferred = pluginSetDeferred,
appInfoDeferred = appInfoDeferred,
@@ -322,25 +280,7 @@ fun CoroutineScope.startApplication(args: List<String>,
logDeferred = logDeferred,
appRegisteredJob = appRegisteredJob,
args = args.filterNot { CommandLineArgs.isKnownArgument(it) })
// 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
IdeStartupWizardCollector.logExperimentState()
if (isInitialStart != null) {
LoadingState.compareAndSetCurrentState(LoadingState.COMPONENTS_LOADED, LoadingState.APP_READY)
val log = logDeferred.await()
runCatching {
span("startup wizard run") {
runStartupWizard(isInitialStart = isInitialStart, app = ApplicationManager.getApplication())
}
}.getOrLogException(log)
}
executeApplicationStarter(starter = starter, args = args)
}
}
scheduleEnableCoroutineDumpAndJstack()
launch {
// required for appStarter.prepareStart
@@ -362,52 +302,31 @@ fun CoroutineScope.startApplication(args: List<String>,
appStarter.start(InitAppContext(appRegistered = appRegisteredJob, appLoaded = appLoaded))
}
}
}
private suspend fun enableNewUi(logDeferred: Deferred<Logger>) {
if (System.getProperty("ide.experimental.ui") == null) {
try {
EarlyAccessRegistryManager.setAndFlush(mapOf("ide.experimental.ui" to "true"))
}
catch (e: CancellationException) {
throw e
}
catch (e: Throwable) {
logDeferred.await().error(e)
}
}
}
@Volatile
@JvmField
internal var isInitialStart: CompletableDeferred<Boolean>? = null
private fun CoroutineScope.scheduleEnableCoroutineDumpAndJstack() {
if (!System.getProperty("idea.enable.coroutine.dump", "true").toBoolean()) {
return
}
// out of appLoaded scope
launch {
span("coroutine debug probes init") {
try {
enableCoroutineDump()
}
catch (ignore: NoClassDefFoundError) {
// if for some reason, the class loader has ByteBuddy in the classpath
// (it is an error, and should be fixed - our dev mode and production behaves correctly)
}
catch (e: Exception) {
e.printStackTrace()
// starter is used later, but we need to wait for appLoaded completion
val starter = appLoaded.await()
val isInitialStart = configImportDeferred.await()
// appLoaded not only provides starter, but also loads app, that's why it is here
launch {
if (ConfigImportHelper.isFirstSession()) {
IdeStartupWizardCollector.logExperimentState()
}
}
span("coroutine jstack configuration") {
JBR.getJstack()?.includeInfoFrom {
"""
$COROUTINE_DUMP_HEADER
${dumpCoroutines(stripDump = false)}
"""
if (isInitialStart != null) {
LoadingState.compareAndSetCurrentState(LoadingState.COMPONENTS_LOADED, LoadingState.APP_READY)
val log = logDeferred.await()
runCatching {
span("startup wizard run") {
runStartupWizard(isInitialStart = isInitialStart, app = ApplicationManager.getApplication())
}
}.getOrLogException(log)
}
executeApplicationStarter(starter = starter, args = args)
}
}
@@ -474,8 +393,9 @@ private fun CoroutineScope.scheduleLoadSystemLibsAndLogInfoAndInitMacApp(logDefe
}
}
fun processWindowsLauncherCommandLine(currentDirectory: String, args: Array<String>): Int =
EXTERNAL_LISTENER.apply(currentDirectory, args)
fun processWindowsLauncherCommandLine(currentDirectory: String, args: Array<String>): Int {
return EXTERNAL_LISTENER.apply(currentDirectory, args)
}
@get:Internal
val isImplicitReadOnEDTDisabled: Boolean
@@ -504,48 +424,6 @@ private suspend fun runPreAppClass(args: List<String>, classBeforeAppProperty: S
}
}
private suspend fun importConfig(args: List<String>,
targetDirectoryToImportConfig: Path,
log: Logger,
appStarter: AppStarter,
euaDocumentDeferred: Deferred<EndUserAgreement.Document?>,
headlessAutoImport: Boolean = false) {
if (headlessAutoImport) {
// headless AppStarters are not notified about config import
val veryFirstStartOnThisComputer = euaDocumentDeferred.await() != null
withContext(RawSwingDispatcher) {
try {
ConfigImportHelper.importConfigsTo(veryFirstStartOnThisComputer, targetDirectoryToImportConfig, args, log, true)
log.info("Automatic config import completed")
}
catch (e: UnsupportedOperationException) {
log.info("Automatic config import is not possible", e)
}
}
EarlyAccessRegistryManager.invalidate()
IconLoader.clearCache()
return
}
span("screen reader checking") {
runCatching {
enableScreenReaderSupportIfNecessary()
}.getOrLogException(log)
}
span("config importing") {
appStarter.beforeImportConfigs()
val veryFirstStartOnThisComputer = euaDocumentDeferred.await() != null
withContext(RawSwingDispatcher) {
ConfigImportHelper.importConfigsTo(veryFirstStartOnThisComputer, targetDirectoryToImportConfig, args, log)
}
appStarter.importFinished(targetDirectoryToImportConfig)
EarlyAccessRegistryManager.invalidate()
IconLoader.clearCache()
}
}
private fun CoroutineScope.configureJavaUtilLogging(): Job {
return launch(CoroutineName("console logger configuration")) {
val rootLogger = java.util.logging.Logger.getLogger("")

View File

@@ -29,8 +29,8 @@ fun isCoroutineDumpHeader(line: String): Boolean {
return line == COROUTINE_DUMP_HEADER || line == COROUTINE_DUMP_HEADER_STRIPPED
}
fun enableCoroutineDump() {
runCatching {
fun enableCoroutineDump(): Result<Unit> {
return runCatching {
DebugProbes.enableCreationStackTraces = false
DebugProbes.install()
}