From 09996159098937c69a3e9ccad4936160b45e4816 Mon Sep 17 00:00:00 2001 From: Vitaly Legchilkin Date: Wed, 24 Sep 2025 18:56:23 +0000 Subject: [PATCH] [python] add TraceContext as a trace tracker for coroutine inner calls * will be used by the Process Tool Window to show the context of the executed command Merge-request: IJ-MR-176530 Merged-by: Vitaly Legchilkin GitOrigin-RevId: 70d41845943b5c19b0647ef4711b16f48c53b28c --- .../messages/PyCommunityBundle.properties | 1 + .../src/com/jetbrains/python/TraceContext.kt | 34 +++++++++++++++++++ .../messages/PyBundle.properties | 14 +++++++- .../stubs/checkers/PyStubsChecker.kt | 5 +-- .../PyV3BaseProjectSettings.kt | 4 +-- .../packaging/PyPackageManagerBridge.kt | 3 +- .../management/PythonPackageManager.kt | 5 +-- .../repository/PythonRepositoryManagerBase.kt | 3 +- .../toolwindow/PyPackagingToolWindowPanel.kt | 4 ++- .../modules/PyPackagesSdkController.kt | 5 ++- .../add/v2/PythonAddLocalInterpreterDialog.kt | 3 +- .../v2/PythonSdkPanelBuilderAndSdkCreator.kt | 3 +- .../com/jetbrains/python/sdk/add/v2/models.kt | 15 ++++---- 13 files changed, 79 insertions(+), 20 deletions(-) create mode 100644 python/openapi/src/com/jetbrains/python/TraceContext.kt diff --git a/python/openapi/resources/messages/PyCommunityBundle.properties b/python/openapi/resources/messages/PyCommunityBundle.properties index 053471f37992..dabf7feb8fff 100644 --- a/python/openapi/resources/messages/PyCommunityBundle.properties +++ b/python/openapi/resources/messages/PyCommunityBundle.properties @@ -1,3 +1,4 @@ python.execution.error={0}\nThe following command finished with error: {1}\nOutput: {2}\nError: {3}\Exit code: {4} python.execution.cant.start.error={0}\nThe following command could not be started: {1}. Error {2} code: {3} python.execution.timeout={0}\nThe following command stopped due to timeout: {1}. +tracecontext.non.interactive=Non Interactive diff --git a/python/openapi/src/com/jetbrains/python/TraceContext.kt b/python/openapi/src/com/jetbrains/python/TraceContext.kt new file mode 100644 index 000000000000..4ca8f1443beb --- /dev/null +++ b/python/openapi/src/com/jetbrains/python/TraceContext.kt @@ -0,0 +1,34 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.jetbrains.python + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.currentCoroutineContext +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.Nls +import kotlin.coroutines.AbstractCoroutineContextElement +import kotlin.coroutines.CoroutineContext + +@ApiStatus.Internal +class TraceContext( + val title: @Nls String, + val parentTraceContext: TraceContext?, +) : AbstractCoroutineContextElement(TraceContext) { + val timestamp: Long = System.currentTimeMillis() + + constructor(title: @Nls String, coroutineScope: CoroutineScope): this(title, coroutineScope.coroutineContext[Key]) + + override fun toString(): String = "TraceContext($title, $timestamp)\n\t-> $parentTraceContext" + + /** + * Key for [TraceContext] instance in the coroutine context. + */ + companion object Key : CoroutineContext.Key { + suspend operator fun invoke(title: @Nls String): TraceContext { + val parent = currentCoroutineContext()[Key] + return TraceContext(title, parent) + } + } +} + +@ApiStatus.Internal +val NON_INTERACTIVE_ROOT_TRACE_CONTEXT: TraceContext = TraceContext(PyCommunityBundle.message("tracecontext.non.interactive"), null) \ No newline at end of file diff --git a/python/pluginResources/messages/PyBundle.properties b/python/pluginResources/messages/PyBundle.properties index 7e66fe671a88..436c4f76e0c0 100644 --- a/python/pluginResources/messages/PyBundle.properties +++ b/python/pluginResources/messages/PyBundle.properties @@ -1699,4 +1699,16 @@ pycharm.free.mode.upgrade.body=Unlock the full potential of your IDE! Boost prod pycharm.free.mode.upgrade.button=Purchase Subscription python.packaging.sync.packages=Syncing project dependencies python.sdk.read.only=Python SDK ''{0}'' is read only and operation cannot be performed. -read.only.python.sdk.system.wide.read.only.message=Modify packages of System Python is forbidden. To modify use another interpreter. \ No newline at end of file +read.only.python.sdk.system.wide.read.only.message=Modify packages of System Python is forbidden. To modify use another interpreter. +tracecontext.detecting.poetry.executable=Detecting Poetry Executable +tracecontext.detecting.pip.executable=Detecting Pip Executable +tracecontext.detecting.uv.executable=Detecting uv Executable +tracecontext.detecting.hatch.executable=Detecting Hatch Executable +tracecontext.detecting.hatch.environments=Detecting Hatch Environments +tracecontext.generating.git=Generating git +tracecontext.packaging.tool.window=Packaging tool window +tracecontext.packages.sdk.controller=Packages SDK Controller +tracecontext.add.local.python.sdk.dialog=Add Local Python SDK Dialog +tracecontext.new.project.wizard=New Project Wizard +tracecontext.loading.interpreter.list=Loading Interpreter List +tracecontext.detecting.conda.executable.and.environments=Detecting Conda Executable and environments \ No newline at end of file diff --git a/python/src/com/jetbrains/python/codeInsight/stubs/checkers/PyStubsChecker.kt b/python/src/com/jetbrains/python/codeInsight/stubs/checkers/PyStubsChecker.kt index fa011dfac842..e735fe51fa70 100644 --- a/python/src/com/jetbrains/python/codeInsight/stubs/checkers/PyStubsChecker.kt +++ b/python/src/com/jetbrains/python/codeInsight/stubs/checkers/PyStubsChecker.kt @@ -7,6 +7,7 @@ import com.intellij.openapi.progress.runBlockingMaybeCancellable import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.util.Key +import com.jetbrains.python.NON_INTERACTIVE_ROOT_TRACE_CONTEXT import com.jetbrains.python.packaging.common.PythonPackageManagementListener import com.jetbrains.python.packaging.management.PythonPackageManager import com.jetbrains.python.packaging.utils.PyPackageCoroutine @@ -19,7 +20,7 @@ internal abstract class PyStubsChecker(val project: Project) : Disposable.Defaul init { project.messageBus.connect(this).subscribe(PythonPackageManager.PACKAGE_MANAGEMENT_TOPIC, object : PythonPackageManagementListener { override fun packagesChanged(sdk: Sdk) { - PyPackageCoroutine.launch(project) { + PyPackageCoroutine.launch(project, NON_INTERACTIVE_ROOT_TRACE_CONTEXT) { checkSdk(sdk) } } @@ -43,7 +44,7 @@ internal abstract class PyStubsChecker(val project: Project) : Disposable.Defaul } } else { - PyPackageCoroutine.launch(project) { + PyPackageCoroutine.launch(project, NON_INTERACTIVE_ROOT_TRACE_CONTEXT) { checkSdk(sdk) } } diff --git a/python/src/com/jetbrains/python/newProjectWizard/PyV3BaseProjectSettings.kt b/python/src/com/jetbrains/python/newProjectWizard/PyV3BaseProjectSettings.kt index 582a619cf001..fa675c8c152e 100644 --- a/python/src/com/jetbrains/python/newProjectWizard/PyV3BaseProjectSettings.kt +++ b/python/src/com/jetbrains/python/newProjectWizard/PyV3BaseProjectSettings.kt @@ -8,12 +8,12 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.platform.ide.progress.withBackgroundProgress import com.jetbrains.python.PyBundle import com.jetbrains.python.Result +import com.jetbrains.python.TraceContext import com.jetbrains.python.errorProcessing.PyResult import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo import com.jetbrains.python.sdk.ModuleOrProject import com.jetbrains.python.sdk.add.v2.PySdkCreator import com.jetbrains.python.sdk.configurePythonSdk -import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @@ -27,7 +27,7 @@ class PyV3BaseProjectSettings(var createGitRepository: Boolean = false) { suspend fun generateAndGetSdk(module: Module, baseDir: VirtualFile, supportsNotEmptyModuleStructure: Boolean = false): PyResult> = coroutineScope { val project = module.project if (createGitRepository) { - launch(CoroutineName("Generating git") + Dispatchers.IO) { + launch(TraceContext(PyBundle.message("tracecontext.generating.git")) + Dispatchers.IO) { withBackgroundProgress(project, PyBundle.message("new.project.git")) { GitRepositoryInitializer.getInstance()?.initRepository(project, baseDir, true) ?: error("No git service available") } diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagerBridge.kt b/python/src/com/jetbrains/python/packaging/PyPackageManagerBridge.kt index 9636c63cb082..4ef7b92b00ac 100644 --- a/python/src/com/jetbrains/python/packaging/PyPackageManagerBridge.kt +++ b/python/src/com/jetbrains/python/packaging/PyPackageManagerBridge.kt @@ -11,6 +11,7 @@ import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.OrderRootType import com.intellij.openapi.vfs.VfsUtil import com.intellij.util.cancelOnDispose +import com.jetbrains.python.NON_INTERACTIVE_ROOT_TRACE_CONTEXT import com.jetbrains.python.getOrNull import com.jetbrains.python.onFailure import com.jetbrains.python.packaging.management.PythonPackageManager @@ -29,7 +30,7 @@ internal open class PyPackageManagerBridge(private val sdk: Sdk) : PyPackageMana override fun hasManagement(): Boolean = true override fun refresh() { - PyPackageCoroutine.launch(null) { + PyPackageCoroutine.launch(null, NON_INTERACTIVE_ROOT_TRACE_CONTEXT) { thisLogger().debug("Refreshing SDK roots and packages cache") writeAction { val files = sdk.getRootProvider().getFiles(OrderRootType.CLASSES) diff --git a/python/src/com/jetbrains/python/packaging/management/PythonPackageManager.kt b/python/src/com/jetbrains/python/packaging/management/PythonPackageManager.kt index 22280bb115a6..b8cd14140763 100644 --- a/python/src/com/jetbrains/python/packaging/management/PythonPackageManager.kt +++ b/python/src/com/jetbrains/python/packaging/management/PythonPackageManager.kt @@ -14,6 +14,7 @@ import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.util.Key import com.intellij.util.cancelOnDispose import com.intellij.util.messages.Topic +import com.jetbrains.python.NON_INTERACTIVE_ROOT_TRACE_CONTEXT import com.jetbrains.python.errorProcessing.PyResult import com.jetbrains.python.getOrNull import com.jetbrains.python.onFailure @@ -44,7 +45,7 @@ import kotlin.coroutines.cancellation.CancellationException abstract class PythonPackageManager(val project: Project, val sdk: Sdk) : Disposable.Default { private val isInited = AtomicBoolean(false) private val initializationJob = if (!shouldBeInitInstantly()) { - PyPackageCoroutine.launch(project, start = CoroutineStart.LAZY) { + PyPackageCoroutine.launch(project, NON_INTERACTIVE_ROOT_TRACE_CONTEXT, start = CoroutineStart.LAZY) { initInstalledPackages() }.also { it.cancelOnDispose(this) @@ -135,7 +136,7 @@ abstract class PythonPackageManager(val project: Project, val sdk: Sdk) : Dispos if (packages != installedPackages) { installedPackages = packages - PyPackageCoroutine.launch(project) { + PyPackageCoroutine.launch(project, NON_INTERACTIVE_ROOT_TRACE_CONTEXT) { reloadOutdatedPackages() }.cancelOnDispose(this) diff --git a/python/src/com/jetbrains/python/packaging/repository/PythonRepositoryManagerBase.kt b/python/src/com/jetbrains/python/packaging/repository/PythonRepositoryManagerBase.kt index 05d3ce23bd96..4d5b220f5100 100644 --- a/python/src/com/jetbrains/python/packaging/repository/PythonRepositoryManagerBase.kt +++ b/python/src/com/jetbrains/python/packaging/repository/PythonRepositoryManagerBase.kt @@ -4,6 +4,7 @@ package com.jetbrains.python.packaging.repository import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.util.cancelOnDispose +import com.jetbrains.python.NON_INTERACTIVE_ROOT_TRACE_CONTEXT import com.jetbrains.python.packaging.PyPackageVersion import com.jetbrains.python.packaging.PyPackageVersionNormalizer import com.jetbrains.python.packaging.PyRequirement @@ -17,7 +18,7 @@ import org.jetbrains.annotations.ApiStatus @ApiStatus.Experimental abstract class PythonRepositoryManagerBase() : PythonRepositoryManager, Disposable.Default { protected val initializationJob: Job by lazy { - PyPackageCoroutine.launch(project, start = CoroutineStart.LAZY) { + PyPackageCoroutine.launch(project, NON_INTERACTIVE_ROOT_TRACE_CONTEXT, start = CoroutineStart.LAZY) { initCaches() }.also { it.cancelOnDispose(this) diff --git a/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowPanel.kt b/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowPanel.kt index 5516d809ea32..02b0f4a9022d 100644 --- a/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowPanel.kt +++ b/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowPanel.kt @@ -27,6 +27,7 @@ import com.intellij.ui.SimpleTextAttributes import com.intellij.util.asDisposable import com.intellij.util.ui.NamedColorUtil import com.jetbrains.python.PyBundle.message +import com.jetbrains.python.TraceContext import com.jetbrains.python.inspections.PyInterpreterInspection import com.jetbrains.python.packaging.toolwindow.details.PyPackageInfoPanel import com.jetbrains.python.packaging.toolwindow.model.DisplayablePackage @@ -63,7 +64,8 @@ class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindo internal val packageListController = PyPackagesListController(project, controller = this) private val moduleController = PyPackagesSdkController(project) private val descriptionController = PyPackageInfoPanel(project) - private val packagingScope = PyPackageCoroutine.getScope(project).childScope("Packaging tool window").also { + private val packagingScope = PyPackageCoroutine.getScope(project) + .childScope("Packaging tool window", TraceContext(message("tracecontext.packaging.tool.window"), null)).also { Disposer.register(this, it.asDisposable()) } diff --git a/python/src/com/jetbrains/python/packaging/toolwindow/modules/PyPackagesSdkController.kt b/python/src/com/jetbrains/python/packaging/toolwindow/modules/PyPackagesSdkController.kt index 3dfaea3ac237..08281fff1b81 100644 --- a/python/src/com/jetbrains/python/packaging/toolwindow/modules/PyPackagesSdkController.kt +++ b/python/src/com/jetbrains/python/packaging/toolwindow/modules/PyPackagesSdkController.kt @@ -19,6 +19,8 @@ import com.intellij.ui.ScrollPaneFactory import com.intellij.ui.SimpleListCellRenderer import com.intellij.ui.components.JBList import com.intellij.util.asDisposable +import com.jetbrains.python.PyBundle +import com.jetbrains.python.TraceContext import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService import com.jetbrains.python.packaging.utils.PyPackageCoroutine import com.jetbrains.python.sdk.PySdkPopupFactory @@ -35,7 +37,8 @@ import javax.swing.event.ListSelectionListener internal class PyPackagesSdkController(private val project: Project) : Disposable.Default { - private val packagingScope: CoroutineScope = PyPackageCoroutine.getScope(project).childScope("Packages SDK Controller").also { + private val packagingScope: CoroutineScope = PyPackageCoroutine.getScope(project) + .childScope("Packages SDK Controller", TraceContext(PyBundle.message("tracecontext.packages.sdk.controller"), null)).also { Disposer.register(this, it.asDisposable()) } diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterDialog.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterDialog.kt index 7ef95e3ca553..f446d74c6a2c 100644 --- a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterDialog.kt +++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterDialog.kt @@ -10,6 +10,7 @@ import com.intellij.ui.dsl.builder.panel import com.intellij.util.concurrency.annotations.RequiresEdt import com.intellij.util.ui.launchOnShow import com.jetbrains.python.PyBundle +import com.jetbrains.python.TraceContext import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows import com.jetbrains.python.packaging.utils.PyPackageCoroutine import com.jetbrains.python.sdk.moduleIfExists @@ -64,7 +65,7 @@ internal class PythonAddLocalInterpreterDialog(private val dialogPresenter: Pyth mainPanel.setupUI(this, WHEN_PROPERTY_CHANGED(AtomicProperty(basePath))) } - rootPanel.launchOnShow("PythonAddLocalInterpreterDialog launchOnShow") { + rootPanel.launchOnShow("PythonAddLocalInterpreterDialog launchOnShow", TraceContext(PyBundle.message("tracecontext.add.local.python.sdk.dialog"), null)) { supervisorScope { model.initialize(this@supervisorScope) mainPanel.onShown(this@supervisorScope) diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonSdkPanelBuilderAndSdkCreator.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonSdkPanelBuilderAndSdkCreator.kt index f9ccba5fb91f..b2cfbf07a826 100644 --- a/python/src/com/jetbrains/python/sdk/add/v2/PythonSdkPanelBuilderAndSdkCreator.kt +++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonSdkPanelBuilderAndSdkCreator.kt @@ -15,6 +15,7 @@ import com.intellij.util.asDisposable import com.intellij.util.ui.launchOnShow import com.jetbrains.python.PyBundle.message import com.jetbrains.python.Result +import com.jetbrains.python.TraceContext import com.jetbrains.python.errorProcessing.ErrorSink import com.jetbrains.python.errorProcessing.PyResult import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo @@ -139,7 +140,7 @@ internal class PythonSdkPanelBuilderAndSdkCreator( } override fun onShownInitialization(scopingComponent: Component) { - scopingComponent.launchOnShow("${this::class.java} onShown initialization") { + scopingComponent.launchOnShow("${this::class.java} onShown initialization", TraceContext(message("tracecontext.new.project.wizard"), null)) { initMutex.withLock { supervisorScope { initialize(this@supervisorScope) diff --git a/python/src/com/jetbrains/python/sdk/add/v2/models.kt b/python/src/com/jetbrains/python/sdk/add/v2/models.kt index 4b2a97428566..f37a16603411 100644 --- a/python/src/com/jetbrains/python/sdk/add/v2/models.kt +++ b/python/src/com/jetbrains/python/sdk/add/v2/models.kt @@ -27,6 +27,7 @@ import com.intellij.util.concurrency.annotations.RequiresEdt import com.jetbrains.python.PyBundle.message import com.jetbrains.python.Result import com.jetbrains.python.Result.Companion.success +import com.jetbrains.python.TraceContext import com.jetbrains.python.errorProcessing.ErrorSink import com.jetbrains.python.errorProcessing.PyResult import com.jetbrains.python.getOrNull @@ -102,7 +103,7 @@ abstract class PythonAddInterpreterModel( modificationCounter.updateAndGet { it + 1 } }.launchIn(scope + Dispatchers.EDT) - scope.launch(CoroutineName("Loading Interpreter List") + Dispatchers.EDT) { + scope.launch(TraceContext(message("tracecontext.loading.interpreter.list"), scope) + Dispatchers.EDT) { installable = getExistingInstallableInterpreters() val existingSelectableInterpreters = getExistingSelectableInterpreters() knownInterpreters.value = existingSelectableInterpreters @@ -128,7 +129,7 @@ abstract class PythonAddInterpreterModel( }.stateIn(scope, started = SharingStarted.Eagerly, initialValue = null) - scope.launch(CoroutineName("Detecting Conda Executable and environments") + Dispatchers.IO) { + scope.launch(TraceContext(message("tracecontext.detecting.conda.executable.and.environments"), scope) + Dispatchers.IO) { detectCondaExecutable() detectCondaEnvironments() }.invokeOnCompletion { @@ -268,22 +269,22 @@ abstract class PythonMutableTargetAddInterpreterModel(projectPathFlows: ProjectP override fun initialize(scope: CoroutineScope) { super.initialize(scope) - scope.launch(CoroutineName("Detecting Poetry Executable")) { + scope.launch(TraceContext(message("tracecontext.detecting.poetry.executable"), scope)) { detectPoetryExecutable() } - scope.launch(CoroutineName("Detecting Pip Executable")) { + scope.launch(TraceContext(message("tracecontext.detecting.pip.executable"), scope)) { detectPipEnvExecutable() } - scope.launch(CoroutineName("Detecting uv Executable")) { + scope.launch(TraceContext(message("tracecontext.detecting.uv.executable"), scope)) { detectUvExecutable() } - scope.launch(CoroutineName("Detecting Hatch Executable")) { + scope.launch(TraceContext(message("tracecontext.detecting.hatch.executable"), scope)) { detectHatchExecutable() } state.hatchExecutable.afterChange(scope.asDisposable()) { pathString -> hatchEnvironmentsResult.value = null - scope.launch(CoroutineName("Detecting Hatch Environments")) { + scope.launch(TraceContext(message("tracecontext.detecting.hatch.environments"), scope)) { val hatchEnvironments = detectHatchEnvironments(pathString) withContext(Dispatchers.EDT) { hatchEnvironmentsResult.value = hatchEnvironments