[rd debugger] IDEA-382185 Preload manager should be empty after scope cancellation

* also preloaded events can be consumed only once

GitOrigin-RevId: 280b17ba1226190a40becbe2c9e7054d66fe3161
This commit is contained in:
Maksim Zuev
2025-11-17 17:29:33 +00:00
committed by intellij-monorepo-bot
parent f7e71265d1
commit cd42f126f7
3 changed files with 21 additions and 19 deletions
@@ -30,6 +30,8 @@ import org.jetbrains.annotations.ApiStatus
import org.jetbrains.concurrency.Promise
import org.jetbrains.concurrency.asPromise
import java.util.concurrent.CompletableFuture
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
@ApiStatus.Internal
class FrontendXValue private constructor(
@@ -368,9 +370,9 @@ private fun renderAdvancedPresentation(renderer: XValuePresentation.XValueTextRe
}
}
internal fun Obsolescent.childCoroutineScope(parentScope: CoroutineScope, name: String): CoroutineScope {
internal fun Obsolescent.childCoroutineScope(parentScope: CoroutineScope, name: String, context: CoroutineContext = EmptyCoroutineContext): CoroutineScope {
val obsolescent = this
val scope = parentScope.childScope(name)
val scope = parentScope.childScope(name, context)
parentScope.launch(context = Dispatchers.IO, start = CoroutineStart.UNDISPATCHED) {
while (!obsolescent.isObsolete) {
delay(ConcurrencyUtil.DEFAULT_TIMEOUT_MS)
@@ -7,7 +7,6 @@ import com.intellij.openapi.project.Project
import com.intellij.platform.debugger.impl.frontend.frame.VariablesPreloadManager
import com.intellij.platform.debugger.impl.rpc.*
import com.intellij.platform.debugger.impl.shared.XValuesPresentationBuilder
import com.intellij.platform.util.coroutines.childScope
import com.intellij.ui.SimpleTextAttributes
import com.intellij.xdebugger.frame.XCompositeNode
import com.intellij.xdebugger.frame.XNamedValue
@@ -58,8 +57,8 @@ internal class FrontendXValueContainer(
override fun computeChildren(node: XCompositeNode) {
val childrenManager = getOrCreteChildrenManager(node)
val scope = cs.childScope("FrontendXValueContainer#computeChildren", childrenManager)
node.childCoroutineScope(parentScope = cs, "FrontendXValueContainer#computeChildren").launch(Dispatchers.EDT) {
val scope = node.childCoroutineScope(parentScope = cs, "FrontendXValueContainer#computeChildren", childrenManager)
scope.launch(Dispatchers.EDT) {
val flow = childrenManager.getChildrenEventsFlow(id)
val builder = XValuesPresentationBuilder()
flow.collect { event ->
@@ -3,11 +3,10 @@ package com.intellij.platform.debugger.impl.frontend.frame
import com.intellij.openapi.diagnostic.fileLogger
import com.intellij.platform.debugger.impl.rpc.*
import com.intellij.platform.util.coroutines.childScope
import com.intellij.util.AwaitCancellationAndInvoke
import com.intellij.util.awaitCancellationAndInvoke
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.flow.Flow
@@ -29,19 +28,19 @@ class VariablesPreloadManager(
treeState: XDebuggerTreeExpandedNode,
frameId: XStackFrameId,
) {
private val cs = parentScope.childScope("VariablesPreloadManager")
private val preloadedEvents = ConcurrentHashMap<XContainerId, Channel<XValueComputeChildrenEvent>>()
init {
markToBeLoaded(frameId)
cs.launch {
val localEvents = hashMapOf<XContainerId, Channel<XValueComputeChildrenEvent>>()
markToBeLoaded(frameId, localEvents)
parentScope.launch {
XValueApi.getInstance().computeExpandedChildren(frameId, treeState).collect { event ->
when (event) {
is PreloadChildrenEvent.ToBePreloaded -> {
markToBeLoaded(event.id)
markToBeLoaded(event.id, localEvents)
}
is PreloadChildrenEvent.ExpandedChildrenEvent -> {
val channel = preloadedEvents[event.id] ?: run {
val channel = localEvents[event.id] ?: run {
fileLogger().error("Preloaded event for ${event.id} was not properly received")
return@collect
}
@@ -50,22 +49,24 @@ class VariablesPreloadManager(
}
}
}
parentScope.awaitCancellationAndInvoke {
preloadedEvents.clear()
}
}
fun getChildrenEventsFlow(entityId: XContainerId): Flow<XValueComputeChildrenEvent>? {
val eventsChannel = preloadedEvents[entityId] ?: return null
// channel can be consumed only once, so we remove it here
val eventsChannel = preloadedEvents.remove(entityId) ?: return null
return channelFlow {
eventsChannel.consumeEach { send(it) }
}
}
private fun markToBeLoaded(id: XContainerId) {
val old = preloadedEvents.put(id, Channel(capacity = Channel.UNLIMITED))
private fun markToBeLoaded(id: XContainerId, localEvents: MutableMap<XContainerId, Channel<XValueComputeChildrenEvent>>) {
val channel = Channel<XValueComputeChildrenEvent>(capacity = Channel.UNLIMITED)
val old = localEvents.put(id, channel)
assert(old == null) { "Channel for $id was already registered" }
}
internal fun cancel() {
cs.cancel()
preloadedEvents[id] = channel
}
companion object {