mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[debugger] IDEA-358626 Show indicator with possibility to cancel the stepping request
IJ-MR-145237 GitOrigin-RevId: 4283b9faf567bdbcaf25a44319928e102e3a7b10
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c84dac7d1e
commit
0729b2a230
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user