[debugger] IDEA-358626 Show indicator with possibility to cancel the stepping request

IJ-MR-145237

GitOrigin-RevId: 4283b9faf567bdbcaf25a44319928e102e3a7b10
This commit is contained in:
Alexey Merkulov
2024-10-01 15:14:39 +00:00
committed by intellij-monorepo-bot
parent c84dac7d1e
commit 0729b2a230
10 changed files with 146 additions and 12 deletions
@@ -156,6 +156,8 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
final ThreadBlockedMonitor myThreadBlockedMonitor = new ThreadBlockedMonitor(this, disposable);
final SteppingProgressTracker mySteppingProgressTracker = new SteppingProgressTracker(this);
// These 2 fields are needs to switching from found suspend-thread context to user-friendly suspend-all context.
// The main related logic is in [SuspendOtherThreadsRequestor].
volatile ParametersForSuspendAllReplacing myParametersForSuspendAllReplacing = null;
@@ -197,6 +199,7 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
DebuggerStatistics.logProcessStatistics(process);
}
});
mySteppingProgressTracker.installListeners();
}
private DebuggerManagerThreadImpl createManagerThread() {
@@ -2262,7 +2265,10 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
}
private class PauseCommand extends DebuggerCommandImpl {
PauseCommand() {
@Nullable private final ThreadReferenceProxyImpl myPredefinedThread;
PauseCommand(@Nullable ThreadReferenceProxyImpl thread) {
myPredefinedThread = thread;
}
@Override
@@ -2274,6 +2280,9 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
getVirtualMachineProxy().suspend();
logThreads();
SuspendContextImpl suspendContext = mySuspendManager.pushSuspendContext(EventRequest.SUSPEND_ALL, 0);
if (myPredefinedThread != null) {
suspendContext.setThread(myPredefinedThread.getThreadReference());
}
myDebugProcessListeners.forEach(it -> it.paused(suspendContext));
myDebuggerManagerThread.schedule(new SuspendContextCommandImpl(suspendContext) {
@@ -2697,8 +2706,8 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
}
@NotNull
public DebuggerCommandImpl createPauseCommand() {
return new PauseCommand();
public DebuggerCommandImpl createPauseCommand(@Nullable ThreadReferenceProxyImpl threadProxy) {
return new PauseCommand(threadProxy);
}
@NotNull
@@ -7,11 +7,7 @@ import com.intellij.debugger.engine.events.SuspendContextCommandImpl
import com.intellij.debugger.engine.managerThread.DebuggerCommand
import com.intellij.debugger.engine.managerThread.DebuggerManagerThread
import com.intellij.debugger.engine.managerThread.SuspendContextCommand
import com.intellij.debugger.impl.DebuggerCompletableFuture
import com.intellij.debugger.impl.DebuggerUtilsAsync
import com.intellij.debugger.impl.InvokeAndWaitThread
import com.intellij.debugger.impl.InvokeThread
import com.intellij.debugger.impl.PrioritizedTask
import com.intellij.debugger.impl.*
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.ComponentManagerEx
@@ -21,16 +17,19 @@ import com.intellij.openapi.progress.util.ProgressIndicatorListener
import com.intellij.openapi.progress.util.ProgressWindow
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.NlsContexts.ProgressTitle
import com.intellij.platform.ide.progress.withBackgroundProgress
import com.intellij.platform.util.coroutines.childScope
import com.intellij.platform.util.progress.withProgressText
import com.intellij.util.concurrency.AppExecutorUtil
import com.sun.jdi.VMDisconnectedException
import kotlinx.coroutines.*
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.TestOnly
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
import kotlin.concurrent.Volatile
class DebuggerManagerThreadImpl(parent: Disposable, private val parentScope: CoroutineScope) :
InvokeAndWaitThread<DebuggerCommandImpl?>(), DebuggerManagerThread, Disposable {
@@ -53,6 +52,24 @@ class DebuggerManagerThreadImpl(parent: Disposable, private val parentScope: Cor
myDisposed = true
}
@ApiStatus.Internal
fun makeCancelable(project: Project, progressTitle: @ProgressTitle String, progressText: @Nls String, howToCancel: () -> Unit): CompletableDeferred<Unit> {
val deferred = CompletableDeferred<Unit>()
coroutineScope.launch {
withBackgroundProgress(project, progressTitle) {
withProgressText(progressText) {
try {
deferred.await()
} catch (e: CancellationException) {
howToCancel()
throw e
}
}
}
}
return deferred
}
private fun createScope() = parentScope.childScope("DebuggerManagerThreadImpl")
override fun invokeAndWait(managerCommand: DebuggerCommandImpl) {
@@ -1,16 +1,21 @@
// 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.debugger.engine
import com.intellij.debugger.JavaDebuggerBundle
import com.sun.jdi.ThreadReference
interface LightOrRealThreadInfo {
val realThread: ThreadReference?
fun checkSameThread(thread: ThreadReference, suspendContext: SuspendContextImpl): Boolean
val filterName: String
}
data class RealThreadInfo(override val realThread: ThreadReference) : LightOrRealThreadInfo {
override fun checkSameThread(thread: ThreadReference, suspendContext: SuspendContextImpl): Boolean {
return realThread == thread
}
override val filterName: String get() = JavaDebuggerBundle.message("stepping.filter.real.thread.name", realThread.name())
}
@@ -0,0 +1,78 @@
// 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.debugger.engine
import com.intellij.debugger.JavaDebuggerBundle
import com.intellij.debugger.engine.events.DebuggerCommandImpl
import com.intellij.debugger.engine.jdi.ThreadReferenceProxy
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.sun.jdi.request.EventRequest
import kotlinx.coroutines.CompletableDeferred
import org.jetbrains.annotations.Nls
private data class TrackedSteppingData(val stepCompetedStatus: CompletableDeferred<Unit>, val threadFilter: (ThreadReferenceProxy?) -> Boolean)
private class SteppingProgressTracker(private val debuggerProcessImpl: DebugProcessImpl) {
private val trackedStepping = mutableListOf<TrackedSteppingData>()
fun installListeners() {
debuggerProcessImpl.addDebugProcessListener(object : DebugProcessListener {
override fun paused(suspendContext: SuspendContext) {
val thread = suspendContext.thread
val completedSteps = trackedStepping.filter { it.threadFilter(thread) }
for ((stepCompetedStatus, _) in completedSteps) {
stepCompetedStatus.complete(Unit)
}
trackedStepping.removeAll(completedSteps)
}
})
}
fun addStepping(stepCompetedStatus: CompletableDeferred<Unit>, threadFilter: (ThreadReferenceProxy?) -> Boolean) {
trackedStepping.add(TrackedSteppingData(stepCompetedStatus, threadFilter))
}
}
private class CancelingSteppingListener : SteppingListener {
override fun beforeSteppingStarted(suspendContext: SuspendContextImpl, steppingAction: SteppingAction) {
val debuggerProcessImpl: DebugProcessImpl = suspendContext.debugProcess
val filter: LightOrRealThreadInfo? = debuggerProcessImpl.requestsManager.filterThread
val threadForStepping: ThreadReferenceProxyImpl? =
if (filter != null) debuggerProcessImpl.virtualMachineProxy.getThreadReferenceProxy(filter.realThread)
else suspendContext.thread
val needSuspendOnlyThread = suspendContext.suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD && threadForStepping != null
val whereStr =
if (threadForStepping != null) JavaDebuggerBundle.message("stepping.filter.real.thread.name", threadForStepping.name())
else filter?.filterName ?: debuggerProcessImpl.session.sessionName
val steppingRestrictionMessage = getSteppingRestrictionMessage(whereStr, steppingAction)
val steppingName = steppingAction.steppingName
val stepCompetedStatus = debuggerProcessImpl.managerThread.makeCancelable(debuggerProcessImpl.project, steppingRestrictionMessage, steppingName) {
val command: DebuggerCommandImpl =
if (needSuspendOnlyThread) debuggerProcessImpl.createFreezeThreadCommand(threadForStepping)
else debuggerProcessImpl.createPauseCommand(threadForStepping)
debuggerProcessImpl.managerThread.schedule(command)
}
val tracker = suspendContext.debugProcess.mySteppingProgressTracker
tracker.addStepping(stepCompetedStatus) { thread ->
!needSuspendOnlyThread || thread == null || thread == threadForStepping
}
}
}
private val SteppingAction.steppingName: @Nls String get() = when (this) {
SteppingAction.STEP_INTO -> JavaDebuggerBundle.message("status.step.into")
SteppingAction.STEP_OUT -> JavaDebuggerBundle.message("status.step.out")
SteppingAction.STEP_OVER -> JavaDebuggerBundle.message("status.step.over")
SteppingAction.RUN_TO_CURSOR -> JavaDebuggerBundle.message("status.run.to.cursor")
}
private fun getSteppingRestrictionMessage(whereSteppingIsPerformed: @Nls String?, steppingAction: SteppingAction): @Nls String {
return when (steppingAction) {
SteppingAction.RUN_TO_CURSOR -> JavaDebuggerBundle.message("status.run.to.cursor.in", whereSteppingIsPerformed)
else -> JavaDebuggerBundle.message("status.stepping.in", whereSteppingIsPerformed)
}
}
@@ -409,7 +409,7 @@ public final class DebuggerSession implements AbstractDebuggerSession {
}
public void pause() {
myDebugProcess.getManagerThread().schedule(myDebugProcess.createPauseCommand());
myDebugProcess.getManagerThread().schedule(myDebugProcess.createPauseCommand(null));
}
/*Presentation*/
@@ -95,6 +95,8 @@ status.step.out=Stepping out
status.step.into=Stepping into
status.step.over=Stepping over
status.run.to.cursor=Run to cursor
status.run.to.cursor.in=Run to cursor in {0}
status.stepping.in=Stepping in {0}
status.process.resumed=Process resumed
status.paused.in.another.thread=Paused in another thread
string.connection=''{0}'', transport: ''{1}''
@@ -548,3 +550,4 @@ paths.to.closest.gc.roots.for=Paths to the Closest GC Roots for {0}
column.name.instances=Instances
column.name.shallow.size=Shallow Size
column.name.retained.size=Retained Size
stepping.filter.real.thread.name=thread {0}