Skip to content

Commit 216a7c6

Browse files
Support listing field variables async (#437)
1 parent 9ce0aef commit 216a7c6

File tree

4 files changed

+169
-68
lines changed

4 files changed

+169
-68
lines changed

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ public static void recordInfo(String key, Object value) {
196196
usageDataLogger.log(Level.INFO, "session info", map);
197197
}
198198

199+
public static void recordInfo(String description, Map<String, Object> data) {
200+
usageDataLogger.log(Level.INFO, description, data);
201+
}
202+
199203
/**
200204
* Record counts for each user errors encountered.
201205
*/

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/AttachRequestHandler.java

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -62,59 +62,66 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
6262
context.setStepFilters(attachArguments.stepFilters);
6363
context.setLocalDebugging(isLocalHost(attachArguments.hostName));
6464

65+
Map<String, Object> traceInfo = new HashMap<>();
66+
traceInfo.put("localAttach", context.isLocalDebugging());
67+
6568
IVirtualMachineManagerProvider vmProvider = context.getProvider(IVirtualMachineManagerProvider.class);
6669
vmHandler.setVmProvider(vmProvider);
6770
IDebugSession debugSession = null;
6871
try {
69-
logger.info(String.format("Trying to attach to remote debuggee VM %s:%d .", attachArguments.hostName, attachArguments.port));
70-
debugSession = DebugUtility.attach(vmProvider.getVirtualMachineManager(), attachArguments.hostName, attachArguments.port,
71-
attachArguments.timeout);
72-
context.setDebugSession(debugSession);
73-
vmHandler.connectVirtualMachine(debugSession.getVM());
74-
logger.info("Attaching to debuggee VM succeeded.");
75-
} catch (IOException | IllegalConnectorArgumentsException e) {
76-
throw AdapterUtils.createCompletionException(
77-
String.format("Failed to attach to remote debuggee VM. Reason: %s", e.toString()),
78-
ErrorCode.ATTACH_FAILURE,
79-
e);
80-
}
72+
try {
73+
logger.info(String.format("Trying to attach to remote debuggee VM %s:%d .", attachArguments.hostName, attachArguments.port));
74+
debugSession = DebugUtility.attach(vmProvider.getVirtualMachineManager(), attachArguments.hostName, attachArguments.port,
75+
attachArguments.timeout);
76+
context.setDebugSession(debugSession);
77+
vmHandler.connectVirtualMachine(debugSession.getVM());
78+
logger.info("Attaching to debuggee VM succeeded.");
79+
} catch (IOException | IllegalConnectorArgumentsException e) {
80+
throw AdapterUtils.createCompletionException(
81+
String.format("Failed to attach to remote debuggee VM. Reason: %s", e.toString()),
82+
ErrorCode.ATTACH_FAILURE,
83+
e);
84+
}
8185

82-
Map<String, Object> options = new HashMap<>();
83-
options.put(Constants.DEBUGGEE_ENCODING, context.getDebuggeeEncoding());
84-
if (attachArguments.projectName != null) {
85-
options.put(Constants.PROJECT_NAME, attachArguments.projectName);
86-
}
87-
// TODO: Clean up the initialize mechanism
88-
ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class);
89-
sourceProvider.initialize(context, options);
90-
// If the debugger and debuggee run at the different JVM platforms, show a warning message.
91-
if (debugSession != null) {
92-
String debuggeeVersion = debugSession.getVM().version();
93-
String debuggerVersion = sourceProvider.getJavaRuntimeVersion(attachArguments.projectName);
94-
if (StringUtils.isNotBlank(debuggerVersion) && !debuggerVersion.equals(debuggeeVersion)) {
95-
String warnMessage = String.format("[Warn] The debugger and the debuggee are running in different versions of JVMs. "
96-
+ "You could see wrong source mapping results.\n"
97-
+ "Debugger JVM version: %s\n"
98-
+ "Debuggee JVM version: %s", debuggerVersion, debuggeeVersion);
99-
logger.warning(warnMessage);
100-
context.getProtocolServer().sendEvent(Events.OutputEvent.createConsoleOutput(warnMessage));
86+
Map<String, Object> options = new HashMap<>();
87+
options.put(Constants.DEBUGGEE_ENCODING, context.getDebuggeeEncoding());
88+
if (attachArguments.projectName != null) {
89+
options.put(Constants.PROJECT_NAME, attachArguments.projectName);
10190
}
91+
// TODO: Clean up the initialize mechanism
92+
ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class);
93+
sourceProvider.initialize(context, options);
94+
// If the debugger and debuggee run at the different JVM platforms, show a warning message.
95+
if (debugSession != null) {
96+
String debuggeeVersion = debugSession.getVM().version();
97+
String debuggerVersion = sourceProvider.getJavaRuntimeVersion(attachArguments.projectName);
98+
if (StringUtils.isNotBlank(debuggerVersion) && !debuggerVersion.equals(debuggeeVersion)) {
99+
String warnMessage = String.format("[Warn] The debugger and the debuggee are running in different versions of JVMs. "
100+
+ "You could see wrong source mapping results.\n"
101+
+ "Debugger JVM version: %s\n"
102+
+ "Debuggee JVM version: %s", debuggerVersion, debuggeeVersion);
103+
logger.warning(warnMessage);
104+
context.getProtocolServer().sendEvent(Events.OutputEvent.createConsoleOutput(warnMessage));
105+
}
102106

103-
EventRequest request = debugSession.getVM().eventRequestManager().createVMDeathRequest();
104-
request.setSuspendPolicy(EventRequest.SUSPEND_NONE);
105-
long sent = System.currentTimeMillis();
106-
request.enable();
107-
long received = System.currentTimeMillis();
108-
logger.info("Network latency for JDWP command: " + (received - sent) + "ms");
109-
UsageDataSession.recordInfo("networkLatency", (received - sent));
110-
}
107+
EventRequest request = debugSession.getVM().eventRequestManager().createVMDeathRequest();
108+
request.setSuspendPolicy(EventRequest.SUSPEND_NONE);
109+
long sent = System.currentTimeMillis();
110+
request.enable();
111+
long received = System.currentTimeMillis();
112+
logger.info("Network latency for JDWP command: " + (received - sent) + "ms");
113+
traceInfo.put("networkLatency", (received - sent));
114+
}
111115

112-
IEvaluationProvider evaluationProvider = context.getProvider(IEvaluationProvider.class);
113-
evaluationProvider.initialize(context, options);
114-
IHotCodeReplaceProvider hcrProvider = context.getProvider(IHotCodeReplaceProvider.class);
115-
hcrProvider.initialize(context, options);
116-
ICompletionsProvider completionsProvider = context.getProvider(ICompletionsProvider.class);
117-
completionsProvider.initialize(context, options);
116+
IEvaluationProvider evaluationProvider = context.getProvider(IEvaluationProvider.class);
117+
evaluationProvider.initialize(context, options);
118+
IHotCodeReplaceProvider hcrProvider = context.getProvider(IHotCodeReplaceProvider.class);
119+
hcrProvider.initialize(context, options);
120+
ICompletionsProvider completionsProvider = context.getProvider(ICompletionsProvider.class);
121+
completionsProvider.initialize(context, options);
122+
} finally {
123+
UsageDataSession.recordInfo("attach debug info", traceInfo);
124+
}
118125

119126
// Send an InitializedEvent to indicate that the debugger is ready to accept configuration requests
120127
// (e.g. SetBreakpointsRequest, SetExceptionBreakpointsRequest).

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import com.sun.jdi.InternalException;
6161
import com.sun.jdi.InvalidStackFrameException;
6262
import com.sun.jdi.ObjectReference;
63+
import com.sun.jdi.ReferenceType;
6364
import com.sun.jdi.StackFrame;
6465
import com.sun.jdi.StringReference;
6566
import com.sun.jdi.Type;
@@ -197,7 +198,7 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
197198
if (varArgs.count > 0) {
198199
childrenList = VariableUtils.listFieldVariables(containerObj, varArgs.start, varArgs.count);
199200
} else {
200-
childrenList = VariableUtils.listFieldVariables(containerObj, showStaticVariables);
201+
childrenList = VariableUtils.listFieldVariables(containerObj, showStaticVariables, context.asyncJDWP());
201202
}
202203
}
203204
} catch (AbsentInformationException e) {
@@ -210,12 +211,24 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
210211

211212
// Find variable name duplicates
212213
Set<String> duplicateNames = getDuplicateNames(childrenList.stream().map(var -> var.name).collect(Collectors.toList()));
213-
Map<Variable, String> variableNameMap = new HashMap<>();
214-
if (!duplicateNames.isEmpty()) {
215-
Map<String, List<Variable>> duplicateVars = childrenList.stream()
216-
.filter(var -> duplicateNames.contains(var.name)).collect(Collectors.groupingBy(var -> var.name, Collectors.toList()));
214+
List<Variable> duplicateVars = childrenList.stream()
215+
.filter(var -> duplicateNames.contains(var.name))
216+
.collect(Collectors.toList());
217+
// Since JDI caches the fetched properties locally, in async mode we can warm up the JDI cache in advance.
218+
if (context.asyncJDWP()) {
219+
try {
220+
AsyncJdwpUtils.await(warmUpJDICache(childrenList, duplicateVars));
221+
} catch (CompletionException | CancellationException e) {
222+
response.body = new Responses.VariablesResponseBody(list);
223+
return CompletableFuture.completedFuture(response);
224+
}
225+
}
217226

218-
duplicateVars.forEach((k, duplicateVariables) -> {
227+
Map<Variable, String> variableNameMap = new HashMap<>();
228+
if (!duplicateVars.isEmpty()) {
229+
Map<String, List<Variable>> duplicateVarGroups = duplicateVars.stream()
230+
.collect(Collectors.groupingBy(var -> var.name, Collectors.toList()));
231+
duplicateVarGroups.forEach((k, duplicateVariables) -> {
219232
Set<String> declarationTypeNames = new HashSet<>();
220233
boolean declarationTypeNameConflict = false;
221234
// try use type formatter to resolve name conflict
@@ -243,16 +256,6 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
243256
});
244257
}
245258

246-
// Since JDI caches the fetched properties locally, in async mode we can warm up the JDI cache in advance.
247-
if (context.asyncJDWP()) {
248-
try {
249-
AsyncJdwpUtils.await(warmUpJDICache(childrenList));
250-
} catch (CompletionException | CancellationException e) {
251-
response.body = new Responses.VariablesResponseBody(list);
252-
return CompletableFuture.completedFuture(response);
253-
}
254-
}
255-
256259
for (Variable javaVariable : childrenList) {
257260
Value value = javaVariable.value;
258261
String name = javaVariable.name;
@@ -441,29 +444,46 @@ private List<Variable> getVariablesOfFrameAsync(StackFrame frame, boolean showSt
441444
return result;
442445
}
443446

444-
private CompletableFuture<Void> warmUpJDICache(List<Variable> variables) {
445-
List<CompletableFuture<Void>> fetchVariableInfoFutures = new ArrayList<>();
447+
private CompletableFuture<Void> warmUpJDICache(List<Variable> variables, List<Variable> duplicatedVars) {
448+
List<CompletableFuture<Void>> futures = new ArrayList<>();
449+
if (duplicatedVars != null && !duplicatedVars.isEmpty()) {
450+
Set<Type> declaringTypes = new HashSet<>();
451+
duplicatedVars.forEach((var) -> {
452+
Type declarationType = var.getDeclaringType();
453+
if (declarationType != null) {
454+
declaringTypes.add(declarationType);
455+
}
456+
});
457+
458+
for (Type type : declaringTypes) {
459+
if (type instanceof ReferenceType) {
460+
// JDWP Command: RT_SIGNATURE
461+
futures.add(AsyncJdwpUtils.runAsync(() -> type.signature()));
462+
}
463+
}
464+
}
465+
446466
for (Variable javaVariable : variables) {
447467
Value value = javaVariable.value;
448468
if (value instanceof ArrayReference) {
449469
// JDWP Command: AR_LENGTH
450-
fetchVariableInfoFutures.add(AsyncJdwpUtils.runAsync(() -> ((ArrayReference) value).length()));
470+
futures.add(AsyncJdwpUtils.runAsync(() -> ((ArrayReference) value).length()));
451471
} else if (value instanceof StringReference) {
452472
// JDWP Command: SR_VALUE
453-
fetchVariableInfoFutures.add(AsyncJdwpUtils.runAsync(() -> {
473+
futures.add(AsyncJdwpUtils.runAsync(() -> {
454474
String strValue = ((StringReference) value).value();
455475
javaVariable.value = new StringReferenceProxy((StringReference) value, strValue);
456476
}));
457477
}
458478

459479
if (value instanceof ObjectReference) {
460480
// JDWP Command: OR_REFERENCE_TYPE, RT_SIGNATURE
461-
fetchVariableInfoFutures.add(AsyncJdwpUtils.runAsync(() -> {
481+
futures.add(AsyncJdwpUtils.runAsync(() -> {
462482
value.type().signature();
463483
}));
464484
}
465485
}
466486

467-
return CompletableFuture.allOf(fetchVariableInfoFutures.toArray(new CompletableFuture[0]));
487+
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
468488
}
469489
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableUtils.java

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
package com.microsoft.java.debug.core.adapter.variables;
1313

1414
import java.util.ArrayList;
15+
import java.util.Collections;
16+
import java.util.HashSet;
1517
import java.util.List;
1618
import java.util.Map;
1719
import java.util.Objects;
20+
import java.util.Set;
1821
import java.util.concurrent.CompletableFuture;
1922
import java.util.concurrent.CompletionException;
2023
import java.util.function.Consumer;
@@ -33,6 +36,8 @@
3336
import com.sun.jdi.AbsentInformationException;
3437
import com.sun.jdi.ArrayReference;
3538
import com.sun.jdi.ArrayType;
39+
import com.sun.jdi.ClassType;
40+
import com.sun.jdi.InterfaceType;
3641
import com.sun.jdi.ClassNotLoadedException;
3742
import com.sun.jdi.Field;
3843
import com.sun.jdi.InternalException;
@@ -77,6 +82,10 @@ public static boolean hasChildren(Value value, boolean includeStatic) {
7782
* when there is any error in retrieving information
7883
*/
7984
public static List<Variable> listFieldVariables(ObjectReference obj, boolean includeStatic) throws AbsentInformationException {
85+
return listFieldVariables(obj, includeStatic, false);
86+
}
87+
88+
public static List<Variable> listFieldVariables(ObjectReference obj, boolean includeStatic, boolean async) throws AbsentInformationException {
8089
List<Variable> res = new ArrayList<>();
8190
ReferenceType type = obj.referenceType();
8291
if (type instanceof ArrayType) {
@@ -89,7 +98,7 @@ public static List<Variable> listFieldVariables(ObjectReference obj, boolean inc
8998
}
9099
return res;
91100
}
92-
List<Field> fields = type.allFields().stream().filter(t -> includeStatic || !t.isStatic())
101+
List<Field> fields = resolveAllFields(type, async).stream().filter(t -> includeStatic || !t.isStatic())
93102
.sorted((a, b) -> {
94103
try {
95104
boolean v1isStatic = a.isStatic();
@@ -466,6 +475,67 @@ private static <T, R> CompletableFuture<List<R>> bulkFetchValuesAsync(List<T> el
466475
return AsyncJdwpUtils.all(futures);
467476
}
468477

478+
private static List<Field> resolveAllFields(ReferenceType type, boolean async) {
479+
if (async) {
480+
return resolveAllFieldsAsync(type);
481+
}
482+
483+
return type.allFields();
484+
}
485+
486+
private static List<Field> resolveAllFieldsAsync(ReferenceType type) {
487+
Set<Field> result = Collections.synchronizedSet(new HashSet<>());
488+
AsyncJdwpUtils.await(resolveAllFieldsAsync(type, result));
489+
List<Field> fields = new ArrayList<>();
490+
fields.addAll(result);
491+
return fields;
492+
}
493+
494+
private static CompletableFuture<Void> resolveAllFieldsAsync(ReferenceType type, Set<Field> result) {
495+
List<CompletableFuture<Void>> futures = new ArrayList<>();
496+
// JDWP Command: RT_FIELDS_WITH_GENERIC
497+
futures.add(
498+
AsyncJdwpUtils.runAsync(() -> result.addAll(type.fields()))
499+
);
500+
501+
if (type instanceof ClassType) {
502+
ClassType classType = (ClassType) type;
503+
// JDWP Command: RT_INTERFACES
504+
futures.add(AsyncJdwpUtils.supplyAsync(() -> classType.interfaces())
505+
.thenCompose((its) -> {
506+
List<CompletableFuture<Void>> itFutures = new ArrayList<>();
507+
for (InterfaceType it : its) {
508+
itFutures.add(resolveAllFieldsAsync(it, result));
509+
}
510+
511+
return CompletableFuture.allOf(itFutures.toArray(new CompletableFuture[0]));
512+
}));
513+
514+
// JDWP Command: CT_SUPERCLASS
515+
AsyncJdwpUtils.supplyAsync(() -> classType.superclass())
516+
.thenCompose((superclass) -> {
517+
if (superclass != null) {
518+
return resolveAllFieldsAsync(superclass, result);
519+
}
520+
return CompletableFuture.completedFuture(null);
521+
});
522+
} else if (type instanceof InterfaceType) {
523+
InterfaceType interfaceType = (InterfaceType) type;
524+
// JDWP Command: RT_INTERFACES
525+
futures.add(AsyncJdwpUtils.supplyAsync(() -> interfaceType.superinterfaces())
526+
.thenCompose((its) -> {
527+
List<CompletableFuture<Void>> itFutures = new ArrayList<>();
528+
for (InterfaceType it : its) {
529+
itFutures.add(resolveAllFieldsAsync(it, result));
530+
}
531+
532+
return CompletableFuture.allOf(itFutures.toArray(new CompletableFuture[0]));
533+
}));
534+
}
535+
536+
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
537+
}
538+
469539
private VariableUtils() {
470540

471541
}

0 commit comments

Comments
 (0)