From 1448c24cf0a9b16c55a977a243b6785464181af0 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Fri, 15 Oct 2021 13:22:24 +0530 Subject: [PATCH 01/17] Refactor server utils (cherry picked from commit 34538baf559b21a1a40dce8a43c1ae6f0f3ad105) --- .../debugadapter/JBallerinaDebugServer.java | 501 +++++++++--------- 1 file changed, 253 insertions(+), 248 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index 51672fedddd6..079baea9117b 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -149,10 +149,10 @@ public class JBallerinaDebugServer implements IDebugProtocolServer { private DebugOutputLogger outputLogger; private final AtomicInteger nextVarReference = new AtomicInteger(); - private final Map stackFramesMap = new ConcurrentHashMap<>(); - private final Map loadedThreadFrames = new ConcurrentHashMap<>(); - private final Map variableToStackFrameMap = new ConcurrentHashMap<>(); - private final Map scopeIdToFrameIdMap = new ConcurrentHashMap<>(); + private final Map stackFrames = new HashMap<>(); + private final Map threadStackTraces = new HashMap<>(); + private final Map scopeIdToFrameIdMap = new HashMap<>(); + private final Map variableToStackFrameMap = new HashMap<>(); private final Map loadedCompoundVariables = new ConcurrentHashMap<>(); @@ -217,7 +217,7 @@ public CompletableFuture initialize(InitializeRequestArguments arg public CompletableFuture setBreakpoints(SetBreakpointsArguments args) { return CompletableFuture.supplyAsync(() -> { BalBreakpoint[] balBreakpoints = Arrays.stream(args.getBreakpoints()) - .map((SourceBreakpoint sourceBreakpoint) -> toBreakpoint(sourceBreakpoint, args.getSource())) + .map((SourceBreakpoint sourceBreakpoint) -> toBalBreakpoint(sourceBreakpoint, args.getSource())) .toArray(BalBreakpoint[]::new); LinkedHashMap breakpointsMap = new LinkedHashMap<>(); @@ -338,15 +338,15 @@ public CompletableFuture stackTrace(StackTraceArguments args StackTraceResponse stackTraceResponse = new StackTraceResponse(); try { activeThread = getAllThreads().get(args.getThreadId()); - if (loadedThreadFrames.containsKey(activeThread.uniqueID())) { - stackTraceResponse.setStackFrames(loadedThreadFrames.get(activeThread.uniqueID())); + if (threadStackTraces.containsKey(activeThread.uniqueID())) { + stackTraceResponse.setStackFrames(threadStackTraces.get(activeThread.uniqueID())); } else { StackFrame[] validFrames = activeThread.frames().stream() .map(this::toDapStackFrame) .filter(JBallerinaDebugServer::isValidFrame) .toArray(StackFrame[]::new); stackTraceResponse.setStackFrames(validFrames); - loadedThreadFrames.put(activeThread.uniqueID(), validFrames); + threadStackTraces.put(activeThread.uniqueID(), validFrames); } return CompletableFuture.completedFuture(stackTraceResponse); } catch (Exception e) { @@ -377,31 +377,27 @@ public CompletableFuture scopes(ScopesArguments args) { @Override public CompletableFuture variables(VariablesArguments args) { + // 1. If frameId is NULL, returns child variables. + // 2. If frameId < 0, returns global variables. + // 3. If frameId >= 0, returns local variables. VariablesResponse variablesResponse = new VariablesResponse(); try { - // 1. If frameId < 0, returns global variables. - // 2. If frameId >= 0, returns local variables. - // 3. If frameId is NULL, returns child variables. Integer frameId = scopeIdToFrameIdMap.get(args.getVariablesReference()); - if (frameId != null && frameId < 0) { - StackFrameProxyImpl stackFrame = stackFramesMap.get(-frameId); - if (stackFrame == null) { - variablesResponse.setVariables(new Variable[0]); - return CompletableFuture.completedFuture(variablesResponse); - } + if (frameId == null) { + variablesResponse.setVariables(computeChildVariables(args)); + return CompletableFuture.completedFuture(variablesResponse); + } - suspendedContext = new SuspendedContext(context, activeThread, stackFrame); - variablesResponse.setVariables(computeGlobalVariables(suspendedContext, args.getVariablesReference())); - } else if (frameId != null) { - StackFrameProxyImpl stackFrame = stackFramesMap.get(frameId); - if (stackFrame == null) { - variablesResponse.setVariables(new Variable[0]); - return CompletableFuture.completedFuture(variablesResponse); - } - suspendedContext = new SuspendedContext(context, activeThread, stackFrame); - variablesResponse.setVariables(computeStackFrameVariables(args)); + StackFrameProxyImpl stackFrame = stackFrames.get(Math.abs(frameId)); + if (stackFrame == null) { + variablesResponse.setVariables(new Variable[0]); + return CompletableFuture.completedFuture(variablesResponse); + } + suspendedContext = new SuspendedContext(context, activeThread, stackFrame); + if (frameId < 0) { + variablesResponse.setVariables(computeGlobalScopeVariables(args)); } else { - variablesResponse.setVariables(computeChildVariables(args)); + variablesResponse.setVariables(computeLocalScopeVariables(args)); } return CompletableFuture.completedFuture(variablesResponse); } catch (Exception e) { @@ -482,29 +478,13 @@ public CompletableFuture evaluate(EvaluateArguments args) { return CompletableFuture.completedFuture(response); } try { - StackFrameProxyImpl frame = stackFramesMap.get(args.getFrameId()); + StackFrameProxyImpl frame = stackFrames.get(args.getFrameId()); SuspendedContext suspendedContext = new SuspendedContext(context, activeThread, frame); EvaluationContext evaluationContext = new EvaluationContext(suspendedContext); evaluator = Objects.requireNonNullElse(evaluator, new DebugExpressionEvaluator(evaluationContext)); evaluator.setExpression(args.getExpression()); - BVariable variable = evaluator.evaluate().getBVariable(); - - if (variable == null) { - return CompletableFuture.completedFuture(response); - } else if (variable instanceof BSimpleVariable) { - variable.getDapVariable().setVariablesReference(0); - } else if (variable instanceof BCompoundVariable) { - int variableReference = nextVarReference.getAndIncrement(); - variable.getDapVariable().setVariablesReference(variableReference); - loadedCompoundVariables.put(variableReference, (BCompoundVariable) variable); - updateVariableToStackFrameMap(args.getFrameId(), variableReference); - } - Variable dapVariable = variable.getDapVariable(); - response.setResult(dapVariable.getValue()); - response.setType(dapVariable.getType()); - response.setIndexedVariables(dapVariable.getIndexedVariables()); - response.setNamedVariables(dapVariable.getNamedVariables()); - response.setVariablesReference(dapVariable.getVariablesReference()); + BVariable evaluationResult = evaluator.evaluate().getBVariable(); + response = constructEvaluateResponse(args, evaluationResult); return CompletableFuture.completedFuture(response); } catch (EvaluationException e) { context.getOutputLogger().sendErrorOutput(e.getMessage()); @@ -571,20 +551,6 @@ public CompletableFuture completions(CompletionsArguments a }); } - private BalBreakpoint toBreakpoint(SourceBreakpoint sourceBreakpoint, Source source) { - BalBreakpoint breakpoint = new BalBreakpoint(source, sourceBreakpoint.getLine()); - breakpoint.setCondition(sourceBreakpoint.getCondition()); - breakpoint.setLogMessage(sourceBreakpoint.getLogMessage()); - return breakpoint; - } - - Thread toDapThread(ThreadReferenceProxyImpl threadReference) { - Thread thread = new Thread(); - thread.setId((int) threadReference.uniqueID()); - thread.setName(threadReference.name()); - return thread; - } - @Override public CompletableFuture disconnect(DisconnectArguments args) { context.setTerminateRequestReceived(true); @@ -747,14 +713,29 @@ private synchronized void updateVariableToStackFrameMap(int parent, int child) { variableToStackFrameMap.put(child, parentRef); } - public StackFrame toDapStackFrame(StackFrameProxyImpl stackFrameProxy) { + /** + * Converts a JDI thread reference to a DAP thread instance. + * + * @param threadReference JDI thread reference + */ + Thread toDapThread(ThreadReferenceProxyImpl threadReference) { + Thread thread = new Thread(); + thread.setId((int) threadReference.uniqueID()); + thread.setName(threadReference.name()); + return thread; + } + + /** + * Coverts a JDI stack frame instance to a DAP stack frame instance. + */ + private StackFrame toDapStackFrame(StackFrameProxyImpl stackFrameProxy) { try { if (!isBalStackFrame(stackFrameProxy.getStackFrame())) { return null; } int referenceId = nextVarReference.getAndIncrement(); - stackFramesMap.put(referenceId, stackFrameProxy); + stackFrames.put(referenceId, stackFrameProxy); BallerinaStackFrame balStackFrame = new BallerinaStackFrame(context, referenceId, stackFrameProxy); return balStackFrame.getAsDAPStackFrame().orElse(null); } catch (JdiProxyException e) { @@ -762,175 +743,11 @@ public StackFrame toDapStackFrame(StackFrameProxyImpl stackFrameProxy) { } } - private Variable[] computeGlobalVariables(SuspendedContext context, int stackFrameReference) { - String classQName = PackageUtils.getQualifiedClassName(context, INIT_CLASS_NAME); - List cls = context.getAttachedVm().classesByName(classQName); - if (cls.size() != 1) { - return new Variable[0]; - } - ArrayList globalVars = new ArrayList<>(); - ReferenceType initClassReference = cls.get(0); - for (Field field : initClassReference.allFields()) { - String fieldName = Utils.decodeIdentifier(field.name()); - if (!field.isPublic() || !field.isStatic() || fieldName.startsWith(GENERATED_VAR_PREFIX)) { - continue; - } - Value fieldValue = initClassReference.getValue(field); - BVariable variable = VariableFactory.getVariable(context, fieldName, fieldValue); - if (variable == null) { - continue; - } - if (variable instanceof BSimpleVariable) { - variable.getDapVariable().setVariablesReference(0); - } else if (variable instanceof BCompoundVariable) { - int variableReference = nextVarReference.getAndIncrement(); - variable.getDapVariable().setVariablesReference(variableReference); - loadedCompoundVariables.put(variableReference, (BCompoundVariable) variable); - updateVariableToStackFrameMap(stackFrameReference, variableReference); - } - globalVars.add(variable.getDapVariable()); - } - return globalVars.toArray(new Variable[0]); - } - - private Variable[] computeStackFrameVariables(VariablesArguments args) throws Exception { - StackFrameProxyImpl stackFrame = suspendedContext.getFrame(); - List variables = new ArrayList<>(); - List localVariableProxies = stackFrame.visibleVariables(); - for (LocalVariableProxyImpl var : localVariableProxies) { - String name = var.name(); - Value value = stackFrame.getValue(var); - // Since the ballerina variables used inside lambda functions are converted into maps during the - // ballerina runtime code generation, such local variables needs to be extracted in a separate manner. - if (VariableUtils.isLambdaParamMap(var)) { - variables.addAll(fetchLocalVariablesFromMap(args, stackFrame, var)); - } else { - Variable dapVariable = getAsDapVariable(name, value, args.getVariablesReference()); - if (dapVariable != null) { - variables.add(dapVariable); - } - } - } - return variables.toArray(new Variable[0]); - } - - /** - * Returns the list of local variables extracted from the given variable map, which contains local variables used - * within lambda functions. - * - * @param args variable args - * @param stackFrame parent stack frame instance - * @param lambdaParamMapVar map variable instance - * @return list of local variables extracted from the given variable map - */ - private List fetchLocalVariablesFromMap(VariablesArguments args, StackFrameProxyImpl stackFrame, - LocalVariableProxyImpl lambdaParamMapVar) { - try { - Value value = stackFrame.getValue(lambdaParamMapVar); - Variable dapVariable = getAsDapVariable("lambdaArgMap", value, args.getVariablesReference()); - if (dapVariable == null || !dapVariable.getType().equals(BVariableType.MAP.getString())) { - return new ArrayList<>(); - } - VariablesArguments childVarRequestArgs = new VariablesArguments(); - childVarRequestArgs.setVariablesReference(dapVariable.getVariablesReference()); - Variable[] childVariables = computeChildVariables(childVarRequestArgs); - return Arrays.asList(childVariables); - } catch (Exception e) { - return new ArrayList<>(); - } - } - - /** - * Coverts a given ballerina runtime value instance into a debugger adapter protocol supported variable instance. - * - * @param name variable name - * @param value runtime value of the variable - * @param stackFrameRef reference ID of the parent stack frame - */ - private Variable getAsDapVariable(String name, Value value, Integer stackFrameRef) { - BVariable variable = VariableFactory.getVariable(suspendedContext, name, value); - if (variable == null) { - return null; - } else if (variable instanceof BSimpleVariable) { - variable.getDapVariable().setVariablesReference(0); - } else if (variable instanceof BCompoundVariable) { - int variableReference = nextVarReference.getAndIncrement(); - variable.getDapVariable().setVariablesReference(variableReference); - loadedCompoundVariables.put(variableReference, (BCompoundVariable) variable); - updateVariableToStackFrameMap(stackFrameRef, variableReference); - } - return variable.getDapVariable(); - } - - private Variable[] computeChildVariables(VariablesArguments args) { - BCompoundVariable parentVar = loadedCompoundVariables.get(args.getVariablesReference()); - Integer stackFrameId = variableToStackFrameMap.get(args.getVariablesReference()); - if (stackFrameId == null) { - return new Variable[0]; - } - - if (parentVar instanceof IndexedCompoundVariable) { - // Handles indexed variables. - int startIndex = (args.getStart() != null) ? args.getStart() : 0; - int count = (args.getCount() != null) ? args.getCount() : 0; - - Either, List> childVars = ((IndexedCompoundVariable) parentVar) - .getIndexedChildVariables(startIndex, count); - if (childVars.isLeft()) { - // Handles map-type indexed variables. - return createVariableArrayFrom(args, childVars.getLeft()); - } else if (childVars.isRight()) { - // Handles list-type indexed variables. - return createVariableArrayFrom(args, childVars.getRight()); - } - return new Variable[0]; - } else if (parentVar instanceof NamedCompoundVariable) { - // Handles named variables. - Map childVars = ((NamedCompoundVariable) parentVar).getNamedChildVariables(); - return createVariableArrayFrom(args, childVars); - } - - return new Variable[0]; - } - - private Variable[] createVariableArrayFrom(VariablesArguments args, Map varMap) { - return varMap.entrySet().stream().map(entry -> { - String name = entry.getKey(); - Value value = entry.getValue(); - BVariable variable = VariableFactory.getVariable(suspendedContext, name, value); - if (variable == null) { - return null; - } else if (variable instanceof BSimpleVariable) { - variable.getDapVariable().setVariablesReference(0); - } else if (variable instanceof BCompoundVariable) { - int variableReference = nextVarReference.getAndIncrement(); - variable.getDapVariable().setVariablesReference(variableReference); - loadedCompoundVariables.put(variableReference, (BCompoundVariable) variable); - updateVariableToStackFrameMap(args.getVariablesReference(), variableReference); - } - return variable.getDapVariable(); - }).filter(Objects::nonNull).toArray(Variable[]::new); - } - - private Variable[] createVariableArrayFrom(VariablesArguments args, List varMap) { - int startIndex = (args.getStart() != null) ? args.getStart() : 0; - AtomicInteger index = new AtomicInteger(startIndex); - - return varMap.stream().map(value -> { - String name = String.format("[%d]", index.getAndIncrement()); - BVariable variable = VariableFactory.getVariable(suspendedContext, name, value); - if (variable == null) { - return null; - } else if (variable instanceof BSimpleVariable) { - variable.getDapVariable().setVariablesReference(0); - } else if (variable instanceof BCompoundVariable) { - int variableReference = nextVarReference.getAndIncrement(); - variable.getDapVariable().setVariablesReference(variableReference); - loadedCompoundVariables.put(variableReference, (BCompoundVariable) variable); - updateVariableToStackFrameMap(args.getVariablesReference(), variableReference); - } - return variable.getDapVariable(); - }).filter(Objects::nonNull).toArray(Variable[]::new); + private BalBreakpoint toBalBreakpoint(SourceBreakpoint sourceBreakpoint, Source source) { + BalBreakpoint breakpoint = new BalBreakpoint(source, sourceBreakpoint.getLine()); + breakpoint.setCondition(sourceBreakpoint.getCondition()); + breakpoint.setLogMessage(sourceBreakpoint.getLogMessage()); + return breakpoint; } /** @@ -1019,19 +836,7 @@ static boolean isBalStackFrame(com.sun.jdi.StackFrame frame) { * @return true if its a valid ballerina frame */ static boolean isValidFrame(StackFrame stackFrame) { - return stackFrame != null && stackFrame.getSource() != null && stackFrame.getLine() > 0 - && !isCompilerGeneratedFrame(stackFrame); - } - - /** - * Validates whether the provided stack frame is a compiler generated one during the codegen phase. - * - * @param stackFrame stack frame instance - * @return true if the provided stack frame is a compiler generated one during the codegen phase - */ - private static boolean isCompilerGeneratedFrame(StackFrame stackFrame) { - String frameName = stackFrame.getName(); - return frameName != null && frameName.startsWith("$") && frameName.endsWith("$"); + return stackFrame != null && stackFrame.getSource() != null && stackFrame.getLine() > 0; } /** @@ -1087,7 +892,7 @@ private void startListeningToProgramOutput() { LOGGER.error(e.getMessage()); outputLogger.sendDebugServerOutput(String.format("Failed to attach to the target VM, address: '%s:%s'.", host, portName)); - terminateDebugServer(context.getDebuggeeVM() != null, false); + terminateDebugServer(context.getDebuggeeVM() != null, true); } }); } @@ -1118,6 +923,206 @@ private void prepareFor(DebugInstruction instruction) { context.setLastInstruction(instruction); } + private Variable[] computeGlobalScopeVariables(VariablesArguments requestArgs) { + int stackFrameReference = requestArgs.getVariablesReference(); + String classQName = PackageUtils.getQualifiedClassName(suspendedContext, INIT_CLASS_NAME); + List cls = suspendedContext.getAttachedVm().classesByName(classQName); + if (cls.size() != 1) { + return new Variable[0]; + } + ArrayList globalVars = new ArrayList<>(); + ReferenceType initClassReference = cls.get(0); + for (Field field : initClassReference.allFields()) { + String fieldName = Utils.decodeIdentifier(field.name()); + if (!field.isPublic() || !field.isStatic() || fieldName.startsWith(GENERATED_VAR_PREFIX)) { + continue; + } + Value fieldValue = initClassReference.getValue(field); + BVariable variable = VariableFactory.getVariable(suspendedContext, fieldName, fieldValue); + if (variable == null) { + continue; + } + if (variable instanceof BSimpleVariable) { + variable.getDapVariable().setVariablesReference(0); + } else if (variable instanceof BCompoundVariable) { + int variableReference = nextVarReference.getAndIncrement(); + variable.getDapVariable().setVariablesReference(variableReference); + loadedCompoundVariables.put(variableReference, (BCompoundVariable) variable); + updateVariableToStackFrameMap(stackFrameReference, variableReference); + } + globalVars.add(variable.getDapVariable()); + } + return globalVars.toArray(new Variable[0]); + } + + private Variable[] computeLocalScopeVariables(VariablesArguments args) throws Exception { + StackFrameProxyImpl stackFrame = suspendedContext.getFrame(); + List variables = new ArrayList<>(); + List localVariableProxies = stackFrame.visibleVariables(); + for (LocalVariableProxyImpl var : localVariableProxies) { + String name = var.name(); + Value value = stackFrame.getValue(var); + // Since the ballerina variables used inside lambda functions are converted into maps during the + // ballerina runtime code generation, such local variables needs to be extracted in a separate manner. + if (VariableUtils.isLambdaParamMap(var)) { + variables.addAll(fetchLocalVariablesFromMap(args, stackFrame, var)); + } else { + Variable dapVariable = getAsDapVariable(name, value, args.getVariablesReference()); + if (dapVariable != null) { + variables.add(dapVariable); + } + } + } + return variables.toArray(new Variable[0]); + } + + /** + * Returns the list of local variables extracted from the given variable map, which contains local variables used + * within lambda functions. + * + * @param args variable args + * @param stackFrame parent stack frame instance + * @param lambdaParamMapVar map variable instance + * @return list of local variables extracted from the given variable map + */ + private List fetchLocalVariablesFromMap(VariablesArguments args, StackFrameProxyImpl stackFrame, + LocalVariableProxyImpl lambdaParamMapVar) { + try { + Value value = stackFrame.getValue(lambdaParamMapVar); + Variable dapVariable = getAsDapVariable("lambdaArgMap", value, args.getVariablesReference()); + if (dapVariable == null || !dapVariable.getType().equals(BVariableType.MAP.getString())) { + return new ArrayList<>(); + } + VariablesArguments childVarRequestArgs = new VariablesArguments(); + childVarRequestArgs.setVariablesReference(dapVariable.getVariablesReference()); + Variable[] childVariables = computeChildVariables(childVarRequestArgs); + return Arrays.asList(childVariables); + } catch (Exception e) { + return new ArrayList<>(); + } + } + + /** + * Coverts a given ballerina runtime value instance into a debugger adapter protocol supported variable instance. + * + * @param name variable name + * @param value runtime value of the variable + * @param stackFrameRef reference ID of the parent stack frame + */ + private Variable getAsDapVariable(String name, Value value, Integer stackFrameRef) { + BVariable variable = VariableFactory.getVariable(suspendedContext, name, value); + if (variable == null) { + return null; + } else if (variable instanceof BSimpleVariable) { + variable.getDapVariable().setVariablesReference(0); + } else if (variable instanceof BCompoundVariable) { + int variableReference = nextVarReference.getAndIncrement(); + variable.getDapVariable().setVariablesReference(variableReference); + loadedCompoundVariables.put(variableReference, (BCompoundVariable) variable); + updateVariableToStackFrameMap(stackFrameRef, variableReference); + } + return variable.getDapVariable(); + } + + private Variable[] computeChildVariables(VariablesArguments args) { + BCompoundVariable parentVar = loadedCompoundVariables.get(args.getVariablesReference()); + Integer stackFrameId = variableToStackFrameMap.get(args.getVariablesReference()); + if (stackFrameId == null) { + return new Variable[0]; + } + + if (parentVar instanceof IndexedCompoundVariable) { + // Handles indexed variables. + int startIndex = (args.getStart() != null) ? args.getStart() : 0; + int count = (args.getCount() != null) ? args.getCount() : 0; + + Either, List> childVars = ((IndexedCompoundVariable) parentVar) + .getIndexedChildVariables(startIndex, count); + if (childVars.isLeft()) { + // Handles map-type indexed variables. + return createVariableArrayFrom(args, childVars.getLeft()); + } else if (childVars.isRight()) { + // Handles list-type indexed variables. + return createVariableArrayFrom(args, childVars.getRight()); + } + return new Variable[0]; + } else if (parentVar instanceof NamedCompoundVariable) { + // Handles named variables. + Map childVars = ((NamedCompoundVariable) parentVar).getNamedChildVariables(); + return createVariableArrayFrom(args, childVars); + } + + return new Variable[0]; + } + + private Variable[] createVariableArrayFrom(VariablesArguments args, Map varMap) { + return varMap.entrySet().stream().map(entry -> { + String name = entry.getKey(); + Value value = entry.getValue(); + BVariable variable = VariableFactory.getVariable(suspendedContext, name, value); + if (variable == null) { + return null; + } else if (variable instanceof BSimpleVariable) { + variable.getDapVariable().setVariablesReference(0); + } else if (variable instanceof BCompoundVariable) { + int variableReference = nextVarReference.getAndIncrement(); + variable.getDapVariable().setVariablesReference(variableReference); + loadedCompoundVariables.put(variableReference, (BCompoundVariable) variable); + updateVariableToStackFrameMap(args.getVariablesReference(), variableReference); + } + return variable.getDapVariable(); + }).filter(Objects::nonNull).toArray(Variable[]::new); + } + + private Variable[] createVariableArrayFrom(VariablesArguments args, List varMap) { + int startIndex = (args.getStart() != null) ? args.getStart() : 0; + AtomicInteger index = new AtomicInteger(startIndex); + + return varMap.stream().map(value -> { + String name = String.format("[%d]", index.getAndIncrement()); + BVariable variable = VariableFactory.getVariable(suspendedContext, name, value); + if (variable == null) { + return null; + } else if (variable instanceof BSimpleVariable) { + variable.getDapVariable().setVariablesReference(0); + } else if (variable instanceof BCompoundVariable) { + int variableReference = nextVarReference.getAndIncrement(); + variable.getDapVariable().setVariablesReference(variableReference); + loadedCompoundVariables.put(variableReference, (BCompoundVariable) variable); + updateVariableToStackFrameMap(args.getVariablesReference(), variableReference); + } + return variable.getDapVariable(); + }).filter(Objects::nonNull).toArray(Variable[]::new); + } + + /** + * Creates a {@link EvaluateResponse} from the given evaluation result variable. + * + * @param args evaluation arguments. + * @param evaluationResult evaluation result variable + */ + private EvaluateResponse constructEvaluateResponse(EvaluateArguments args, BVariable evaluationResult) { + EvaluateResponse response = new EvaluateResponse(); + if (evaluationResult == null) { + return response; + } else if (evaluationResult instanceof BSimpleVariable) { + evaluationResult.getDapVariable().setVariablesReference(0); + } else if (evaluationResult instanceof BCompoundVariable) { + int variableReference = nextVarReference.getAndIncrement(); + evaluationResult.getDapVariable().setVariablesReference(variableReference); + loadedCompoundVariables.put(variableReference, (BCompoundVariable) evaluationResult); + updateVariableToStackFrameMap(args.getFrameId(), variableReference); + } + + Variable dapVariable = evaluationResult.getDapVariable(); + response.setResult(dapVariable.getValue()); + response.setType(dapVariable.getType()); + response.setIndexedVariables(dapVariable.getIndexedVariables()); + response.setNamedVariables(dapVariable.getNamedVariables()); + response.setVariablesReference(dapVariable.getVariablesReference()); + return response; + } + /** * Clears state information. */ @@ -1125,11 +1130,11 @@ private void clearState() { suspendedContext = null; evaluator = null; activeThread = null; - stackFramesMap.clear(); + stackFrames.clear(); loadedCompoundVariables.clear(); variableToStackFrameMap.clear(); scopeIdToFrameIdMap.clear(); - loadedThreadFrames.clear(); + threadStackTraces.clear(); nextVarReference.set(1); } } From 9ca84ce4ce3bc84226ffa746c0fc8e7121ee1338 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Fri, 15 Oct 2021 16:08:14 +0530 Subject: [PATCH 02/17] Add asynchronous evaluation request processing (cherry picked from commit 36d66174ac4cc15e4d44542ae8a872a34981897e) --- .../debugadapter/JBallerinaDebugServer.java | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index 079baea9117b..67a9542950b5 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -142,7 +142,6 @@ public class JBallerinaDebugServer implements IDebugProtocolServer { private ClientConfigHolder clientConfigHolder; private DebugExecutionManager executionManager; private JDIEventProcessor eventProcessor; - private DebugExpressionEvaluator evaluator; private final ExecutionContext context; private ThreadReferenceProxyImpl activeThread; private SuspendedContext suspendedContext; @@ -153,7 +152,6 @@ public class JBallerinaDebugServer implements IDebugProtocolServer { private final Map threadStackTraces = new HashMap<>(); private final Map scopeIdToFrameIdMap = new HashMap<>(); private final Map variableToStackFrameMap = new HashMap<>(); - private final Map loadedCompoundVariables = new ConcurrentHashMap<>(); private static final Logger LOGGER = LoggerFactory.getLogger(JBallerinaDebugServer.class); @@ -204,12 +202,12 @@ public CompletableFuture initialize(InitializeRequestArguments arg capabilities.setSupportsExceptionFilterOptions(false); capabilities.setSupportsExceptionInfoRequest(false); - context.setClient(client); context.setSupportsRunInTerminalRequest(args.getSupportsRunInTerminalRequest() != null && args.getSupportsRunInTerminalRequest()); eventProcessor = new JDIEventProcessor(context); + outputLogger = new DebugOutputLogger(client); + context.setClient(client); client.initialized(); - this.outputLogger = new DebugOutputLogger(client); return CompletableFuture.completedFuture(capabilities); } @@ -453,13 +451,12 @@ public CompletableFuture setExceptionBreakpoints(SetExceptionBreakpointsAr @Override public CompletableFuture evaluate(EvaluateArguments args) { - EvaluateResponse response = new EvaluateResponse(); // If the execution manager is not active, it implies that the debug server is still not connected to the // remote VM and therefore the request should be rejected immediately. if (executionManager == null || !executionManager.isActive()) { context.getOutputLogger().sendErrorOutput(EvaluationExceptionKind.PREFIX + "Debug server is not " + "connected to any program VM."); - return CompletableFuture.completedFuture(response); + return CompletableFuture.completedFuture(new EvaluateResponse()); } // If the frame ID is missing in the client args, it implies that remote program is still running and therefore // the request should be rejected immediately. @@ -474,25 +471,28 @@ public CompletableFuture evaluate(EvaluateArguments args) { // If evaluate arguments context is equal to `variables`, then respond with expression as it is without // evaluation process. if (args.getContext() != null && args.getContext().equals(EVAL_ARGS_CONTEXT_VARIABLES)) { + EvaluateResponse response = new EvaluateResponse(); response.setResult(args.getExpression()); return CompletableFuture.completedFuture(response); } - try { - StackFrameProxyImpl frame = stackFrames.get(args.getFrameId()); - SuspendedContext suspendedContext = new SuspendedContext(context, activeThread, frame); - EvaluationContext evaluationContext = new EvaluationContext(suspendedContext); - evaluator = Objects.requireNonNullElse(evaluator, new DebugExpressionEvaluator(evaluationContext)); - evaluator.setExpression(args.getExpression()); - BVariable evaluationResult = evaluator.evaluate().getBVariable(); - response = constructEvaluateResponse(args, evaluationResult); - return CompletableFuture.completedFuture(response); - } catch (EvaluationException e) { - context.getOutputLogger().sendErrorOutput(e.getMessage()); - return CompletableFuture.completedFuture(response); - } catch (Exception e) { - context.getOutputLogger().sendErrorOutput(EvaluationExceptionKind.PREFIX + "internal error"); - return CompletableFuture.completedFuture(response); - } + + return CompletableFuture.supplyAsync(() -> { + try { + StackFrameProxyImpl frame = stackFrames.get(args.getFrameId()); + SuspendedContext suspendedContext = new SuspendedContext(context, activeThread, frame); + EvaluationContext evaluationContext = new EvaluationContext(suspendedContext); + DebugExpressionEvaluator evaluator = new DebugExpressionEvaluator(evaluationContext); + evaluator.setExpression(args.getExpression()); + BVariable evaluationResult = evaluator.evaluate().getBVariable(); + return constructEvaluateResponse(args, evaluationResult); + } catch (EvaluationException e) { + context.getOutputLogger().sendErrorOutput(e.getMessage()); + return new EvaluateResponse(); + } catch (Exception e) { + context.getOutputLogger().sendErrorOutput(EvaluationExceptionKind.PREFIX + "internal error"); + return new EvaluateResponse(); + } + }); } @Override @@ -1128,7 +1128,6 @@ private EvaluateResponse constructEvaluateResponse(EvaluateArguments args, BVari */ private void clearState() { suspendedContext = null; - evaluator = null; activeThread = null; stackFrames.clear(); loadedCompoundVariables.clear(); From 59d3dca6a9a4d1857b1bfebeb6007bd40f9b7301 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Sat, 16 Oct 2021 09:51:07 +0530 Subject: [PATCH 03/17] Improve variable loading to fetch values parallelly (cherry picked from commit 1e0d97884828455984a16f83429061b34a99639d) --- .../debugadapter/JBallerinaDebugServer.java | 143 +++++++++++------- 1 file changed, 85 insertions(+), 58 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index 67a9542950b5..40853c775577 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -150,8 +150,8 @@ public class JBallerinaDebugServer implements IDebugProtocolServer { private final AtomicInteger nextVarReference = new AtomicInteger(); private final Map stackFrames = new HashMap<>(); private final Map threadStackTraces = new HashMap<>(); - private final Map scopeIdToFrameIdMap = new HashMap<>(); - private final Map variableToStackFrameMap = new HashMap<>(); + private final Map scopeIdToFrameIds = new HashMap<>(); + private final Map variableToStackFrames = new ConcurrentHashMap<>(); private final Map loadedCompoundVariables = new ConcurrentHashMap<>(); private static final Logger LOGGER = LoggerFactory.getLogger(JBallerinaDebugServer.class); @@ -359,12 +359,12 @@ public CompletableFuture scopes(ScopesArguments args) { // Creates local variable scope. Scope localScope = new Scope(); localScope.setName(SCOPE_NAME_LOCAL); - scopeIdToFrameIdMap.put(nextVarReference.get(), args.getFrameId()); + scopeIdToFrameIds.put(nextVarReference.get(), args.getFrameId()); localScope.setVariablesReference(nextVarReference.getAndIncrement()); // Creates global variable scope. Scope globalScope = new Scope(); globalScope.setName(SCOPE_NAME_GLOBAL); - scopeIdToFrameIdMap.put(nextVarReference.get(), -args.getFrameId()); + scopeIdToFrameIds.put(nextVarReference.get(), -args.getFrameId()); globalScope.setVariablesReference(nextVarReference.getAndIncrement()); Scope[] scopes = {localScope, globalScope}; @@ -380,7 +380,7 @@ public CompletableFuture variables(VariablesArguments args) { // 3. If frameId >= 0, returns local variables. VariablesResponse variablesResponse = new VariablesResponse(); try { - Integer frameId = scopeIdToFrameIdMap.get(args.getVariablesReference()); + Integer frameId = scopeIdToFrameIds.get(args.getVariablesReference()); if (frameId == null) { variablesResponse.setVariables(computeChildVariables(args)); return CompletableFuture.completedFuture(variablesResponse); @@ -701,16 +701,16 @@ public void connect(IDebugProtocolClient client) { } private synchronized void updateVariableToStackFrameMap(int parent, int child) { - if (!variableToStackFrameMap.containsKey(parent)) { - variableToStackFrameMap.put(child, parent); + if (!variableToStackFrames.containsKey(parent)) { + variableToStackFrames.put(child, parent); return; } Integer parentRef; do { - parentRef = variableToStackFrameMap.get(parent); - } while (variableToStackFrameMap.containsKey(parentRef)); - variableToStackFrameMap.put(child, parentRef); + parentRef = variableToStackFrames.get(parent); + } while (variableToStackFrames.containsKey(parentRef)); + variableToStackFrames.put(child, parentRef); } /** @@ -923,14 +923,14 @@ private void prepareFor(DebugInstruction instruction) { context.setLastInstruction(instruction); } - private Variable[] computeGlobalScopeVariables(VariablesArguments requestArgs) { + private Variable[] computeGlobalScopeVariables(VariablesArguments requestArgs) throws Exception { int stackFrameReference = requestArgs.getVariablesReference(); String classQName = PackageUtils.getQualifiedClassName(suspendedContext, INIT_CLASS_NAME); List cls = suspendedContext.getAttachedVm().classesByName(classQName); if (cls.size() != 1) { return new Variable[0]; } - ArrayList globalVars = new ArrayList<>(); + List> scheduledVariables = new ArrayList<>(); ReferenceType initClassReference = cls.get(0); for (Field field : initClassReference.allFields()) { String fieldName = Utils.decodeIdentifier(field.name()); @@ -938,26 +938,25 @@ private Variable[] computeGlobalScopeVariables(VariablesArguments requestArgs) { continue; } Value fieldValue = initClassReference.getValue(field); - BVariable variable = VariableFactory.getVariable(suspendedContext, fieldName, fieldValue); - if (variable == null) { - continue; - } - if (variable instanceof BSimpleVariable) { - variable.getDapVariable().setVariablesReference(0); - } else if (variable instanceof BCompoundVariable) { - int variableReference = nextVarReference.getAndIncrement(); - variable.getDapVariable().setVariablesReference(variableReference); - loadedCompoundVariables.put(variableReference, (BCompoundVariable) variable); - updateVariableToStackFrameMap(stackFrameReference, variableReference); - } - globalVars.add(variable.getDapVariable()); + scheduledVariables.add(computeVariableAsync(fieldName, fieldValue, stackFrameReference)); } - return globalVars.toArray(new Variable[0]); + + return scheduledVariables.stream() + .map(varFuture -> { + try { + return varFuture.get(1000, TimeUnit.MILLISECONDS); + } catch (Exception ignored) { + return null; + } + }) + .filter(Objects::nonNull) + .toArray(Variable[]::new); } private Variable[] computeLocalScopeVariables(VariablesArguments args) throws Exception { StackFrameProxyImpl stackFrame = suspendedContext.getFrame(); - List variables = new ArrayList<>(); + List> scheduledVariables = new ArrayList<>(); + List> scheduledLambdaMapVariables = new ArrayList<>(); List localVariableProxies = stackFrame.visibleVariables(); for (LocalVariableProxyImpl var : localVariableProxies) { String name = var.name(); @@ -965,15 +964,34 @@ private Variable[] computeLocalScopeVariables(VariablesArguments args) throws Ex // Since the ballerina variables used inside lambda functions are converted into maps during the // ballerina runtime code generation, such local variables needs to be extracted in a separate manner. if (VariableUtils.isLambdaParamMap(var)) { - variables.addAll(fetchLocalVariablesFromMap(args, stackFrame, var)); + scheduledLambdaMapVariables.add(fetchLocalVariablesFromMap(args, stackFrame, var)); } else { - Variable dapVariable = getAsDapVariable(name, value, args.getVariablesReference()); - if (dapVariable != null) { - variables.add(dapVariable); - } + CompletableFuture dapVariable = computeVariableAsync(name, value, args.getVariablesReference()); + scheduledVariables.add(dapVariable); } } - return variables.toArray(new Variable[0]); + + List resolvedVariables = new ArrayList<>(); + scheduledVariables.forEach(varFuture -> { + try { + Variable variable = varFuture.get(1000, TimeUnit.MILLISECONDS); + if (variable != null) { + resolvedVariables.add(variable); + } + } catch (Exception ignored) { + } + }); + + scheduledLambdaMapVariables.forEach(varFuture -> { + try { + Variable[] variables = varFuture.get(1000, TimeUnit.MILLISECONDS); + if (variables != null) { + resolvedVariables.addAll(Arrays.asList(variables)); + } + } catch (Exception ignored) { + } + }); + return resolvedVariables.toArray(new Variable[0]); } /** @@ -985,52 +1003,61 @@ private Variable[] computeLocalScopeVariables(VariablesArguments args) throws Ex * @param lambdaParamMapVar map variable instance * @return list of local variables extracted from the given variable map */ - private List fetchLocalVariablesFromMap(VariablesArguments args, StackFrameProxyImpl stackFrame, - LocalVariableProxyImpl lambdaParamMapVar) { + private CompletableFuture fetchLocalVariablesFromMap(VariablesArguments args, StackFrameProxyImpl + stackFrame, LocalVariableProxyImpl lambdaParamMapVar) { try { Value value = stackFrame.getValue(lambdaParamMapVar); - Variable dapVariable = getAsDapVariable("lambdaArgMap", value, args.getVariablesReference()); + CompletableFuture scheduledVariable = computeVariableAsync("lambdaArgMap", value, + args.getVariablesReference()); + Variable dapVariable = scheduledVariable.get(); if (dapVariable == null || !dapVariable.getType().equals(BVariableType.MAP.getString())) { - return new ArrayList<>(); + return new CompletableFuture<>(); } VariablesArguments childVarRequestArgs = new VariablesArguments(); childVarRequestArgs.setVariablesReference(dapVariable.getVariablesReference()); - Variable[] childVariables = computeChildVariables(childVarRequestArgs); - return Arrays.asList(childVariables); + return computeChildVariablesAsync(childVarRequestArgs); } catch (Exception e) { - return new ArrayList<>(); + return new CompletableFuture<>(); } } /** - * Coverts a given ballerina runtime value instance into a debugger adapter protocol supported variable instance. + * Asynchronously computes a debugger adapter protocol supported variable instance from Coverts a given ballerina + * runtime value instance. * * @param name variable name * @param value runtime value of the variable * @param stackFrameRef reference ID of the parent stack frame */ - private Variable getAsDapVariable(String name, Value value, Integer stackFrameRef) { - BVariable variable = VariableFactory.getVariable(suspendedContext, name, value); - if (variable == null) { - return null; - } else if (variable instanceof BSimpleVariable) { - variable.getDapVariable().setVariablesReference(0); - } else if (variable instanceof BCompoundVariable) { - int variableReference = nextVarReference.getAndIncrement(); - variable.getDapVariable().setVariablesReference(variableReference); - loadedCompoundVariables.put(variableReference, (BCompoundVariable) variable); - updateVariableToStackFrameMap(stackFrameRef, variableReference); - } - return variable.getDapVariable(); + private CompletableFuture computeVariableAsync(String name, Value value, Integer stackFrameRef) { + return CompletableFuture.supplyAsync(() -> { + BVariable variable = VariableFactory.getVariable(suspendedContext, name, value); + if (variable == null) { + return null; + } + if (variable instanceof BSimpleVariable) { + variable.getDapVariable().setVariablesReference(0); + } else if (variable instanceof BCompoundVariable) { + int variableReference = nextVarReference.getAndIncrement(); + variable.getDapVariable().setVariablesReference(variableReference); + loadedCompoundVariables.put(variableReference, (BCompoundVariable) variable); + updateVariableToStackFrameMap(stackFrameRef, variableReference); + } + return variable.getDapVariable(); + }); + } + + private CompletableFuture computeChildVariablesAsync(VariablesArguments args) { + return CompletableFuture.supplyAsync(() -> computeChildVariables(args)); } private Variable[] computeChildVariables(VariablesArguments args) { + // Todo: Add support for parallel child variable computations. BCompoundVariable parentVar = loadedCompoundVariables.get(args.getVariablesReference()); - Integer stackFrameId = variableToStackFrameMap.get(args.getVariablesReference()); + Integer stackFrameId = variableToStackFrames.get(args.getVariablesReference()); if (stackFrameId == null) { return new Variable[0]; } - if (parentVar instanceof IndexedCompoundVariable) { // Handles indexed variables. int startIndex = (args.getStart() != null) ? args.getStart() : 0; @@ -1131,8 +1158,8 @@ private void clearState() { activeThread = null; stackFrames.clear(); loadedCompoundVariables.clear(); - variableToStackFrameMap.clear(); - scopeIdToFrameIdMap.clear(); + variableToStackFrames.clear(); + scopeIdToFrameIds.clear(); threadStackTraces.clear(); nextVarReference.set(1); } From 50dccf652bf846f5cc6083ce0f6f33a0e9f5c7cd Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Mon, 18 Oct 2021 16:54:17 +0530 Subject: [PATCH 04/17] Handle potential IncompatibleThreadState exceptions (cherry picked from commit 5dc21fb893adba7d60e9df0e07275d3e4b10babd) --- .../debugadapter/variable/VariableUtils.java | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java index 2b8921cac828..4d916bc6d2b0 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java @@ -18,6 +18,7 @@ import com.sun.jdi.ClassType; import com.sun.jdi.Field; +import com.sun.jdi.IncompatibleThreadStateException; import com.sun.jdi.Method; import com.sun.jdi.ObjectReference; import com.sun.jdi.Value; @@ -32,6 +33,7 @@ import java.util.Map; import java.util.Optional; +import static com.sun.jdi.ThreadReference.THREAD_STATUS_RUNNING; import static org.ballerinalang.debugadapter.evaluation.EvaluationException.createEvaluationException; /** @@ -173,14 +175,29 @@ public static String getStringValue(SuspendedContext context, Value jvmObject) { return UNKNOWN_VALUE; } Optional method = VariableUtils.getMethod(jvmObject, METHOD_STRINGVALUE); - if (method.isPresent()) { - Value stringValue = ((ObjectReference) jvmObject).invokeMethod(context.getOwningThread() - .getThreadReference(), method.get(), Collections.singletonList(null), - ObjectReference.INVOKE_SINGLE_THREADED); - return VariableUtils.getStringFrom(stringValue); + if (method.isEmpty()) { + return UNKNOWN_VALUE; + } + + // Since the remote VM's active thread will be accessed from multiple debugger threads, there's a chance + // for 'IncompatibleThreadStateException's. Therefore debugger might need to retry few times until the + // thread gets released. (current wait time => ~100ms) + for (int attempt = 0; attempt < 10; attempt++) { + try { + Value stringValue = ((ObjectReference) jvmObject).invokeMethod(context.getOwningThread() + .getThreadReference(), method.get(), Collections.singletonList(null), + ObjectReference.INVOKE_SINGLE_THREADED); + return VariableUtils.getStringFrom(stringValue); + } catch (IncompatibleThreadStateException e) { + try { + Thread.sleep(10); + } catch (InterruptedException ignored) { + } + } } + return UNKNOWN_VALUE; - } catch (Exception ignored) { + } catch (Exception e) { return UNKNOWN_VALUE; } } From a38769197e57830f3417e4ee011d9ddd2d81f62b Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Tue, 19 Oct 2021 09:51:07 +0530 Subject: [PATCH 05/17] Add review suggestions and minor fixes (cherry picked from commit d3eb84721e2347b630d3e7a0f621343b6c73308e) --- .../debugadapter/JBallerinaDebugServer.java | 3 +- .../debugadapter/variable/VariableUtils.java | 49 +++++++++++++------ .../debugadapter/variable/types/BFuture.java | 7 +-- .../debugadapter/variable/types/BMap.java | 13 ++--- .../debugadapter/variable/types/BTable.java | 15 ++---- 5 files changed, 48 insertions(+), 39 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index 40853c775577..f414b4974ffb 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -717,6 +717,7 @@ private synchronized void updateVariableToStackFrameMap(int parent, int child) { * Converts a JDI thread reference to a DAP thread instance. * * @param threadReference JDI thread reference + * @return the corresponding DAP thread instance */ Thread toDapThread(ThreadReferenceProxyImpl threadReference) { Thread thread = new Thread(); @@ -923,7 +924,7 @@ private void prepareFor(DebugInstruction instruction) { context.setLastInstruction(instruction); } - private Variable[] computeGlobalScopeVariables(VariablesArguments requestArgs) throws Exception { + private Variable[] computeGlobalScopeVariables(VariablesArguments requestArgs) { int stackFrameReference = requestArgs.getVariablesReference(); String classQName = PackageUtils.getQualifiedClassName(suspendedContext, INIT_CLASS_NAME); List cls = suspendedContext.getAttachedVm().classesByName(classQName); diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java index 4d916bc6d2b0..56b864414432 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java @@ -33,7 +33,6 @@ import java.util.Map; import java.util.Optional; -import static com.sun.jdi.ThreadReference.THREAD_STATUS_RUNNING; import static org.ballerinalang.debugadapter.evaluation.EvaluationException.createEvaluationException; /** @@ -170,24 +169,45 @@ public static String getStringFrom(Value stringValue) { * @return result of the method invocation as a string. */ public static String getStringValue(SuspendedContext context, Value jvmObject) { + try { + Value result = invokeRemoteVMMethod(context, jvmObject, METHOD_STRINGVALUE, + Collections.singletonList(null)); + return getStringFrom(result); + } catch (DebugVariableException e) { + return UNKNOWN_VALUE; + } + } + + public static Value invokeRemoteVMMethod(SuspendedContext context, Value jvmObject, String methodName, + List arguments) throws DebugVariableException { + if (!(jvmObject instanceof ObjectReference)) { + throw new DebugVariableException("Failed to invoke remote VM method."); + } + Optional method = VariableUtils.getMethod(jvmObject, methodName); + if (method.isEmpty()) { + throw new DebugVariableException("Failed to invoke remote VM method."); + } + + return invokeRemoteVMMethod(context, jvmObject, method.get(), arguments); + } + + public static Value invokeRemoteVMMethod(SuspendedContext context, Value jvmObject, Method method, + List arguments) throws DebugVariableException { try { if (!(jvmObject instanceof ObjectReference)) { - return UNKNOWN_VALUE; - } - Optional method = VariableUtils.getMethod(jvmObject, METHOD_STRINGVALUE); - if (method.isEmpty()) { - return UNKNOWN_VALUE; + throw new DebugVariableException("Failed to invoke remote VM method."); } + if (arguments == null) { + arguments = Collections.emptyList(); + } // Since the remote VM's active thread will be accessed from multiple debugger threads, there's a chance // for 'IncompatibleThreadStateException's. Therefore debugger might need to retry few times until the - // thread gets released. (current wait time => ~100ms) - for (int attempt = 0; attempt < 10; attempt++) { + // thread gets released. (current wait time => ~1000ms) + for (int attempt = 0; attempt < 100; attempt++) { try { - Value stringValue = ((ObjectReference) jvmObject).invokeMethod(context.getOwningThread() - .getThreadReference(), method.get(), Collections.singletonList(null), - ObjectReference.INVOKE_SINGLE_THREADED); - return VariableUtils.getStringFrom(stringValue); + return ((ObjectReference) jvmObject).invokeMethod(context.getOwningThread() + .getThreadReference(), method, arguments, ObjectReference.INVOKE_SINGLE_THREADED); } catch (IncompatibleThreadStateException e) { try { Thread.sleep(10); @@ -195,10 +215,9 @@ public static String getStringValue(SuspendedContext context, Value jvmObject) { } } } - - return UNKNOWN_VALUE; + throw new DebugVariableException("Failed to invoke remote VM method as the invocation thread is busy"); } catch (Exception e) { - return UNKNOWN_VALUE; + throw new DebugVariableException("Failed to invoke remote VM method due to an internal error"); } } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BFuture.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BFuture.java index 91d1afc7f8ae..727588bd4183 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BFuture.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BFuture.java @@ -17,14 +17,12 @@ package org.ballerinalang.debugadapter.variable.types; import com.sun.jdi.Method; -import com.sun.jdi.ObjectReference; import com.sun.jdi.Value; import org.ballerinalang.debugadapter.SuspendedContext; import org.ballerinalang.debugadapter.variable.BVariableType; import org.ballerinalang.debugadapter.variable.NamedCompoundVariable; import org.ballerinalang.debugadapter.variable.VariableUtils; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; @@ -68,9 +66,8 @@ public Map computeChildVariables() { // Invokes "getLocalizedMessage()" method of the panic object. Optional method = VariableUtils.getMethod(panic.get(), METHOD_LOCALIZEDMESSAGE); if (method.isPresent()) { - Value stringValue = ((ObjectReference) panic.get()).invokeMethod(getContext().getOwningThread() - .getThreadReference(), method.get(), new ArrayList<>(), - ObjectReference.INVOKE_SINGLE_THREADED); + Value stringValue = VariableUtils.invokeRemoteVMMethod(context, panic.get(), + METHOD_LOCALIZEDMESSAGE, null); childVarMap.put(FIELD_PANIC, stringValue); } } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BMap.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BMap.java index 9ecc8610fdbb..34653bc3ae5b 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BMap.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BMap.java @@ -19,7 +19,6 @@ import com.sun.jdi.ArrayReference; import com.sun.jdi.IntegerValue; import com.sun.jdi.Method; -import com.sun.jdi.ObjectReference; import com.sun.jdi.Value; import org.ballerinalang.debugadapter.SuspendedContext; import org.ballerinalang.debugadapter.variable.BVariableType; @@ -113,12 +112,12 @@ private Map getEntries(int startIndex, int count) { private Value getValueFor(Value key) { try { - Optional getMethod = VariableUtils.getMethod(jvmValue, METHOD_GET); - if (getMethod.isEmpty()) { + Optional getValueMethod = VariableUtils.getMethod(jvmValue, METHOD_GET); + if (getValueMethod.isEmpty()) { return null; } - return ((ObjectReference) jvmValue).invokeMethod(context.getOwningThread().getThreadReference(), - getMethod.get(), Collections.singletonList(key), ObjectReference.INVOKE_SINGLE_THREADED); + return VariableUtils.invokeRemoteVMMethod(context, jvmValue, getValueMethod.get(), + Collections.singletonList(key)); } catch (Exception ignored) { return null; } @@ -130,9 +129,7 @@ private void loadAllKeys() { if (entrySetMethod.isEmpty()) { return; } - Value keyArray = ((ObjectReference) jvmValue).invokeMethod(context.getOwningThread().getThreadReference(), - entrySetMethod.get(), Collections.emptyList(), ObjectReference.INVOKE_SINGLE_THREADED); - + Value keyArray = VariableUtils.invokeRemoteVMMethod(context, jvmValue, entrySetMethod.get(), null); loadedKeys = (ArrayReference) keyArray; loadedValues = new Value[getChildrenCount()]; } catch (Exception ignored) { diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BTable.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BTable.java index 87172eee4eef..d3cbdcadca3c 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BTable.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/types/BTable.java @@ -149,8 +149,7 @@ private void populateTableSize() { tableSize = -1; return; } - Value size = ((ObjectReference) jvmValue).invokeMethod(getContext().getOwningThread().getThreadReference(), - method.get(), new ArrayList<>(), ObjectReference.INVOKE_SINGLE_THREADED); + Value size = VariableUtils.invokeRemoteVMMethod(context, jvmValue, method.get(), null); tableSize = ((IntegerValue) size).intValue(); } catch (Exception e) { this.tableSize = -1; @@ -191,8 +190,7 @@ private Value getIterator() throws Exception { if (getIteratorMethod.isEmpty()) { return null; } - return ((ObjectReference) jvmValue).invokeMethod(getContext().getOwningThread().getThreadReference(), - getIteratorMethod.get(), new ArrayList<>(), ObjectReference.INVOKE_SINGLE_THREADED); + return VariableUtils.invokeRemoteVMMethod(context, jvmValue, getIteratorMethod.get(), null); } private boolean hasNext(Value iterator) throws Exception { @@ -200,8 +198,7 @@ private boolean hasNext(Value iterator) throws Exception { if (hasNextMethod.isEmpty()) { return false; } - Value hasNext = ((ObjectReference) iterator).invokeMethod(getContext().getOwningThread().getThreadReference(), - hasNextMethod.get(), new ArrayList<>(), ObjectReference.INVOKE_SINGLE_THREADED); + Value hasNext = VariableUtils.invokeRemoteVMMethod(context, iterator, hasNextMethod.get(), null); return Boolean.parseBoolean(hasNext.toString()); } @@ -210,8 +207,7 @@ private Value nextElement(Value iterator) throws Exception { if (nextMethod.isEmpty()) { return null; } - return ((ObjectReference) iterator).invokeMethod(getContext().getOwningThread().getThreadReference(), - nextMethod.get(), new ArrayList<>(), ObjectReference.INVOKE_SINGLE_THREADED); + return VariableUtils.invokeRemoteVMMethod(context, iterator, nextMethod.get(), null); } private Value getValues(Value next) throws Exception { @@ -219,7 +215,6 @@ private Value getValues(Value next) throws Exception { if (getValuesMethod.isEmpty()) { return null; } - return ((ObjectReference) next).invokeMethod(getContext().getOwningThread().getThreadReference(), - getValuesMethod.get(), new ArrayList<>(), ObjectReference.INVOKE_SINGLE_THREADED); + return VariableUtils.invokeRemoteVMMethod(context, next, getValuesMethod.get(), null); } } From f0edeb7432f1bd4688d30ec977bdb6f817dfa44c Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Thu, 21 Oct 2021 16:57:39 +0530 Subject: [PATCH 06/17] Fix spotbugs warnings (cherry picked from commit dd820e2febb3bf5cd1238eded4907defbcc2c10c) --- .../debugadapter/JBallerinaDebugServer.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index f414b4974ffb..16ca8f5ef9a4 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -118,7 +118,7 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -945,8 +945,10 @@ private Variable[] computeGlobalScopeVariables(VariablesArguments requestArgs) { return scheduledVariables.stream() .map(varFuture -> { try { - return varFuture.get(1000, TimeUnit.MILLISECONDS); + return varFuture.get(); } catch (Exception ignored) { + // Todo - Refactor after implementing debug/trace logger + LOGGER.error("Failed to load some debug variables due to runtime exceptions."); return null; } }) @@ -967,29 +969,33 @@ private Variable[] computeLocalScopeVariables(VariablesArguments args) throws Ex if (VariableUtils.isLambdaParamMap(var)) { scheduledLambdaMapVariables.add(fetchLocalVariablesFromMap(args, stackFrame, var)); } else { - CompletableFuture dapVariable = computeVariableAsync(name, value, args.getVariablesReference()); - scheduledVariables.add(dapVariable); + CompletableFuture dapVar = computeVariableAsync(name, value, args.getVariablesReference()); + scheduledVariables.add(dapVar); } } List resolvedVariables = new ArrayList<>(); scheduledVariables.forEach(varFuture -> { try { - Variable variable = varFuture.get(1000, TimeUnit.MILLISECONDS); + Variable variable = varFuture.get(); if (variable != null) { resolvedVariables.add(variable); } - } catch (Exception ignored) { + } catch (InterruptedException | ExecutionException e) { + // Todo - Refactor after implementing debug/trace logger + LOGGER.error("Failed to load some debug variables due to runtime exceptions."); } }); scheduledLambdaMapVariables.forEach(varFuture -> { try { - Variable[] variables = varFuture.get(1000, TimeUnit.MILLISECONDS); + Variable[] variables = varFuture.get(); if (variables != null) { resolvedVariables.addAll(Arrays.asList(variables)); } - } catch (Exception ignored) { + } catch (InterruptedException | ExecutionException e) { + // Todo - Refactor after implementing debug/trace logger + LOGGER.error("Failed to load some debug variables due to runtime exceptions."); } }); return resolvedVariables.toArray(new Variable[0]); From 52fe6405f70c31960d6a0ba9895f5d48b2c91c65 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Sun, 24 Oct 2021 09:51:07 +0530 Subject: [PATCH 07/17] Fix JDI proxy issues (cherry picked from commit 48ce9be8259d61f1fc05494c99b22d5d8001cb37) --- .../debugadapter/jdi/StackFrameProxyImpl.java | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java index f8c8efde759b..c564ffa6bb89 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java @@ -58,6 +58,7 @@ public class StackFrameProxyImpl extends JdiProxy implements StackFrameProxy { private ClassLoaderReference myClassLoader; private ThreeState myIsObsolete = ThreeState.UNSURE; private Map myAllValues; + private static final int RETRY_COUNT = 5; public StackFrameProxyImpl(ThreadReferenceProxyImpl threadProxy, StackFrame frame, int fromBottomIndex) { super(threadProxy.getVirtualMachine()); @@ -72,7 +73,7 @@ public boolean isObsolete() throws JdiProxyException { return myIsObsolete.toBoolean(); } InvalidStackFrameException error = null; - for (int attempt = 0; attempt < 2; attempt++) { + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { Method method = getMethod(location()); boolean isObsolete = @@ -128,17 +129,25 @@ protected void clearCaches() { @Override public StackFrame getStackFrame() throws JdiProxyException { checkValid(); - if (myStackFrame == null) { + if (myStackFrame != null) { + return myStackFrame; + } + IncompatibleThreadStateException error = null; + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { final ThreadReference threadRef = myThreadProxy.getThreadReference(); myStackFrame = threadRef.frame(getFrameIndex()); - } catch (IndexOutOfBoundsException | IncompatibleThreadStateException e) { + return myStackFrame; + } catch (IncompatibleThreadStateException e) { + error = e; + clearCaches(); + } catch (IndexOutOfBoundsException e) { throw new JdiProxyException(e.getMessage(), e); } catch (ObjectCollectedException ignored) { throw new JdiProxyException("Thread has been collected"); } } - return myStackFrame; + throw new JdiProxyException("Thread has become invalid", error); } @Override @@ -162,7 +171,7 @@ public VirtualMachineProxyImpl getVirtualMachine() { @Override public Location location() throws JdiProxyException { InvalidStackFrameException error = null; - for (int attempt = 0; attempt < 2; attempt++) { + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { return getStackFrame().location(); } catch (InvalidStackFrameException e) { @@ -190,7 +199,7 @@ public String toString() { public ObjectReference thisObject() throws JdiProxyException { checkValid(); try { - for (int attempt = 0; attempt < 2; attempt++) { + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { if (myThisReference == null) { myThisReference = getStackFrame().thisObject(); @@ -221,7 +230,7 @@ public ObjectReference thisObject() throws JdiProxyException { public List visibleVariables() throws JdiProxyException { RuntimeException error = null; - for (int attempt = 0; attempt < 2; attempt++) { + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { final List list = getStackFrame().visibleVariables(); final List locals = new ArrayList<>(list.size()); @@ -252,7 +261,7 @@ public Value visibleValueByName(String name) throws JdiProxyException { protected LocalVariable visibleVariableByNameInt(String name) throws JdiProxyException { InvalidStackFrameException error = null; - for (int attempt = 0; attempt < 2; attempt++) { + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { try { return getStackFrame().visibleVariableByName(name); @@ -269,7 +278,7 @@ protected LocalVariable visibleVariableByNameInt(String name) throws JdiProxyExc public Value getValue(LocalVariableProxyImpl localVariable) throws JdiProxyException { Exception error = null; - for (int attempt = 0; attempt < 2; attempt++) { + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { Map values = getAllValues(); LocalVariable variable = localVariable.getVariable(); @@ -333,7 +342,7 @@ private Map getAllValues() throws JdiProxyException { public void setValue(LocalVariableProxyImpl localVariable, Value value) throws JdiProxyException, ClassNotLoadedException, InvalidTypeException { InvalidStackFrameException error = null; - for (int attempt = 0; attempt < 2; attempt++) { + for (int attempt = 0; attempt < RETRY_COUNT; attempt++) { try { final LocalVariable variable = localVariable.getVariable(); final StackFrame stackFrame = getStackFrame(); From 7faab2506af9ed667c5478ab48fb95de2d491975 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Tue, 7 Dec 2021 16:21:01 +0530 Subject: [PATCH 08/17] Synchronize JDI method invocations (cherry picked from commit 4bcfb4523e0c1b8d9ebeb8d851848d8e722081d5) --- .../evaluation/engine/invokable/RuntimeInstanceMethod.java | 2 +- .../evaluation/engine/invokable/RuntimeStaticMethod.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeInstanceMethod.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeInstanceMethod.java index da297339db81..8ddcf9e7f8a1 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeInstanceMethod.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeInstanceMethod.java @@ -44,7 +44,7 @@ public RuntimeInstanceMethod(SuspendedContext context, Value objectRef, Method m } @Override - protected Value invoke() throws EvaluationException { + protected synchronized Value invoke() throws EvaluationException { try { if (!(objectValueRef instanceof ObjectReference)) { throw createEvaluationException(FUNCTION_EXECUTION_ERROR, methodRef.name()); diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeStaticMethod.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeStaticMethod.java index f4696e8be06d..275c93e3e297 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeStaticMethod.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/evaluation/engine/invokable/RuntimeStaticMethod.java @@ -45,7 +45,7 @@ public RuntimeStaticMethod(SuspendedContext context, ReferenceType classRef, Met } @Override - protected Value invoke() throws EvaluationException { + protected synchronized Value invoke() throws EvaluationException { try { if (!(classRef instanceof ClassType)) { throw createEvaluationException(FUNCTION_EXECUTION_ERROR, methodRef.name()); From 07a3c6a5a5dddfb8be3d9bf88771d8d090812ba2 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Wed, 15 Dec 2021 10:56:49 +0530 Subject: [PATCH 09/17] Increase JDI retry count (cherry picked from commit 1d576c30df931cf729d36cc4e3ff35148108f501) --- .../debugadapter/jdi/StackFrameProxyImpl.java | 2 +- .../debugger/test/utils/DebugTestRunner.java | 13 +++++++------ .../adapter/variables/VariableVisibilityTest.java | 1 + 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java index c564ffa6bb89..52bc5659d590 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java @@ -58,7 +58,7 @@ public class StackFrameProxyImpl extends JdiProxy implements StackFrameProxy { private ClassLoaderReference myClassLoader; private ThreeState myIsObsolete = ThreeState.UNSURE; private Map myAllValues; - private static final int RETRY_COUNT = 5; + private static final int RETRY_COUNT = 20; public StackFrameProxyImpl(ThreadReferenceProxyImpl threadProxy, StackFrame frame, int fromBottomIndex) { super(threadProxy.getVirtualMachine()); diff --git a/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/DebugTestRunner.java b/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/DebugTestRunner.java index 5c77084cf732..17d41fc3cb49 100644 --- a/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/DebugTestRunner.java +++ b/tests/jballerina-debugger-integration-test/src/main/java/org/ballerinalang/debugger/test/utils/DebugTestRunner.java @@ -520,25 +520,26 @@ public boolean waitForDebugTermination(long timeoutMillis) { */ public Map fetchVariables(StoppedEventArguments args, VariableScope scope) throws BallerinaTestException { - Map variables = new HashMap<>(); if (!hitListener.getConnector().isConnected()) { - return variables; + throw new BallerinaTestException("Debug server is not connected."); } - StackTraceArguments stackTraceArgs = new StackTraceArguments(); + StackTraceArguments traceArgs = new StackTraceArguments(); VariablesArguments variableArgs = new VariablesArguments(); ScopesArguments scopeArgs = new ScopesArguments(); - stackTraceArgs.setThreadId(args.getThreadId()); + traceArgs.setThreadId(args.getThreadId()); try { - StackTraceResponse stackResp = hitListener.getConnector().getRequestManager().stackTrace(stackTraceArgs); + StackTraceResponse stackResp = hitListener.getConnector().getRequestManager().stackTrace(traceArgs); StackFrame[] stackFrames = stackResp.getStackFrames(); if (stackFrames.length == 0) { - return variables; + throw new BallerinaTestException("Stack frame response does not contain any frames"); } scopeArgs.setFrameId(scope == VariableScope.LOCAL ? stackFrames[0].getId() : -stackFrames[0].getId()); ScopesResponse scopesResp = hitListener.getConnector().getRequestManager().scopes(scopeArgs); variableArgs.setVariablesReference(scopesResp.getScopes()[0].getVariablesReference()); VariablesResponse variableResp = hitListener.getConnector().getRequestManager().variables(variableArgs); + + Map variables = new HashMap<>(); Arrays.stream(variableResp.getVariables()).forEach(variable -> variables.put(variable.getName(), variable)); return variables; } catch (Exception e) { diff --git a/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/variables/VariableVisibilityTest.java b/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/variables/VariableVisibilityTest.java index 74ef3c28987a..321ab0903cb1 100644 --- a/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/variables/VariableVisibilityTest.java +++ b/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/variables/VariableVisibilityTest.java @@ -337,6 +337,7 @@ public void localVariableChildrenVisibilityTest() throws BallerinaTestException localVariables = debugTestRunner.fetchVariables(debugHitInfo.getRight(), VariableScope.LOCAL); // xml child variable visibility test + Assert.assertNotNull(localVariables.get("xmlVar")); Map xmlChildVariables = debugTestRunner.fetchChildVariables(localVariables.get("xmlVar")); debugTestRunner.assertVariable(xmlChildVariables, "attributes", "XMLAttributeMap (size = 16)", "map"); debugTestRunner.assertVariable(xmlChildVariables, "children", "XMLSequence (size = 2)", "xml"); From f8846125d9cf52ed6b0f270d1f784cdc2f336bed Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Mon, 20 Dec 2021 15:02:51 +0530 Subject: [PATCH 10/17] Use a cached thread pool for variable processing (cherry picked from commit c3524e8e946d0b61df1e55d5e236b5ede82dc99f) --- .../ballerinalang/debugadapter/JBallerinaDebugServer.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index 16ca8f5ef9a4..b9ec9bc91761 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -119,6 +119,8 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -153,6 +155,7 @@ public class JBallerinaDebugServer implements IDebugProtocolServer { private final Map scopeIdToFrameIds = new HashMap<>(); private final Map variableToStackFrames = new ConcurrentHashMap<>(); private final Map loadedCompoundVariables = new ConcurrentHashMap<>(); + private final ExecutorService variableExecutor = Executors.newCachedThreadPool(); private static final Logger LOGGER = LoggerFactory.getLogger(JBallerinaDebugServer.class); private static final String SCOPE_NAME_LOCAL = "Local"; @@ -1051,11 +1054,11 @@ private CompletableFuture computeVariableAsync(String name, Value valu updateVariableToStackFrameMap(stackFrameRef, variableReference); } return variable.getDapVariable(); - }); + }, variableExecutor); } private CompletableFuture computeChildVariablesAsync(VariablesArguments args) { - return CompletableFuture.supplyAsync(() -> computeChildVariables(args)); + return CompletableFuture.supplyAsync(() -> computeChildVariables(args), variableExecutor); } private Variable[] computeChildVariables(VariablesArguments args) { From 564aab5a92034188085bd8489f37e8c0e315a946 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Thu, 6 Jan 2022 10:16:44 +0530 Subject: [PATCH 11/17] Improve exception handling when accessing thread references (cherry picked from commit 066ee2111974b65ed1e8b78a6fdcdccb0b073dec) --- .../org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java index 52bc5659d590..d3423665bca8 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java @@ -145,6 +145,8 @@ public StackFrame getStackFrame() throws JdiProxyException { throw new JdiProxyException(e.getMessage(), e); } catch (ObjectCollectedException ignored) { throw new JdiProxyException("Thread has been collected"); + } catch (Exception e) { + throw new JdiProxyException("Unknown error when trying to access thread reference", e); } } throw new JdiProxyException("Thread has become invalid", error); From 3990ca352c9f464d1a0746332d78c60d088bcc34 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Tue, 8 Feb 2022 16:07:07 +0530 Subject: [PATCH 12/17] Add minor fixes (cherry picked from commit d81b5950ad8a85e08dadcd83a47081a9965474e0) --- .../ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java | 4 ++++ .../ballerinalang/debugadapter/variable/VariableUtils.java | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java index d3423665bca8..e3cb2d9a522a 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/jdi/StackFrameProxyImpl.java @@ -141,6 +141,10 @@ public StackFrame getStackFrame() throws JdiProxyException { } catch (IncompatibleThreadStateException e) { error = e; clearCaches(); + try { + Thread.sleep(10); + } catch (InterruptedException ignored) { + } } catch (IndexOutOfBoundsException e) { throw new JdiProxyException(e.getMessage(), e); } catch (ObjectCollectedException ignored) { diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java index 56b864414432..9a261bad2590 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java @@ -18,7 +18,6 @@ import com.sun.jdi.ClassType; import com.sun.jdi.Field; -import com.sun.jdi.IncompatibleThreadStateException; import com.sun.jdi.Method; import com.sun.jdi.ObjectReference; import com.sun.jdi.Value; @@ -208,7 +207,7 @@ public static Value invokeRemoteVMMethod(SuspendedContext context, Value jvmObje try { return ((ObjectReference) jvmObject).invokeMethod(context.getOwningThread() .getThreadReference(), method, arguments, ObjectReference.INVOKE_SINGLE_THREADED); - } catch (IncompatibleThreadStateException e) { + } catch (Exception e) { try { Thread.sleep(10); } catch (InterruptedException ignored) { From c58d0e7bbe71b36173c139bedbf10087dc4b2232 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Tue, 23 May 2023 15:01:59 +0530 Subject: [PATCH 13/17] Fix build failures --- .../ballerinalang/debugadapter/JBallerinaDebugServer.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index b9ec9bc91761..c9886720424f 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -121,6 +121,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -144,6 +145,7 @@ public class JBallerinaDebugServer implements IDebugProtocolServer { private ClientConfigHolder clientConfigHolder; private DebugExecutionManager executionManager; private JDIEventProcessor eventProcessor; + private DebugExpressionEvaluator evaluator; private final ExecutionContext context; private ThreadReferenceProxyImpl activeThread; private SuspendedContext suspendedContext; @@ -466,7 +468,8 @@ public CompletableFuture evaluate(EvaluateArguments args) { if (args.getFrameId() == null) { context.getOutputLogger().sendErrorOutput(EvaluationExceptionKind.PREFIX + "Remote VM is not suspended " + "and still in running state."); - return CompletableFuture.completedFuture(response); + + return CompletableFuture.completedFuture(new EvaluateResponse()); } // Evaluate arguments context becomes `variables` when we do a `Copy Value` from VS Code, and From c24eebbd194b6042b5a0ee1787aa4148a1694acf Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Mon, 11 Nov 2024 12:09:15 +0530 Subject: [PATCH 14/17] Fix a merge conflict --- .../org/ballerinalang/debugadapter/variable/VariableUtils.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java index c33c72c284b0..d953c1c5a8fd 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/variable/VariableUtils.java @@ -174,8 +174,7 @@ public static String getStringFrom(Value stringValue) { */ public static String getStringValue(SuspendedContext context, Value jvmObject) { try { - Value result = invokeRemoteVMMethod(context, jvmObject, METHOD_STRINGVALUE, - Collections.singletonList(null)); + Value result = invokeRemoteVMMethod(context, jvmObject, METHOD_STR_VALUE, Collections.singletonList(null)); return getStringFrom(result); } catch (DebugVariableException e) { return UNKNOWN_VALUE; From 10a779eac9919a20ced218ecd3f9a877525477e6 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Tue, 12 Nov 2024 11:11:13 +0530 Subject: [PATCH 15/17] Use single threaded executor for variable processing to avoid intermittent VM crashes --- .../ballerinalang/debugadapter/JBallerinaDebugServer.java | 6 +++--- .../org/ballerinalang/debugadapter/JDIEventProcessor.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index 6897deb776e1..2cf3f7302e68 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -145,7 +145,6 @@ public class JBallerinaDebugServer implements IDebugProtocolServer { private ClientConfigHolder clientConfigHolder; private DebugExecutionManager executionManager; private JDIEventProcessor eventProcessor; - private DebugExpressionEvaluator evaluator; private final ExecutionContext context; private ThreadReferenceProxyImpl activeThread; private SuspendedContext suspendedContext; @@ -157,7 +156,8 @@ public class JBallerinaDebugServer implements IDebugProtocolServer { private final Map scopeIdToFrameIds = new HashMap<>(); private final Map variableToStackFrames = new ConcurrentHashMap<>(); private final Map loadedCompoundVariables = new ConcurrentHashMap<>(); - private final ExecutorService variableExecutor = Executors.newCachedThreadPool(); + // Multi-threading is avoided here due to observed intermittent VM crashes, likely related to JDI limitations. + private final ExecutorService variableExecutor = Executors.newSingleThreadExecutor(); private static final Logger LOGGER = LoggerFactory.getLogger(JBallerinaDebugServer.class); private static final String SCOPE_NAME_LOCAL = "Local"; @@ -926,7 +926,7 @@ private void attachToRemoteVM(String hostName, int portName) throws IOException, EventRequestManager erm = context.getEventManager(); ClassPrepareRequest classPrepareRequest = erm.createClassPrepareRequest(); classPrepareRequest.enable(); - eventProcessor.startListening(); + eventProcessor.listenAsync(); } /** diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java index b538daf2e89b..e1e32101cbc8 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JDIEventProcessor.java @@ -74,7 +74,7 @@ List getStepRequests() { /** * Asynchronously listens and processes the incoming JDI events. */ - void startListening() { + void listenAsync() { CompletableFuture.runAsync(() -> { isRemoteVmAttached = true; while (isRemoteVmAttached) { From a2173fdde109001a33018b31568f1ab92c287e4f Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Tue, 12 Nov 2024 15:14:58 +0530 Subject: [PATCH 16/17] Fix evaluation failures --- .../debugadapter/JBallerinaDebugServer.java | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index 2cf3f7302e68..552d5d9577c0 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -38,6 +38,7 @@ import org.ballerinalang.debugadapter.config.ClientConfigHolder; import org.ballerinalang.debugadapter.config.ClientConfigurationException; import org.ballerinalang.debugadapter.config.ClientLaunchConfigHolder; +import org.ballerinalang.debugadapter.evaluation.BExpressionValue; import org.ballerinalang.debugadapter.evaluation.DebugExpressionEvaluator; import org.ballerinalang.debugadapter.evaluation.EvaluationException; import org.ballerinalang.debugadapter.evaluation.EvaluationExceptionKind; @@ -149,6 +150,7 @@ public class JBallerinaDebugServer implements IDebugProtocolServer { private ThreadReferenceProxyImpl activeThread; private SuspendedContext suspendedContext; private DebugOutputLogger outputLogger; + private DebugExpressionEvaluator evaluator; private final AtomicInteger nextVarReference = new AtomicInteger(); private final Map stackFrames = new HashMap<>(); @@ -335,7 +337,7 @@ public CompletableFuture threads() { @Override public CompletableFuture pause(PauseArguments args) { VirtualMachineProxyImpl debuggeeVM = context.getDebuggeeVM(); - // Checks if the program VM is a read-only VM. (If a method which would modify the state of the VM is called + // Checks if the program VM is a read-only VM. If a method which modified the state of the VM is called // on a read-only VM, a `VMCannotBeModifiedException` will be thrown. if (!debuggeeVM.canBeModified()) { getOutputLogger().sendConsoleOutput("Failed to suspend the remote VM due to: pause requests are not " + @@ -490,23 +492,22 @@ public CompletableFuture evaluate(EvaluateArguments args) { return CompletableFuture.completedFuture(response); } - return CompletableFuture.supplyAsync(() -> { - try { - StackFrameProxyImpl frame = stackFrames.get(args.getFrameId()); - SuspendedContext suspendedContext = new SuspendedContext(context, activeThread, frame); - EvaluationContext evaluationContext = new EvaluationContext(suspendedContext); - DebugExpressionEvaluator evaluator = new DebugExpressionEvaluator(evaluationContext); - evaluator.setExpression(args.getExpression()); - BVariable evaluationResult = evaluator.evaluate().getBVariable(); - return constructEvaluateResponse(args, evaluationResult); - } catch (EvaluationException e) { - context.getOutputLogger().sendErrorOutput(e.getMessage()); - return new EvaluateResponse(); - } catch (Exception e) { - context.getOutputLogger().sendErrorOutput(EvaluationExceptionKind.PREFIX + "internal error"); - return new EvaluateResponse(); - } - }); + try { + StackFrameProxyImpl frame = stackFrames.get(args.getFrameId()); + SuspendedContext suspendedContext = new SuspendedContext(context, activeThread, frame); + EvaluationContext evaluationContext = new EvaluationContext(suspendedContext); + evaluator = Objects.requireNonNullElse(evaluator, new DebugExpressionEvaluator(evaluationContext)); + evaluator.setExpression(args.getExpression()); + BExpressionValue evaluateResult = evaluator.evaluate(); + EvaluateResponse evaluateResponse = constructEvaluateResponse(args, evaluateResult.getBVariable()); + return CompletableFuture.completedFuture(evaluateResponse); + } catch (EvaluationException e) { + context.getOutputLogger().sendErrorOutput(e.getMessage()); + return CompletableFuture.completedFuture(new EvaluateResponse()); + } catch (Exception e) { + context.getOutputLogger().sendErrorOutput(EvaluationExceptionKind.PREFIX + "internal error"); + return CompletableFuture.completedFuture(new EvaluateResponse()); + } } @Override @@ -648,7 +649,7 @@ void terminateDebugServer(boolean terminateDebuggee, boolean logsEnabled) { if (context.getLaunchedProcess().isPresent() && context.getLaunchedProcess().get().isAlive()) { killProcessWithDescendants(context.getLaunchedProcess().get()); } - // Destroys remote VM process, if `terminteDebuggee' flag is set. + // Destroys remote VM process, if `terminateDebuggee' flag is set. if (terminateDebuggee && context.getDebuggeeVM() != null) { int exitCode = 0; if (context.getDebuggeeVM().process() != null) { @@ -845,7 +846,7 @@ static boolean isBalStackFrame(com.sun.jdi.StackFrame frame) { } /** - * Validates a given ballerina stack frame for for its source information. + * Validates a given ballerina stack frame for its source information. * * @param stackFrame ballerina stack frame * @return true if its a valid ballerina frame @@ -1189,6 +1190,7 @@ private EvaluateResponse constructEvaluateResponse(EvaluateArguments args, BVari */ private void clearState() { suspendedContext = null; + evaluator = null; activeThread = null; stackFrames.clear(); loadedCompoundVariables.clear(); From d7a7723d3dae35652c5b52acd86621824654627d Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Tue, 12 Nov 2024 20:30:01 +0530 Subject: [PATCH 17/17] Fix step-over behaviour for init methods --- .../debugadapter/BreakpointProcessor.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java index 8433049daa2f..8cb6dd0d22cc 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BreakpointProcessor.java @@ -234,7 +234,9 @@ void activateDynamicBreakPoints(int threadId, DynamicBreakpointMode mode, boolea Location currentLocation = validFrames.get(0).getJStackFrame().location(); Optional prevLocation = context.getPrevLocation(); if (!validate || prevLocation.isEmpty() || !isWithinSameSource(currentLocation, prevLocation.get())) { - doActivateDynamicBreakPoints(currentLocation); + context.getEventManager().deleteAllBreakpoints(); + configureBreakpointsForMethod(currentLocation); + context.setPrevLocation(currentLocation); } context.setPrevLocation(currentLocation); } @@ -243,7 +245,11 @@ void activateDynamicBreakPoints(int threadId, DynamicBreakpointMode mode, boolea // temporary breakpoint on the location of its invocation. This is supposed to handle the situations where // the user wants to step over on an exit point of the current function. if (mode == DynamicBreakpointMode.CALLER && validFrames.size() > 1) { - doActivateDynamicBreakPoints(validFrames.get(1).getJStackFrame().location()); + context.getEventManager().deleteAllBreakpoints(); + for (int frameIndex = 1; frameIndex < validFrames.size(); frameIndex++) { + configureBreakpointsForMethod(validFrames.get(frameIndex).getJStackFrame().location()); + } + context.setPrevLocation(validFrames.get(0).getJStackFrame().location()); } } catch (JdiProxyException e) { LOGGER.error(e.getMessage()); @@ -254,12 +260,6 @@ void activateDynamicBreakPoints(int threadId, DynamicBreakpointMode mode, boolea } } - private void doActivateDynamicBreakPoints(Location location) { - context.getEventManager().deleteAllBreakpoints(); - configureBreakpointsForMethod(location); - context.setPrevLocation(location); - } - /** * Checks whether the given two locations are within the same source file. *