mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[debugger] LSP-423: Migrate execution stack calculation from flow to suspend function
Current implementations of execution stack computations immediately return the list of execution stacks with last = true, so let's just wait for all coming execution stacks instead of getting them incrementally. This will reduce code complexity around flows and its lifetimes GitOrigin-RevId: 6b15710796939c017b9e8b848510669193286fb0
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3d04daabc9
commit
9cf0c2e699
+25
-52
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.debugger.impl.backend
|
||||
|
||||
import com.intellij.ide.rpc.FrontendDocumentId
|
||||
@@ -14,11 +14,9 @@ import com.intellij.openapi.editor.event.DocumentEvent
|
||||
import com.intellij.openapi.editor.event.DocumentListener
|
||||
import com.intellij.openapi.project.IndexNotReadyException
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.platform.debugger.impl.rpc.*
|
||||
import com.intellij.platform.project.ProjectId
|
||||
import com.intellij.platform.project.findProject
|
||||
import com.intellij.platform.util.coroutines.attachAsChildTo
|
||||
import com.intellij.platform.util.coroutines.childScope
|
||||
import com.intellij.ui.FileColorManager
|
||||
import com.intellij.util.ThreeState
|
||||
@@ -29,12 +27,10 @@ import com.intellij.xdebugger.evaluation.EvaluationMode
|
||||
import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider
|
||||
import com.intellij.xdebugger.frame.XExecutionStack
|
||||
import com.intellij.xdebugger.frame.XStackFrame
|
||||
import com.intellij.xdebugger.frame.XSuspendContext
|
||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||
import com.intellij.xdebugger.impl.XSourceKind
|
||||
import com.intellij.xdebugger.impl.XSteppingSuspendContext
|
||||
import com.intellij.xdebugger.impl.frame.XStackFrameWithCustomBackgroundColor
|
||||
import com.intellij.xdebugger.impl.frame.XStackFrameWithSeparatorAbove
|
||||
import com.intellij.xdebugger.impl.frame.*
|
||||
import com.intellij.xdebugger.impl.rpc.models.findValue
|
||||
import com.intellij.xdebugger.impl.rpc.models.getOrStoreGlobally
|
||||
import com.intellij.xdebugger.impl.rpc.models.storeGlobally
|
||||
@@ -44,15 +40,10 @@ import com.intellij.xdebugger.stepping.ForceSmartStepIntoSource
|
||||
import com.intellij.xdebugger.stepping.XSmartStepIntoHandler
|
||||
import com.intellij.xdebugger.stepping.XSmartStepIntoVariant
|
||||
import fleet.rpc.core.toRpc
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.future.asDeferred
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.concurrency.Promise
|
||||
import org.jetbrains.concurrency.await
|
||||
import org.jetbrains.concurrency.rejectedPromise
|
||||
@@ -200,57 +191,39 @@ internal class BackendXDebugSessionApi : XDebugSessionApi {
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun computeRunningExecutionStacks(sessionId: XDebugSessionId): Flow<XExecutionStacksEvent> {
|
||||
val session = sessionId.findValue() ?: return emptyFlow()
|
||||
val scope = session.coroutineScope.childScopeCancelledOnSessionEvents("RunningExecutionStacksScope", session)
|
||||
return createExecutionStacksEventFlow(session, scope) { container ->
|
||||
session.debugProcess.computeRunningExecutionStacks(container)
|
||||
override suspend fun computeRunningExecutionStacks(sessionId: XDebugSessionId): TimeoutSafeResult<XExecutionStacksResult> {
|
||||
val session = sessionId.findValue()
|
||||
?: return CompletableDeferred(value = XExecutionStacksResult.ExecutionStacks(listOf()))
|
||||
return computeExecutionStacksImpl(session.coroutineScope, session) {
|
||||
session.debugProcess.computeRunningExecutionStacksSuspend()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun computeExecutionStacks(suspendContextId: XSuspendContextId): Flow<XExecutionStacksEvent> {
|
||||
val suspendContextModel = suspendContextId.findValue() ?: return emptyFlow()
|
||||
override suspend fun computeExecutionStacks(suspendContextId: XSuspendContextId): TimeoutSafeResult<XExecutionStacksResult> {
|
||||
val suspendContextModel = suspendContextId.findValue()
|
||||
?: return CompletableDeferred(value = XExecutionStacksResult.ExecutionStacks(listOf()))
|
||||
val session = suspendContextModel.session
|
||||
return createExecutionStacksEventFlow(session, suspendContextModel.coroutineScope) { container ->
|
||||
suspendContextModel.suspendContext.computeExecutionStacks(container)
|
||||
return computeExecutionStacksImpl(suspendContextModel.coroutineScope, session) {
|
||||
suspendContextModel.suspendContext.computeExecutionStacksSuspend()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createExecutionStacksEventFlow(
|
||||
session: XDebugSessionImpl,
|
||||
private fun computeExecutionStacksImpl(
|
||||
scope: CoroutineScope,
|
||||
computeExecutionStacks: (XSuspendContext.XExecutionStackContainer) -> Unit
|
||||
) : Flow<XExecutionStacksEvent> {
|
||||
return channelFlow {
|
||||
attachAsChildTo(scope)
|
||||
val container = object : XSuspendContext.XExecutionStackContainer {
|
||||
@Volatile
|
||||
var obsolete = false
|
||||
|
||||
override fun isObsolete(): Boolean {
|
||||
return obsolete
|
||||
}
|
||||
|
||||
override fun addExecutionStack(executionStacks: List<XExecutionStack>, last: Boolean) {
|
||||
val stacks = executionStacks.map { stack ->
|
||||
stack.toRpc(scope, session)
|
||||
}
|
||||
trySend(XExecutionStacksEvent.NewExecutionStacks(stacks, last))
|
||||
if (last) {
|
||||
this@channelFlow.close()
|
||||
}
|
||||
}
|
||||
|
||||
override fun errorOccurred(errorMessage: @NlsContexts.DialogMessage String) {
|
||||
trySend(XExecutionStacksEvent.ErrorOccurred(errorMessage))
|
||||
session: XDebugSessionImpl,
|
||||
computeExecutionStacks: suspend () -> List<XExecutionStack>,
|
||||
): TimeoutSafeResult<XExecutionStacksResult> {
|
||||
return scope.async {
|
||||
try {
|
||||
val stackDtos = computeExecutionStacks().map { stack ->
|
||||
stack.toRpc(scope, session)
|
||||
}
|
||||
XExecutionStacksResult.ExecutionStacks(stackDtos)
|
||||
}
|
||||
computeExecutionStacks(container)
|
||||
|
||||
awaitClose {
|
||||
container.obsolete = true
|
||||
catch (e: XExecutionStacksComputationException) {
|
||||
XExecutionStacksResult.ErrorOccurred(e.message ?: "Error occurred while computing execution stacks")
|
||||
}
|
||||
}.buffer(Channel.UNLIMITED)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.childScopeCancelledOnSessionEvents(name: String, session: XDebugSessionImpl): CoroutineScope =
|
||||
|
||||
+3
-2
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.debugger.impl.frontend
|
||||
|
||||
import com.intellij.execution.RunContentDescriptorIdImpl
|
||||
@@ -437,7 +437,8 @@ class FrontendXDebuggerSession private constructor(
|
||||
coroutineScope.launch {
|
||||
XDebugSessionApi.getInstance()
|
||||
.computeRunningExecutionStacks(id)
|
||||
.collectExecutionStackEvents(project, coroutineScope, container)
|
||||
.await()
|
||||
.handleResult(project, coroutineScope, container)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
-19
@@ -1,11 +1,11 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.debugger.impl.frontend.frame
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.debugger.impl.frontend.storage.FrontendXStackFramesStorage
|
||||
import com.intellij.platform.debugger.impl.frontend.storage.getOrCreateStackFrame
|
||||
import com.intellij.platform.debugger.impl.rpc.XDebugSessionApi
|
||||
import com.intellij.platform.debugger.impl.rpc.XExecutionStacksEvent
|
||||
import com.intellij.platform.debugger.impl.rpc.XExecutionStacksResult
|
||||
import com.intellij.platform.debugger.impl.rpc.XStackFrameDto
|
||||
import com.intellij.platform.debugger.impl.rpc.XSuspendContextDto
|
||||
import com.intellij.platform.util.coroutines.childScope
|
||||
@@ -14,7 +14,6 @@ import com.intellij.xdebugger.frame.XSuspendContext
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class FrontendXSuspendContext(
|
||||
@@ -41,7 +40,8 @@ internal class FrontendXSuspendContext(
|
||||
lifetimeScope.launch {
|
||||
XDebugSessionApi.getInstance()
|
||||
.computeExecutionStacks(id)
|
||||
.collectExecutionStackEvents(project, lifetimeScope, container)
|
||||
.await()
|
||||
.handleResult(project, lifetimeScope, container)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,24 +54,23 @@ internal class FrontendXSuspendContext(
|
||||
}
|
||||
}
|
||||
|
||||
internal suspend fun Flow<XExecutionStacksEvent>.collectExecutionStackEvents(
|
||||
internal fun XExecutionStacksResult.handleResult(
|
||||
project: Project,
|
||||
coroutineScope: CoroutineScope,
|
||||
container: XSuspendContext.XExecutionStackContainer
|
||||
container: XSuspendContext.XExecutionStackContainer,
|
||||
) {
|
||||
collect { executionStackEvent ->
|
||||
when (executionStackEvent) {
|
||||
is XExecutionStacksEvent.ErrorOccurred -> {
|
||||
container.errorOccurred(executionStackEvent.errorMessage)
|
||||
}
|
||||
is XExecutionStacksEvent.NewExecutionStacks -> {
|
||||
// TODO[IJPL-177087]: here we are binding FrontendXExecutionStack to the suspend context scope,
|
||||
// which is the safest-narrowest scope in our possession.
|
||||
// However, maybe it's possible to set up, for example, a scope that ends when another stack is selected from a combobox.
|
||||
// But it requires further investigation.
|
||||
val feStacks = executionStackEvent.stacks.map { FrontendXExecutionStack(it, project, coroutineScope) }
|
||||
container.addExecutionStack(feStacks, executionStackEvent.last)
|
||||
}
|
||||
val executionStackEvent = this
|
||||
when (executionStackEvent) {
|
||||
is XExecutionStacksResult.ErrorOccurred -> {
|
||||
container.errorOccurred(executionStackEvent.errorMessage)
|
||||
}
|
||||
is XExecutionStacksResult.ExecutionStacks -> {
|
||||
// TODO[IJPL-177087]: here we are binding FrontendXExecutionStack to the suspend context scope,
|
||||
// which is the safest-narrowest scope in our possession.
|
||||
// However, maybe it's possible to set up, for example, a scope that ends when another stack is selected from a combobox.
|
||||
// But it requires further investigation.
|
||||
val feStacks = executionStackEvent.stacks.map { FrontendXExecutionStack(it, project, coroutineScope) }
|
||||
container.addExecutionStack(feStacks, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.debugger.impl.rpc
|
||||
|
||||
import com.intellij.execution.RunContentDescriptorIdImpl
|
||||
@@ -58,9 +58,9 @@ interface XDebugSessionApi : RemoteApi<Unit> {
|
||||
|
||||
suspend fun setCurrentStackFrame(sessionId: XDebugSessionId, executionStackId: XExecutionStackId, frameId: XStackFrameId, isTopFrame: Boolean, changedByUser: Boolean = false)
|
||||
|
||||
suspend fun computeExecutionStacks(suspendContextId: XSuspendContextId): Flow<XExecutionStacksEvent>
|
||||
suspend fun computeExecutionStacks(suspendContextId: XSuspendContextId): TimeoutSafeResult<XExecutionStacksResult>
|
||||
|
||||
suspend fun computeRunningExecutionStacks(sessionId: XDebugSessionId): Flow<XExecutionStacksEvent>
|
||||
suspend fun computeRunningExecutionStacks(sessionId: XDebugSessionId): TimeoutSafeResult<XExecutionStacksResult>
|
||||
|
||||
suspend fun muteBreakpoints(sessionDataId: XDebugSessionDataId, muted: Boolean)
|
||||
|
||||
@@ -98,12 +98,12 @@ data class XDebugSessionDto(
|
||||
|
||||
@ApiStatus.Internal
|
||||
@Serializable
|
||||
sealed interface XExecutionStacksEvent {
|
||||
sealed interface XExecutionStacksResult {
|
||||
@Serializable
|
||||
data class NewExecutionStacks(val stacks: List<XExecutionStackDto>, val last: Boolean) : XExecutionStacksEvent
|
||||
data class ExecutionStacks(val stacks: List<XExecutionStackDto>) : XExecutionStacksResult
|
||||
|
||||
@Serializable
|
||||
data class ErrorOccurred(val errorMessage: @NlsContexts.DialogMessage String) : XExecutionStacksEvent
|
||||
data class ErrorOccurred(val errorMessage: @NlsContexts.DialogMessage String) : XExecutionStacksResult
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.xdebugger.impl.frame
|
||||
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.xdebugger.XDebugProcess
|
||||
import com.intellij.xdebugger.frame.XExecutionStack
|
||||
import com.intellij.xdebugger.frame.XSuspendContext
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
/**
|
||||
* Computes the list of running execution stacks for this debug process.
|
||||
*
|
||||
* This is a suspend wrapper around [XDebugProcess.computeRunningExecutionStacks] that converts
|
||||
* the callback-based API to a coroutine-based one.
|
||||
*
|
||||
* @return the list of currently running [XExecutionStack]s
|
||||
* @throws XExecutionStacksComputationException if an error occurs during computation
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
suspend fun XDebugProcess.computeRunningExecutionStacksSuspend(): List<XExecutionStack> {
|
||||
return computeExecutionStacksImpl {
|
||||
computeRunningExecutionStacks(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes all execution stacks for this suspend context.
|
||||
*
|
||||
* This is a suspend wrapper around [XSuspendContext.computeExecutionStacks] that converts
|
||||
* the callback-based API to a coroutine-based one.
|
||||
*
|
||||
* @return the list of all [XExecutionStack]s in this suspend context
|
||||
* @throws XExecutionStacksComputationException if an error occurs during computation
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
suspend fun XSuspendContext.computeExecutionStacksSuspend(): List<XExecutionStack> {
|
||||
return computeExecutionStacksImpl { container ->
|
||||
computeExecutionStacks(container)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown when an error occurs during the computation of execution stacks.
|
||||
*
|
||||
* This exception wraps error messages reported via [com.intellij.xdebugger.frame.XSuspendContext.XExecutionStackContainer.errorOccurred].
|
||||
*
|
||||
* @param message the error message describing what went wrong during computation
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
class XExecutionStacksComputationException(message: String) : Exception(message)
|
||||
|
||||
private suspend fun computeExecutionStacksImpl(
|
||||
computeExecutionStacks: (XSuspendContext.XExecutionStackContainer) -> Unit,
|
||||
): List<XExecutionStack> {
|
||||
val result = mutableListOf<XExecutionStack>()
|
||||
val computationCompleted = CompletableDeferred<Unit>()
|
||||
var obsolete = false
|
||||
val container = object : XSuspendContext.XExecutionStackContainer {
|
||||
override fun isObsolete(): Boolean {
|
||||
return obsolete
|
||||
}
|
||||
|
||||
override fun addExecutionStack(executionStacks: List<XExecutionStack>, last: Boolean) {
|
||||
result.addAll(executionStacks)
|
||||
if (last) {
|
||||
computationCompleted.complete(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
override fun errorOccurred(errorMessage: @NlsContexts.DialogMessage String) {
|
||||
computationCompleted.completeExceptionally(XExecutionStacksComputationException(errorMessage))
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
computeExecutionStacks(container)
|
||||
computationCompleted.await()
|
||||
}
|
||||
finally {
|
||||
obsolete = true
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user