debug web workers the same way as node child process

allow several paused threads, choosing active, stepping, resuming
This commit is contained in:
Konstantin Ulitin
2017-06-21 14:14:42 +03:00
parent 377aae0af0
commit 3e9cc5b829
5 changed files with 104 additions and 35 deletions
@@ -58,9 +58,6 @@ interface SuspendContext<CALL_FRAME : CallFrame> {
val vm: Vm
get() = throw UnsupportedOperationException()
val workerId: String?
get() = null
}
abstract class ContextDependentAsyncResultConsumer<T>(private val context: SuspendContext<*>) : Consumer<T> {
@@ -55,7 +55,7 @@ abstract class SuspendContextManagerBase<T : SuspendContextBase<CALL_FRAME>, CAL
throw IllegalStateException("Expected $context, but another suspend context exists")
}
context.valueManager.markObsolete()
debugListener.resumed()
debugListener.resumed(context.vm)
}
override val context: SuspendContext<CALL_FRAME>?
@@ -32,8 +32,9 @@ public interface DebugEventListener extends EventListener {
/**
* Reports the virtual machine has resumed. This can happen
* asynchronously, due to a user action in the browser (without explicitly resuming the VM through
* @param vm
*/
default void resumed() {
default void resumed(Vm vm) {
}
/**
@@ -135,7 +135,7 @@ abstract class DebugProcessImpl<C : VmConnection<*>>(session: XDebugSession,
}
val XSuspendContext?.vm: Vm
get() = (this as? SuspendContextView)?.activeExecutionStack?.suspendContext?.vm ?: mainVm!!
get() = (this as? SuspendContextView)?.activeVm ?: mainVm!!
override final fun startForceStepInto(context: XSuspendContext?) {
isForceStep = true
@@ -16,6 +16,7 @@
package org.jetbrains.debugger
import com.intellij.icons.AllIcons
import com.intellij.openapi.diagnostic.logger
import com.intellij.ui.ColoredTextContainer
import com.intellij.ui.SimpleTextAttributes
import com.intellij.util.ui.UIUtil
@@ -30,8 +31,6 @@ import org.jetbrains.debugger.frame.CallFrameView
import org.jetbrains.debugger.values.StringValue
import java.util.*
const val MAIN_LOOP_NAME = "main loop"
/**
* Debugging several VMs simultaneously should be similar to debugging multi-threaded Java application when breakpoints suspend only one thread.
* 1. When thread is paused and another thread reaches breakpoint, show notification about it with possibility to switch thread.
@@ -41,46 +40,48 @@ const val MAIN_LOOP_NAME = "main loop"
* 4. Stepping/releasing updates current thread icon and clears frame, but doesn't switch thread. To release other threads, user needs to
* select them firstly.
*/
abstract class SuspendContextView(protected val debugProcess: MultiVmDebugProcess, protected val activeStack: ExecutionStackView) : XSuspendContext() {
abstract class SuspendContextView(protected val debugProcess: MultiVmDebugProcess,
activeStack: ExecutionStackView,
@Volatile var activeVm: Vm)
: XSuspendContext() {
protected open val stacks: Array<out XExecutionStack> by lazy {
private val stacks: MutableMap<Vm, ScriptExecutionStack> = Collections.synchronizedMap(LinkedHashMap<Vm, ScriptExecutionStack>())
init {
val mainVm = debugProcess.mainVm
val vmList = debugProcess.collectVMs
if (mainVm != null && !vmList.isEmpty()) {
val list = ArrayList<XExecutionStack>()
// main vm should go first
vmList.mapNotNullTo(list) {
vmList.forEach {
val context = it.suspendContextManager.context
if (context == activeStack.suspendContext) {
activeStack
}
else {
createStackView(context, it.presentableName)
}
val stack: ScriptExecutionStack =
if (context == null) {
RunningThreadExecutionStackView(it)
}
else if (context == activeStack.suspendContext) {
activeStack
}
else {
logger<SuspendContextView>().error("Paused VM was lost.")
InactiveAtBreakpointExecutionStackView(it)
}
stacks[it] = stack
}
list.toTypedArray()
}
else {
arrayOf(activeStack)
stacks[activeVm] = activeStack
}
}
private fun createStackView(context: SuspendContext<*>?, displayName: String): XExecutionStack {
return if (context == null) {
RunningThreadExecutionStackView(displayName)
}
else {
InactiveAtBreakpointExecutionStackView(displayName)
}
}
override fun getActiveExecutionStack() = stacks[activeVm]
override fun getActiveExecutionStack() = activeStack
override fun getExecutionStacks(): Array<out XExecutionStack> = stacks
override fun getExecutionStacks(): Array<out XExecutionStack> = stacks.values.toTypedArray()
fun evaluateExpression(expression: String): Promise<String> {
val activeStack = stacks[activeVm]!!
val frame = activeStack.topFrame ?: return rejectedPromise("Top frame is null")
if (frame !is CallFrameView) return rejectedPromise("Can't evaluate on non-paused thread")
return evaluateExpression(frame.callFrame.evaluateContext, expression)
}
@@ -94,9 +95,57 @@ abstract class SuspendContextView(protected val debugProcess: MultiVmDebugProces
resolvedPromise(value.valueString!!)
}
}
fun pauseInactiveThread(inactiveThread: ExecutionStackView) {
stacks[inactiveThread.vm] = inactiveThread
}
fun hasPausedThreads(): Boolean {
return stacks.values.any { it is ExecutionStackView }
}
fun resume(vm: Vm) {
val prevStack = stacks[vm]
if (prevStack is ExecutionStackView) {
stacks[vm] = RunningThreadExecutionStackView(prevStack.vm)
}
}
fun resumeCurrentThread() {
resume(activeVm)
}
fun setActiveThread(selectedStackFrame: XStackFrame?): Boolean {
if (selectedStackFrame !is CallFrameView) return false
var selectedVm: Vm? = null
for ((key, value) in stacks) {
if (value is ExecutionStackView && value.topFrame?.vm == selectedStackFrame.vm) {
selectedVm = key
break
}
}
val selectedVmStack = stacks[selectedVm]
if (selectedVm != null && selectedVmStack is ExecutionStackView) {
activeVm = selectedVm
stacks[selectedVm] = selectedVmStack.copyWithIsCurrent(true)
stacks.keys.forEach {
val stack = stacks[it]
if (it != selectedVm && stack is ExecutionStackView) {
stacks[it] = stack.copyWithIsCurrent(false)
}
}
return stacks[selectedVm] !== selectedVmStack
}
return false
}
}
class RunningThreadExecutionStackView(displayName: String) : XExecutionStack(displayName, AllIcons.Debugger.ThreadRunning) {
class RunningThreadExecutionStackView(vm: Vm) : ScriptExecutionStack(vm, vm.presentableName, AllIcons.Debugger.ThreadRunning) {
override fun computeStackFrames(firstFrameIndex: Int, container: XStackFrameContainer?) {
// add dependency to DebuggerBundle?
container?.errorOccurred("Frames not available for unsuspended thread")
@@ -105,17 +154,33 @@ class RunningThreadExecutionStackView(displayName: String) : XExecutionStack(dis
override fun getTopFrame(): XStackFrame? = null
}
class InactiveAtBreakpointExecutionStackView(displayName: String) : XExecutionStack(displayName, AllIcons.Debugger.ThreadAtBreakpoint) {
class InactiveAtBreakpointExecutionStackView(vm: Vm) : ScriptExecutionStack(vm, vm.presentableName, AllIcons.Debugger.ThreadAtBreakpoint) {
override fun getTopFrame(): XStackFrame? = null
override fun computeStackFrames(firstFrameIndex: Int, container: XStackFrameContainer?) {}
}
abstract class ScriptExecutionStack(val vm: Vm, displayName: String, icon: javax.swing.Icon): XExecutionStack(displayName, icon) {
override fun hashCode(): Int {
return vm.hashCode()
}
override fun equals(other: Any?): Boolean {
return other is ScriptExecutionStack && other.vm == vm
}
}
// TODO should be AllIcons.Debugger.ThreadCurrent, but because of strange logic to add non-equal XExecutionStacks we can't update icon.
private fun getThreadIcon(isCurrent: Boolean) = AllIcons.Debugger.ThreadAtBreakpoint
class ExecutionStackView(val suspendContext: SuspendContext<*>,
internal val viewSupport: DebuggerViewSupport,
private val topFrameScript: Script?,
private val topFrameSourceInfo: SourceInfo? = null,
displayName: String = "") : XExecutionStack(displayName, AllIcons.Debugger.ThreadCurrent) {
displayName: String = "",
isCurrent: Boolean = true)
: ScriptExecutionStack(suspendContext.vm, displayName, getThreadIcon(isCurrent)) {
private var topCallFrameView: CallFrameView? = null
override fun getTopFrame(): CallFrameView? {
@@ -164,6 +229,12 @@ class ExecutionStackView(val suspendContext: SuspendContext<*>,
container.addStackFrames(result, true)
}
}
fun copyWithIsCurrent(isCurrent: Boolean): ExecutionStackView {
if (icon == getThreadIcon(isCurrent)) return this
return ExecutionStackView(suspendContext, viewSupport, topFrameScript, topFrameSourceInfo, displayName, isCurrent)
}
}
private val PREFIX_ATTRIBUTES = SimpleTextAttributes(SimpleTextAttributes.STYLE_ITALIC, UIUtil.getInactiveTextColor())